Scheduled reminders cancelled when booking is modified or cancelled before fire time
Why it matters
A reminder that fires for a cancelled booking is the worst possible notification: the customer shows up for an appointment that no longer exists. A reminder that fires with the original time after a reschedule is functionally identical — the customer is told to show up at the wrong time. CWE-362 (race condition) applies to the in-flight case: a reminder job queued at 8:00 AM for a booking cancelled at 8:01 AM will still fire if the worker does not check current booking status before sending. ISO 25010 reliability.fault-tolerance requires that booking lifecycle changes propagate atomically to all derived state, including scheduled notifications.
Severity rationale
High because sending reminders for cancelled or rescheduled bookings causes customers to show up at wrong times or for non-existent appointments, generating immediate operational and trust failures.
Remediation
On cancellation, null out reminder_due_at and reset reminder_sent. On modification, recalculate reminder_due_at from the new start time. Inside the reminder worker, always check booking.status === 'confirmed' as the final guard:
// src/lib/bookings.ts — cancellation
async function cancelBooking(bookingId: string) {
await db.bookings.update({
where: { id: bookingId },
data: { status: 'cancelled', reminder_due_at: null, reminder_sent: false },
})
}
// src/lib/bookings.ts — modification
async function modifyBooking(bookingId: string, changes: Partial<Booking>) {
const booking = await db.bookings.findUnique({ where: { id: bookingId } })
const service = await db.services.findUnique({ where: { id: booking.serviceId } })
const newStart = changes.start_time ?? booking.start_time
const intervalMs = service.reminder_interval_ms ?? 86400000
await db.bookings.update({
where: { id: bookingId },
data: { ...changes, reminder_due_at: new Date(newStart.getTime() - intervalMs), reminder_sent: false },
})
}
// src/jobs/reminder-worker.ts — in-flight guard
const booking = await db.bookings.findUnique({ where: { id: job.data.bookingId } })
if (booking.status !== 'confirmed') return
DetectionPro
The detection method for this pattern is part of AuditBuffet Pro.
The full method (what to look for, pass and fail conditions, and how results get reported) ships inside the Booking Notifications & Reminders Audit. The pattern definition, severity rationale, and remediation remain openly licensed under CC-BY-4.0.
External references
- cwe · CWE-362 — Race Condition — in-flight reminder fires after cancellation
- iso-25010:2011 · reliability.fault-tolerance
Taxons
History
- 2026-04-18·v1.0.0·Initial import from booking-notifications-reminders·automated