> ## Documentation Index
> Fetch the complete documentation index at: https://docs.robase.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Transactional SMS patterns

> Delivery confirmations, status updates, appointment reminders — done right.

Transactional SMS is every message triggered by a user action: booking confirmations, shipping updates, ride ETAs, appointment reminders, payment receipts. Unlike marketing SMS, users expect them — and get mad when they don't arrive.

## The mindset

<CardGroup cols={2}>
  <Card title="Timely or worthless" icon="clock">
    A "your ride arrives in 3 min" SMS 10 minutes late is worse than no message at all. Send fast or don't send.
  </Card>

  <Card title="One segment, every time" icon="ruler">
    Keep bodies ≤ 160 GSM-7 chars. Two segments is 2x the cost for zero added value.
  </Card>

  <Card title="Include the action" icon="arrow-right">
    "Ride arriving" ❌ · "Chidi arrives at Gate 2 in 3 min in an Hiace (ABC 123 XY)" ✅
  </Card>

  <Card title="Never rely on SMS alone" icon="bridge">
    Pair with push notification + in-app state. SMS is a fallback + confirmation, not the primary UI.
  </Card>
</CardGroup>

## Patterns

### Booking confirmation

```typescript theme={null}
await pm.sms.send({
  to: trip.passenger_phone,
  from: 'SHUTTLERS',
  template: 'booking-confirmed',
  variables: {
    ride_type: trip.ride_type,       // e.g. 'Airport'
    time: trip.pickup_time_short,    // '10:00am'
    driver: trip.driver.first_name,  // 'Chidi'
    plate: trip.vehicle.plate,       // 'ABC 123 XY'
    ref: trip.reference,             // 'T-92413'
  },
}, `booking-${trip.id}-confirmed`);
```

Template body:

```
Your {{.ride_type}} ride at {{.time}} is confirmed. Driver: {{.driver}}. Plate: {{.plate}}. Ref: {{.ref}}.
```

### Status change

Fire on every state transition: `assigned → en-route → arriving → completed`. Dedupe with an idempotency key so a webhook retry doesn't double-send:

```typescript theme={null}
await pm.sms.send({
  to: trip.passenger_phone,
  from: 'SHUTTLERS',
  body: `Your ride is ${newStatus}. Driver: ${trip.driver.first_name}. Track: https://shut.ng/t/${trip.id}`,
}, `trip-${trip.id}-status-${newStatus}`);
```

### Appointment reminder

Scheduled sends are perfect for reminders:

```typescript theme={null}
const reminder = new Date(appointment.time);
reminder.setHours(reminder.getHours() - 2);

await pm.sms.send({
  to: patient.phone,
  from: 'HEALTHCO',
  template: 'appointment-reminder',
  variables: { doctor: appointment.doctor_name, time: appointment.time_short, clinic: appointment.clinic_address },
  send_at: reminder.toISOString(),
}, `appt-${appointment.id}-t2h`);
```

### Delivery receipt

```typescript theme={null}
await pm.sms.send({
  to: order.phone,
  from: 'JUMIA',
  body: `Your order ${order.ref} has been delivered. Rate: https://jumia.ng/rate/${order.id}`,
}, `order-${order.id}-delivered`);
```

## Things to avoid

<AccordionGroup>
  <Accordion title="Don't include URLs with query strings in SMS">
    Short URLs get flagged as spam. Use a branded redirect domain (`shut.ng/t/…`) and keep the path tight.
  </Accordion>

  <Accordion title="Don't send the same user multiple messages in 60 seconds">
    Batch updates ("your status changed 3 times in the last minute") into one message if the transitions happen fast. Users read the most recent anyway.
  </Accordion>

  <Accordion title="Don't skip the sender ID">
    "From: 1234" looks like spam. `From: SHUTTLERS` is recognizable.
  </Accordion>

  <Accordion title="Don't rely on webhooks for UI updates">
    Webhooks have a lag. Drive the UI from your own order state + push notifications. Treat `sms.delivered` as confirmation that **the SMS** got through, not that **the user acted**.
  </Accordion>
</AccordionGroup>

## Deliverability hygiene

For transactional SMS at scale (>100k/month), do these quarterly:

1. **Review bounces.** Run `GET /v1/sms?status=failed&limit=200` — look for clusters by carrier, by time window, by number format.
2. **Clean stale numbers.** If you've failed to deliver to a number 5 times in a row, take it off your list.
3. **Monitor carrier breakdown.** `GET /v1/sms/analytics/carriers` — if one MNO suddenly drops delivery rate, escalate to us — we can re-route via Termii or file a trouble ticket.
