fix(emails): send METHOD:CANCEL in ICS for cancelled bookings (#29708)

Fixes an issue where cancellation emails were sending ICS files with
METHOD:REQUEST instead of METHOD:CANCEL. As per RFC 5546, calendar
clients only process cancellations correctly when METHOD:CANCEL is used,
otherwise cancelled events remain in attendee calendars with active
reminders.

- Use METHOD:CANCEL for cancelled booking ICS files
- Keep METHOD:REQUEST for non-cancelled booking invitations
- Preserve STATUS:CANCELLED and sequence handling
- Add tests to verify the generated ICS method for cancellations
This commit is contained in:
Gaurav Kumar
2026-07-05 10:23:53 +00:00
committed by GitHub
parent 032962f577
commit 188aaa1857
3 changed files with 6 additions and 3 deletions
+1 -1
View File
@@ -36,6 +36,6 @@ export default function generateIcsFile({
status,
t,
}),
method: "REQUEST",
method: status === "CANCELLED" ? "CANCEL" : "REQUEST",
};
}
@@ -46,6 +46,9 @@ const testIcsStringContains = ({
}
expect(icsString).toEqual(expect.stringContaining(`DTEND:${DTEND}`));
expect(icsString).toEqual(expect.stringContaining(`STATUS:${status}`));
expect(icsString).toEqual(
expect.stringContaining(`METHOD:${status === "CANCELLED" ? "CANCEL" : "REQUEST"}`)
);
// Getting an error expected icsString to deeply equal stringMatching
// for (const attendee of event.attendees) {
// expect(icsString).toEqual(
+2 -2
View File
@@ -61,7 +61,7 @@ const generateIcsString = ({
const location = getVideoCallUrlFromCalEvent(event) || event.location;
// Taking care of recurrence rule
let recurrenceRule: string | undefined = undefined;
let recurrenceRule: string | undefined;
const icsRole: ParticipationRole = "REQ-PARTICIPANT";
if (event.recurringEvent?.count) {
// ics appends "RRULE:" already, so removing it from RRule generated string
@@ -107,7 +107,7 @@ const generateIcsString = ({
: []),
],
location: location ?? undefined,
method: "REQUEST",
method: status === "CANCELLED" ? "CANCEL" : "REQUEST",
status,
...(event.hideCalendarEventDetails ? { classification: "PRIVATE" } : {}),
busyStatus: "BUSY",