# SMS sent within 60 seconds of booking confirmation if SMS is enabled

- **Pattern:** `ab-000565` (`booking-notifications-reminders.sms.sms-sent-60s`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000565
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000565)

## 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:

```ts
// 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:

  ```ts
  // 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
- iso-25010 reliability.fault-tolerance

Taxons: error-resilience

HTML version: https://auditbuffet.com/patterns/ab-000565
