Reminder jobs idempotent; no duplicate reminders sent after crash recovery
Why it matters
Job queues provide at-least-once delivery guarantees — if a worker crashes after sending the reminder but before acknowledging the job, the queue will re-deliver it on restart. Without an idempotency guard that reads the reminder_sent flag before proceeding, crash recovery produces a duplicate send on every failure. CWE-362 (race condition) applies: two workers processing the same job concurrently (a known BullMQ failure mode under high load) can both pass a post-send flag check and send twice. ISO 25010 reliability.fault-tolerance requires that the system produce correct outputs even under failure and recovery. The fix — checking the flag before sending, not after — trades a low-probability missed send for a guaranteed no-duplicate guarantee.
Severity rationale
Critical because without an idempotency guard, every worker crash during a reminder send produces a guaranteed duplicate notification on queue recovery, which erodes customer trust at scale.
Remediation
In src/jobs/reminder-worker.ts, check reminder_sent before sending and set it to true before making the carrier call, not after. This is the set-before-send pattern — it accepts a low-probability missed send on crash in exchange for a guaranteed no-duplicate guarantee:
const worker = new Worker('reminders', async (job) => {
if (job.name === 'send-reminder') {
const booking = await db.bookings.findUnique({ where: { id: job.data.bookingId } })
if (booking.reminder_sent) return // Already sent — skip
// Set flag BEFORE send — prevents duplicates on crash recovery
await db.bookings.update({ where: { id: booking.id }, data: { reminder_sent: true } })
try {
await sendReminderMessage(booking)
} catch (error) {
await db.deliveryLog.create({ data: { booking_id: booking.id, type: 'reminder', error: error.message } })
throw error // Queue retries; idempotency guard prevents double-send
}
}
}, { connection: redis })
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 — non-atomic flag-set and send
- iso-25010:2011 · reliability.fault-tolerance
Taxons
History
- 2026-04-18·v1.0.0·Initial import from booking-notifications-reminders·automated