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

# WhatsApp + SMS fallback

> Use WhatsApp as primary, fall back to SMS when it fails.

In Nigeria and most of Africa, WhatsApp is how people actually communicate — but delivery is less predictable than SMS, and some users have data off. The practical pattern: **WhatsApp primary, SMS as automatic fallback**.

<Note>
  WhatsApp Business API sending isn't part of Robase's core API yet — it's on the Phase 4 roadmap. This guide shows the pattern using your own WhatsApp provider today (Twilio, 360dialog, Infobip), falling back to `pm.sms.send` when WhatsApp fails.
</Note>

## Why fallback, not one or the other

| Channel  | Strengths                                                             | Weaknesses                                               |
| -------- | --------------------------------------------------------------------- | -------------------------------------------------------- |
| WhatsApp | Rich formatting, receipts, delivery fast when online, no segment cost | Requires data on; template approval; higher per-msg cost |
| SMS      | Works offline, no template approval, cheaper                          | Plain text only, 160 chars, no read receipts             |

Send via WhatsApp first, watch for delivery, and fall back to SMS if it doesn't deliver within a window.

## The flow

```
 App event  →  WhatsApp send
                    │
         Webhook: delivered / read?    (wait up to 60 s)
                    │
          Yes                 No / timeout
                                    │
                              SMS send
```

## Reference implementation

```typescript theme={null}
import { Robase } from '@robase/node';
import WhatsApp from 'my-whatsapp-sdk';
import Bull from 'bull';

const pm = new Robase(process.env.ROBASE_API_KEY!);
const wa = new WhatsApp(process.env.WA_TOKEN);
const fallbackQueue = new Bull('wa-fallback');

async function notify(userId: string, body: { short: string; rich: string }) {
  const user = await db.users.get(userId);
  let wa_message_id: string | null = null;

  // 1. Try WhatsApp first.
  if (user.whatsapp_opted_in) {
    try {
      const { id } = await wa.send({ to: user.phone, body: body.rich });
      wa_message_id = id;
    } catch (e) {
      console.warn('wa send failed, fallback immediate', e);
      return smsFallback(user, body.short);
    }
  } else {
    return smsFallback(user, body.short);
  }

  // 2. Schedule the SMS fallback for 60s later — cancel if WhatsApp delivers.
  await fallbackQueue.add(
    { user_id: userId, message: body.short, wa_message_id },
    { delay: 60_000, jobId: `fallback-${wa_message_id}` }
  );
}

async function smsFallback(user: { phone: string }, body: string) {
  await pm.sms.send(
    { to: user.phone, from: 'SHUTTLERS', body },
    `fallback-${user.phone}-${Date.now()}`,
  );
}

// WhatsApp webhook: cancel the fallback when delivered.
app.post('/webhooks/whatsapp', async (req, res) => {
  if (!verifyWhatsApp(req)) return res.status(401).end();
  const event = req.body;
  if (event.type === 'message.delivered') {
    await fallbackQueue.removeJobs(`fallback-${event.message_id}`);
  }
  res.status(200).end();
});

// Fallback worker.
fallbackQueue.process(async (job) => {
  const { user_id, message } = job.data;
  const user = await db.users.get(user_id);
  await pm.sms.send({ to: user.phone, from: 'SHUTTLERS', body: message }, `fb-${job.id}`);
});
```

## Tuning the fallback window

* **OTP**: 30–45s. Users expect the code fast.
* **Ride ETA**: 60s.
* **Receipts / non-urgent**: 5 min. Most users read WhatsApp within 5 minutes; SMS is a safety net, not a duplication.
* **Marketing**: Don't fall back. If WhatsApp fails, the message isn't worth \$1 of SMS.

## Two versions of the body

Keep your WhatsApp body rich (links, bold, emoji) and the SMS body tight (≤ 160 GSM-7):

```typescript theme={null}
await notify(user.id, {
  rich:  `*Your ride is on its way* 🚗\n\nDriver: ${driver.name}\nPlate: ${vehicle.plate}\n\nTrack: https://shut.ng/t/${trip.id}`,
  short: `Your ride is here. Driver: ${driver.name}. Plate: ${vehicle.plate}. Track: shut.ng/t/${trip.id}`,
});
```

## Measuring the fallback rate

High fallback rate (say, >30%) usually means:

1. **Users disabled WhatsApp notifications** — they'll still receive the SMS, but your WhatsApp cost is wasted. Check opt-in quality.
2. **WhatsApp templates not approved** — silent delivery failures. Check your WA dashboard.
3. **Network issues in a specific region** — cluster by user location; route problem regions to SMS-primary.

Track both channels side by side; rolling 7-day fallback rate is the single most informative metric.
