Modified booking sends updated .ics with the same UID and incremented SEQUENCE
Why it matters
RFC 5545 defines SEQUENCE as the mechanism calendar clients use to determine whether an incoming invite supersedes an existing one. When a modified booking is sent without an incremented SEQUENCE, clients that have already processed a SEQUENCE:0 invite ignore or flag the incoming update as stale — the customer's calendar retains the old time and location. The result is a no-show caused not by the customer but by the booking system's failure to follow the standard. Without a sequence column in the database, the increment cannot be persisted across calls, meaning the SEQUENCE will reset to 0 on each modification and every update will be treated as stale.
Severity rationale
High because an absent or non-incrementing SEQUENCE causes calendar clients to silently discard reschedule notifications, producing no-shows caused by the system rather than the customer.
Remediation
Add a sequence integer column to the bookings table, increment it on every modification, and pass the stored value to the ICS generator in src/lib/bookings.ts:
async function modifyBooking(bookingId: string, changes: Partial<Booking>) {
const booking = await db.bookings.findUnique({ where: { id: bookingId } })
const updated = await db.bookings.update({
where: { id: bookingId },
data: { ...changes, sequence: (booking.sequence ?? 0) + 1 },
})
const icsData = await generateBookingICS(updated, booking.host)
await emailQueue.add('send-modification-email', {
bookingId,
customerEmail: booking.customer_email,
icsAttachment: icsData,
})
}
The generateBookingICS function in src/lib/calendar.ts must read booking.sequence and set it as the SEQUENCE field — not hardcode 0.
Detection
-
ID:
ics-modified-sequence -
Severity:
high -
What to look for: Find the booking modification code path. When a booking's date, time, or location changes, check whether: (1) A new .ics is generated and sent — search the modification handler for calendar generation calls. (2) The UID remains the same as the original (verify the same stable UID derivation is used). (3) A SEQUENCE field is incremented — look for a
sequencecolumn in the booking model/table and check it is incremented on modification. The SEQUENCE value in the .ics must match the stored value. (4) The updated .ics is attached to or linked from a modification email sent to the customer. -
Pass criteria: Enumerate all 4 conditions. ALL 4 must be met (minimum 4 of 4): (1) .ics is regenerated on modification — quote the function call. (2) UID is the same stable value — confirm the same derivation. (3) SEQUENCE is stored in the database and incremented by at least 1 per modification — quote the column and increment code. A missing SEQUENCE column does NOT count as pass. (4) Updated .ics is sent to the customer via email. Report the count: "4 of 4 conditions met" even on pass.
-
Fail criteria: No .ics sent on modification. UID changes (customer sees a new event instead of an update). SEQUENCE not tracked or not incremented. Updated .ics generated but not sent to customer.
-
Skip (N/A) when: Calendar invites not implemented (same criteria as ics-complete-uid skip), OR booking modifications are not supported (no modification handler found).
-
Detail on fail: For each of the 4 conditions, state "MET" or "NOT MET" with evidence. Example:
"(1) .ics regenerated: MET — generateBookingICS() called in modifyBooking(). (2) UID stable: MET — same booking.id@domain. (3) SEQUENCE incremented: NOT MET — no sequence column in bookings table, SEQUENCE always 0. (4) .ics sent: MET — attached to modification email.". -
Remediation: Track SEQUENCE in the database and increment on every modification:
// src/lib/bookings.ts async function modifyBooking(bookingId: string, changes: Partial<Booking>) { const booking = await db.bookings.findUnique({ where: { id: bookingId } }) // Increment SEQUENCE const updated = await db.bookings.update({ where: { id: bookingId }, data: { ...changes, sequence: (booking.sequence ?? 0) + 1 } }) // Generate updated .ics with same UID, new SEQUENCE const icsData = await generateBookingICS(updated, booking.host) // Send to customer await emailQueue.add('send-modification-email', { bookingId, customerEmail: booking.customer_email, icsAttachment: icsData, }) } -
Cross-reference: Booking Flow & Lifecycle audit checks modification state transitions. Booking Calendar Availability audit checks time slot recalculation. Database Audit checks schema migration for the sequence column.
External references
- external · RFC5545-SEQUENCE — iCalendar RFC 5545 — SEQUENCE property for itinerary updates
Taxons
History
- 2026-04-18·v1.0.0·Initial import from booking-notifications-reminders·automated