Skip to content

fix(emails): increment iCalendar SEQUENCE on cancellation per RFC 5546 (#29706) - #29926

Open
Samarth1306w wants to merge 5 commits into
calcom:mainfrom
Samarth1306w:fix/ics-cancellation-increment-sequence-29706
Open

fix(emails): increment iCalendar SEQUENCE on cancellation per RFC 5546 (#29706)#29926
Samarth1306w wants to merge 5 commits into
calcom:mainfrom
Samarth1306w:fix/ics-cancellation-increment-sequence-29706

Conversation

@Samarth1306w

Copy link
Copy Markdown

Fixes #29706

Summary

Per RFC 5546 §3.2.5, when a cancellation iCalendar packet () is issued, the number MUST be incremented above the original event's sequence.

Without a bumped , recipient calendar clients (such as Outlook, Apple Calendar, and Thunderbird) treat the cancellation payload as an obsolete or duplicate revision and fail to remove the event from attendee calendars.

Updated to increment on cancellation ().

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Welcome to Cal.diy, @Samarth1306w! Thanks for opening this pull request.

A few things to keep in mind:

  • This is Cal.diy, not Cal.com. Cal.diy is a community-driven, fully open-source fork of Cal.com licensed under MIT. Your changes here will be part of Cal.diy — they will not be deployed to the Cal.com production app.
  • Please review our Contributing Guidelines if you haven't already.
  • Make sure your PR title follows the Conventional Commits format.

A maintainer will review your PR soon. Thanks for contributing!

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The change updates cancellation ICS generation to increment event sequences and verifies the CANCEL method. CalDAV availability filtering now excludes events where the authenticated attendee has NEEDS-ACTION or DECLINED status, while retaining organized events. Email matching supports case-insensitive attendee and organizer parameters and mailto: values. Timezone formatting removes leading zeroes from single-digit hours while preserving minute values, with expanded test coverage.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Calendar availability filtering and timezone formatting changes are unrelated to the linked cancellation email issue. Remove the unrelated CalendarService availability and timezone changes, or link them to separate issues.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the cancellation SEQUENCE fix and references the relevant RFC.
Description check ✅ Passed The description explains the cancellation SEQUENCE change and its effect on calendar clients.
Linked Issues check ✅ Passed The changes implement the linked issue requirements for METHOD:CANCEL behavior and incremented SEQUENCE values.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Warning

⚠️ This pull request shows signs of AI-generated slop (description_diff_mismatch). It has been flagged by CodeRabbit slop detection and should be reviewed carefully.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/lib/CalendarService.test.ts (1)

700-716: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add an explicit DECLINED fixture.

The fixture omits PARTSTAT, so it tests only the default NEEDS-ACTION path. Add an attendee with PARTSTAT=DECLINED and assert that getAvailability also returns no busy interval.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/lib/CalendarService.test.ts` around lines 700 - 716, Add an explicit
CalDAV event fixture in the test beginning “skips CalDAV events…” with an
attendee for user@example.com having PARTSTAT=DECLINED, then verify
getAvailability returns no busy interval for that event as well as the existing
NEEDS-ACTION case.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/emails/lib/generateIcsString.test.ts`:
- Around line 96-97: Update the ICS test assertions around the existing
METHOD:CANCEL and SEQUENCE checks to validate exact lines rather than
substrings, preventing partial sequence matches. Add a test case that sets
event.iCalSequence to 2 and verify the generated ICS contains the exact line
SEQUENCE:3, while preserving coverage of the default sequence behavior.

In `@packages/lib/CalendarService.test.ts`:
- Around line 702-725: Update the test setup around getAvailability to provide
the user email through the TestCalendarService constructor instead of assigning
the private BaseCalendarService.credential member. Replace the remaining as any
casts by using typed CalDAV object and calendar fixture values, while preserving
the unanswered-invite scenario and existing availability assertions.

---

Nitpick comments:
In `@packages/lib/CalendarService.test.ts`:
- Around line 700-716: Add an explicit CalDAV event fixture in the test
beginning “skips CalDAV events…” with an attendee for user@example.com having
PARTSTAT=DECLINED, then verify getAvailability returns no busy interval for that
event as well as the existing NEEDS-ACTION case.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6347034e-3aea-4ca0-881e-a3a8d2b47251

📥 Commits

Reviewing files that changed from the base of the PR and between b2c28a2 and c92eaad.

📒 Files selected for processing (6)
  • packages/emails/lib/generateIcsString.test.ts
  • packages/emails/lib/generateIcsString.ts
  • packages/lib/CalendarService.test.ts
  • packages/lib/CalendarService.ts
  • packages/lib/timezone.test.ts
  • packages/lib/timezone.ts

Comment on lines +96 to +97
expect(assertedIcsString).toContain("METHOD:CANCEL");
expect(assertedIcsString).toContain("SEQUENCE:1");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the ICS assertions exact and cover a non-zero sequence.

Lines 96-97 use substring matching. A value such as SEQUENCE:10 would satisfy the SEQUENCE:1 assertion. Replace both assertions with exact-line checks. Add a case with event.iCalSequence = 2 and assert SEQUENCE:3; the current test only covers the default sequence path.

Proposed assertion fix
-      expect(assertedIcsString).toContain("METHOD:CANCEL");
-      expect(assertedIcsString).toContain("SEQUENCE:1");
+      expect(assertedIcsString).toMatch(/(?:^|\r?\n)METHOD:CANCEL(?:\r?\n|$)/);
+      expect(assertedIcsString).toMatch(/(?:^|\r?\n)SEQUENCE:1(?:\r?\n|$)/);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/emails/lib/generateIcsString.test.ts` around lines 96 - 97, Update
the ICS test assertions around the existing METHOD:CANCEL and SEQUENCE checks to
validate exact lines rather than substrings, preventing partial sequence
matches. Add a test case that sets event.iCalSequence to 2 and verify the
generated ICS contains the exact line SEQUENCE:3, while preserving coverage of
the default sequence behavior.

Comment on lines +702 to +725
service.credential.user = { email: "user@example.com" } as any;

const objects = [
{
data: `BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:unanswered-1
SUMMARY:Unanswered Invite
DTSTART:20260729T100000Z
DTEND:20260729T110000Z
ORGANIZER;EMAIL=organizer@example.com:mailto:organizer@example.com
ATTENDEE;EMAIL=user@example.com;X-APPLE-NEEDS-REPLY=TRUE:mailto:user@example.com
END:VEVENT
END:VCALENDAR`,
},
];

vi.mocked(fetchCalendarObjects).mockResolvedValue(objects as any);

const busy = await service.getAvailability({
dateFrom: "2026-07-29T00:00:00Z",
dateTo: "2026-07-29T23:59:59Z",
selectedCalendars: [{ externalId: "cal1", integration: "caldav_calendar" }] as any,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the private member, the test access, and TypeScript test/typecheck configuration.
rg -n -C 3 'private credential|service\.credential|as any' \
  packages/lib/CalendarService.ts \
  packages/lib/CalendarService.test.ts

fd -HI -t f '^(tsconfig.*\.json|package\.json)$' . -x sh -c '
  echo "=== $1 ==="
  rg -n "include|exclude|typecheck|test|vitest" "$1" || true
' sh {}

Repository: calcom/cal.diy

Length of output: 50371


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== target file relevant lines ==="
sed -n '680,735p' packages/lib/CalendarService.test.ts
echo "=== BaseCalendarService credential area ==="
rg -n -C 5 'credential|constructor|class BaseCalendarService|class TestCalendarService' packages/lib/CalendarService.ts
echo "=== top-level tsconfig/package test/typecheck references (repo root only) ==="
fd -HI -t f '^(tsconfig.*\.json|package\.json)$' . \
  -x sh -c 'echo "=== $1 ==="; rg -n "include|exclude|typecheck|test|vitest" "$1" || true' sh {} \
  | sed -n '1,240p'
echo "=== deterministic TypeScript private-member behavior probe ==="
node - <<'JS'
console.log("skip runtime ts-node probe if unavailable")
JS

Repository: calcom/cal.diy

Length of output: 23217


Configure the test credential through TestCalendarService.

BaseCalendarService.credential is private, so service.credential = ... as any is blocked by private-member access rules and the test will fail type-checking. Pass the user email through the test service constructor and use typed CalDAV object and calendar fixtures instead of the remaining as any casts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/lib/CalendarService.test.ts` around lines 702 - 725, Update the test
setup around getAvailability to provide the user email through the
TestCalendarService constructor instead of assigning the private
BaseCalendarService.credential member. Replace the remaining as any casts by
using typed CalDAV object and calendar fixture values, while preserving the
unanswered-invite scenario and existing availability assertions.

Source: Coding guidelines

@CLAassistant

CLAassistant commented Aug 6, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cancellation emails send .ics with METHOD:REQUEST instead of METHOD:CANCEL → event stays in attendee calendars

2 participants