addGuests: deduplication keeps the first email but the last guest object
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 72/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Quiet
- Tech stack
- typescript
- Domain
- backend
Research direction
Start in packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts and inspect sanitizeAndFilterGuests(), deduplicateGuestEmails(), and emailToGuestMap. Check the call from packages/features/bookings/services/BookingAttendeesService.ts:103. Done means duplicate base emails consistently retain the first guest object, including its name, email, timeZone, and language.
Written by the indexing model from the issue text.
Description
Issue Summary
sanitizeAndFilterGuests() in packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts deduplicates guests by their base email (plus-addressing stripped), but it does that in two places that disagree about which duplicate wins.
deduplicateGuestEmails() keeps the first occurrence:
function deduplicateGuestEmails(guests: string[]): string[] {
const seenBaseEmails = new Set<string>();
return guests.filter((guest) => {
const baseEmail = extractBaseEmail(guest).toLowerCase();
if (seenBaseEmails.has(baseEmail)) return false;
seenBaseEmails.add(baseEmail);
return true;
});
}
…while emailToGuestMap is built with new Map(...), where a later entry overwrites an earlier one:
const emailToGuestMap = new Map(
guests.map((guest) => [extractBaseEmail(guest.email).toLowerCase(), guest])
);
...
return uniqueGuestEmails
.map((email) => emailToGuestMap.get(extractBaseEmail(email).toLowerCase()))
So the surviving email comes from the first duplicate, but the guest object that gets looked up is the last one. When they differ, the wrong person is added.
Actual results
Adding two guests whose base emails collide:
guests = [
{ email: "alice+work@x.com", name: "Alice Work" },
{ email: "alice+home@x.com", name: "Alice Home" },
];
deduplicated -> ["alice+work@x.com"] // first wins
emailToGuestMap -> { "alice@x.com" => { email: "alice+home@x.com", name: "Alice Home" } } // last wins
result -> [{ email: "alice+home@x.com", name: "Alice Home" }]
The guest that survives deduplication is alice+work@x.com, but the object returned — and therefore the name, email, timeZone and language written to the booking by addGuestsHandler → updateBookingAttendees — belongs to alice+home@x.com.
packages/features/bookings/services/BookingAttendeesService.ts:103 calls the same function.
The mismatch also affects the plain-duplicate case: sending the same base email twice with different display names silently keeps the second name while reporting the first address.
Expected results
The guest object that is returned is the one whose email survived deduplication. One consistent tie-break — first occurrence wins, matching deduplicateGuestEmails — used in both places.
Reproduction
const extractBaseEmail = (e) => { const [l, d] = e.split("@"); return `${l.split("+")[0]}@${d}`; };
const guests = [
{ email: "alice+work@x.com", name: "Alice Work" },
{ email: "alice+home@x.com", name: "Alice Home" },
];
const map = new Map(guests.map((g) => [extractBaseEmail(g.email).toLowerCase(), g]));
map.get("alice@x.com"); // { email: 'alice+home@x.com', name: 'Alice Home' } <- second one
Related
While reading this: extractBaseEmail() in packages/lib/extract-base-email.ts returns the string "noatsign@undefined" for an input with no @, because email.split("@") yields a single element and domain is undefined. That value then flows into the blacklist comparison and the user lookup. It's a separate, smaller issue — happy to file it on its own if you'd prefer.
I have a fix ready and will open a PR referencing this issue.
- Dominant language
- TypeScript
- Stars
- 48.6k
- Forks
- 15.2k
- Avg merge
- 2d 11h
- Merged PRs (30d)
- 12
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from calcom/cal.diy
-
🐛 bug
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
🐛 bug
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
-
🐛 bug
Difficulty 1/5 Under an hour Newbie friendliness 88/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Similar issues
-
calcite-components needs triage refactor
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Esri/calcite-design-system#15203 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 91/100
-
community first-timers-only good first issue hacktoberfest help wanted low hanging fruit up-for-grabs
Difficulty 1/5 Under an hour Newbie friendliness 95/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Automattic/studio#4908 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 90/100