Skip to main content
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

Timely or worthless

A “your ride arrives in 3 min” SMS 10 minutes late is worse than no message at all. Send fast or don’t send.

One segment, every time

Keep bodies ≤ 160 GSM-7 chars. Two segments is 2x the cost for zero added value.

Include the action

“Ride arriving” ❌ · “Chidi arrives at Gate 2 in 3 min in an Hiace (ABC 123 XY)” ✅

Never rely on SMS alone

Pair with push notification + in-app state. SMS is a fallback + confirmation, not the primary UI.

Patterns

Booking confirmation

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

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

Short URLs get flagged as spam. Use a branded redirect domain (shut.ng/t/…) and keep the path tight.
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.
“From: 1234” looks like spam. From: SHUTTLERS is recognizable.
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.

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.