SMS sent within 60 seconds of booking confirmation if SMS is enabled
Why it matters
An SMS confirmation sent 10 minutes after booking is effectively useless — customers check their phone immediately after completing a transaction to verify it went through. A cron job polling every 5 minutes introduces an average 2.5-minute delay and a maximum 5-minute delay, which is long enough for customers to attempt a duplicate booking or contact support. CWE-391 and ISO 25010 reliability.fault-tolerance flag the missing retry path: a single synchronous SMS send with no retry means a carrier timeout silently voids the only channel some customers monitor. Event-driven dispatch removes the timing gap and the retry gap in one change.
Severity rationale
High because a polling-based or retry-less SMS confirmation produces delayed or silently dropped messages, undermining customer trust at the highest-anxiety moment in the booking flow.
Remediation
Queue the SMS in the same confirmBooking() function as the status write, after checking opt-out status. Use the same job queue and retry settings as confirmation email:
// src/lib/bookings.ts
async function confirmBooking(bookingId: string) {
const booking = await db.bookings.update(bookingId, { status: 'confirmed' })
if (booking.customer_phone) {
const isOptedOut = await checkOptOut(booking.customer_phone)
if (!isOptedOut) {
await smsQueue.add(
'send-confirmation-sms',
{ bookingId, phoneNumber: booking.customer_phone },
{ attempts: 5, backoff: { type: 'exponential', delay: 2000 } }
)
}
}
}
Remove any cron or polling job that handles SMS confirmation — the queue is the only dispatch path.
Detection
-
ID:
sms-sent-60s -
Severity:
high -
What to look for: Find the SMS send trigger — usually when a booking reaches "confirmed" status. Trace the code path from status change to SMS dispatch. Verify: (1) SMS is queued automatically in the same function as booking confirmation (not a separate polling job). (2) The SMS queue job has retry logic with at least 3 attempts. (3) The opt-out check happens before queuing. Count the retry attempts configured.
-
Pass criteria: ALL of the following: (1) SMS is queued automatically in the same code path as booking confirmation — quote the function name and the queue addition call. (2) Queue job has at least 3 retry attempts with backoff — report the count of attempts even on pass. (3) Opt-out check precedes the queue addition. A separate polling job that sends SMS on a timer does NOT count as pass.
-
Fail criteria: SMS not sent automatically. SMS sent from a separate polling or cron job (not event-driven). No retry logic. Fewer than 3 retry attempts. Opt-out not checked before queuing.
-
Skip (N/A) when: SMS is not offered (same criteria as sms-optout-persisted skip).
-
Detail on fail: Quote the function where booking confirmation happens and describe the SMS flow. Example:
"confirmBooking() at src/lib/bookings.ts:25 changes status but has no SMS dispatch. SMS is sent by a separate cron job checkPendingSMS() at src/jobs/sms-cron.ts:8 that runs every 5 minutes — not real-time.". -
Remediation: Queue SMS in the same function as booking confirmation:
// src/lib/bookings.ts — add SMS queue in same function async function confirmBooking(bookingId: string) { const booking = await db.bookings.update(bookingId, { status: 'confirmed' }) // Queue SMS after opt-out check if (booking.customer_phone) { const isOptedOut = await checkOptOut(booking.customer_phone) if (!isOptedOut) { await smsQueue.add('send-confirmation-sms', { bookingId, phoneNumber: booking.customer_phone, }, { attempts: 5, backoff: { type: 'exponential', delay: 2000 } }) } } } -
Cross-reference: Booking Flow & Lifecycle audit checks the confirmation status transition. Email/SMS Compliance audit checks SMS content requirements. Performance Audit checks queue latency patterns.
External references
- cwe · CWE-391 — Unchecked Error Condition
- iso-25010:2011 · reliability.fault-tolerance
Taxons
History
- 2026-04-18·v1.0.0·Initial import from booking-notifications-reminders·automated