diff --git a/components/email/__tests__/calendar-invitation-banner.test.tsx b/components/email/__tests__/calendar-invitation-banner.test.tsx
index 37d9be2d5..efef720a8 100644
--- a/components/email/__tests__/calendar-invitation-banner.test.tsx
+++ b/components/email/__tests__/calendar-invitation-banner.test.tsx
@@ -23,6 +23,7 @@ const mocks = vi.hoisted(() => {
const authState = {
client: clientMock,
primaryIdentity: { email: 'user@example.com' },
+ identities: [{ email: 'user@example.com' }] as Array<{ email: string }>,
getClientForAccount: vi.fn((id: string) => (id === 'login-b' ? sourceClientMock : undefined)),
};
@@ -188,6 +189,7 @@ describe('CalendarInvitationBanner', () => {
mocks.sourceClientMock.fetchBlob.mockReset();
mocks.sourceClientMock.fetchBlob.mockRejectedValue(new Error('no blob'));
mocks.authState.getClientForAccount.mockClear();
+ mocks.authState.identities = [{ email: 'user@example.com' }];
mocks.emailState.viewingAccountId = null;
mocks.emailState.selectedMailbox = 'inbox';
mocks.emailState.mailboxes = [];
@@ -546,6 +548,41 @@ describe('CalendarInvitationBanner', () => {
expect(await screen.findByText('Response sent')).toBeInTheDocument();
});
+ it('finds the user under an alias identity the invite was sent to (#1283)', async () => {
+ mocks.authState.identities = [{ email: 'user@example.com' }, { email: 'Alias@example.com' }];
+ const participants = {
+ aliasAttendee: {
+ '@type': 'Participant',
+ email: 'alias@example.com',
+ name: 'User',
+ roles: { attendee: true },
+ participationStatus: 'needs-action',
+ },
+ };
+ mocks.calendarState.events = [
+ { id: 'event-8', uid: 'uid-8', start: '2026-03-30T09:00:00Z', participants },
+ ];
+ mocks.clientMock.parseCalendarEvents.mockResolvedValue([
+ { uid: 'uid-8', title: 'Alias Event', start: '2026-03-30T09:00:00Z', participants },
+ ]);
+ mocks.calendarState.rsvpEvent.mockResolvedValue(true);
+
+ render();
+
+ fireEvent.click(await screen.findByText('Accept'));
+
+ await waitFor(() => {
+ expect(mocks.calendarState.rsvpEvent).toHaveBeenCalledWith(
+ mocks.clientMock,
+ 'event-8',
+ 'aliasAttendee',
+ 'accepted',
+ null,
+ );
+ });
+ expect(await screen.findByText('Response sent')).toBeInTheDocument();
+ });
+
it('says nothing was sent when a plugin stops the answer', async () => {
mocks.calendarState.events = [
{
diff --git a/components/email/calendar-invitation-banner.tsx b/components/email/calendar-invitation-banner.tsx
index cbc1a18b2..53919235a 100644
--- a/components/email/calendar-invitation-banner.tsx
+++ b/components/email/calendar-invitation-banner.tsx
@@ -34,7 +34,7 @@ import {
getInvitationTrustAssessment,
formatEventSummary,
formatInvitationSchedule,
- findParticipantByEmail,
+ findParticipantByEmails,
extractMethodFromRawIcs,
type EventSummary,
type InvitationMethod,
@@ -390,7 +390,14 @@ export function CalendarInvitationBanner({ email }: CalendarInvitationBannerProp
const viewedMailbox = viewMailboxes.find((m) => m.id === selectedMailboxId);
const sharedOwnerAccountId = viewedMailbox?.isShared ? viewedMailbox.accountId : undefined;
const blobAccountId = email.sourceAccountId ?? sharedOwnerAccountId ?? parseClient?.getAccountId();
- const currentUserEmail = useAuthStore((s) => s.primaryIdentity?.email);
+ // Every address the user sends as, primary first: an invite may be
+ // addressed to an alias identity (#1283).
+ const identityEmails = useAuthStore(useShallow((s) => {
+ const emails = [s.primaryIdentity?.email, ...s.identities.map((identity) => identity.email)]
+ .filter((address): address is string => Boolean(address))
+ .map((address) => address.toLowerCase());
+ return Array.from(new Set(emails));
+ }));
const calendarInvitationParsingEnabled = useSettingsStore((s) => s.calendarInvitationParsingEnabled);
const timeFormat = useSettingsStore((s) => s.timeFormat);
const { calendars, supportsCalendar, importEvents, rsvpEvent, updateEvent, events: storeEvents, setSelectedDate } = useCalendarStore(useShallow((state) => ({
@@ -524,12 +531,12 @@ export function CalendarInvitationBanner({ email }: CalendarInvitationBannerProp
? storeEvents.find((e) => e.uid === parsedEvent.uid)
: null;
- const currentUserParticipant = existingEvent && currentUserEmail
- ? findParticipantByEmail(existingEvent, currentUserEmail)
+ const currentUserParticipant = existingEvent
+ ? findParticipantByEmails(existingEvent, identityEmails)
: null;
- const fallbackParsedParticipant = !currentUserParticipant && parsedEvent && currentUserEmail
- ? findParticipantByEmail(parsedEvent, currentUserEmail)
+ const fallbackParsedParticipant = !currentUserParticipant && parsedEvent
+ ? findParticipantByEmails(parsedEvent, identityEmails)
: null;
const participantForUser = currentUserParticipant || fallbackParsedParticipant;
@@ -545,9 +552,8 @@ export function CalendarInvitationBanner({ email }: CalendarInvitationBannerProp
|| myParticipant?.participant.participationStatus
|| null;
const userIsOrganizer = Boolean(isOnlyOrganizer);
- const userIsSender = Boolean(
- currentUserEmail && email.from?.[0]?.email?.toLowerCase() === currentUserEmail.toLowerCase()
- );
+ const senderAddress = email.from?.[0]?.email?.toLowerCase();
+ const userIsSender = Boolean(senderAddress && identityEmails.includes(senderAddress));
const bannerTitle = getBannerTitle(t, method);
const bannerInfo = getBannerInfo(t, method, userIsOrganizer, supportsCalendar);
const trustAssessment = parsedEvent && !userIsSender ? getInvitationTrustAssessment(parsedEvent, email, method) : null;
@@ -602,8 +608,8 @@ export function CalendarInvitationBanner({ email }: CalendarInvitationBannerProp
const eventForRsvp = existingEvent
? await resolveExistingEventForRsvp()
: null;
- const existingEventParticipant = eventForRsvp && currentUserEmail
- ? findParticipantByEmail(eventForRsvp, currentUserEmail)
+ const existingEventParticipant = eventForRsvp
+ ? findParticipantByEmails(eventForRsvp, identityEmails)
: null;
const canFallbackToParsedParticipant = Boolean(
existingEvent
@@ -646,7 +652,7 @@ export function CalendarInvitationBanner({ email }: CalendarInvitationBannerProp
(e) => e.uid === parsedEvent.uid
);
const participant = myParticipant
- || (newEvent && currentUserEmail ? findParticipantByEmail(newEvent, currentUserEmail) : null);
+ || (newEvent ? findParticipantByEmails(newEvent, identityEmails) : null);
if (newEvent && participant) {
if (!(await rsvpEvent(client, newEvent.id, participant.id, status, replyToForRsvp))) return;
setRsvpStatus(status);
diff --git a/lib/calendar-invitation.ts b/lib/calendar-invitation.ts
index a467068b4..2ff7a22ea 100644
--- a/lib/calendar-invitation.ts
+++ b/lib/calendar-invitation.ts
@@ -702,6 +702,22 @@ export function formatInvitationSchedule(
return range.end ? `${start} – ${formatPoint(range.end, true)}` : start;
}
+/**
+ * The participant matching the first of `emails` found in the event. The
+ * user's identities, primary first: an invite may be addressed to an alias
+ * identity rather than the primary address (#1283).
+ */
+export function findParticipantByEmails(
+ event: Partial,
+ emails: readonly string[]
+): { id: string; participant: CalendarParticipant } | null {
+ for (const email of emails) {
+ const found = findParticipantByEmail(event, email);
+ if (found) return found;
+ }
+ return null;
+}
+
export function findParticipantByEmail(
event: Partial,
email: string