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.
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.
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.
ID: booking-notifications-reminders.sms.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.