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

# Multi-channel Notify: one message to a mixed recipient list

> Send a single message to a heterogeneous recipient list across SMS, WhatsApp, RCS, email, push, Viber, Telegram, and Messenger from Messages → Multi-channel notify — fan-out everything at once, or cascade per recipient. Twilio Notify parity.

# Multi-channel Notify

The **Messages → Multi-channel notify** page is the one-shot, mixed-channel send surface: it composes a recipient list where every entry picks its own channel, writes one shared message, and dispatches everything in a single `POST /api/v1/notify` call. It mirrors Twilio Notify — a heterogeneous binding list of `(channel, address)` pairs plus one shared payload, with delivery resolved per binding.

Eight channels are offered in the composer: RCS, WhatsApp, SMS, email, push, Viber, Telegram, and Messenger. The address is whatever the channel routes on — E.164 phone for SMS/WhatsApp/RCS/Viber, an email address, a device token for push, or a handle for Telegram/Messenger. Every recipient runs through the same delivery pipeline as a single send on that channel: opt-out lists, quota, sender resolution, and compliance checks all apply.

## When to use Notify vs a flow vs a campaign

| Use Multi-channel notify when                                    | Use a flow when                                                | Use a campaign when                                                   |
| ---------------------------------------------------------------- | -------------------------------------------------------------- | --------------------------------------------------------------------- |
| You have a finite list, each recipient on their own best channel | The send is one step in a branching, event-driven conversation | You want the same channel for everyone, with campaign-level lifecycle |
| One shared body, sent now — nothing to nurture                   | Logic waits for replies, splits on data, or runs over days     | You need audiences, drafts, dry-run, holdouts, and ROI attribution    |
| Channel choice is per recipient, not per audience                | Timing and branches are the point, not the channel mix         | The recipient set is a live segment evaluated at send time            |

Notify is the "one message, mixed list, right now" tool. A campaign is the governed blast with analytics; a flow is the conversation. If every recipient is on SMS, the simpler surface is [Batch SMS](/guides/messages-batch-sms). If the same recipient should be chased across channels only when the first one fails, that is also Notify — in cascade mode, covered below.

## Role requirements

The page is gated to **owner**, **admin**, and **developer** roles. Members and viewers do not see the page and cannot open the composer — the same role guard that protects other dispatch surfaces. If the page is missing from your dashboard, ask an owner or admin for one of those roles.

## Composing a send

1. Open **Messages → Multi-channel notify** and click **Multi-channel notify** to open the composer.
2. Pick a **Delivery strategy** — **Fan-out** (the default, Twilio Notify parity) or **Cascade (failover)**. The next section explains the difference.
3. Write the **Message** body. It is applied to every recipient. An optional **Sender** overrides the default sender resolution; leave it blank and the router resolves a per-channel sender for you.
4. Build the recipient list: one row per recipient, each with a channel select and an address. Use **Add recipient** for more rows; the composer accepts up to 10,000 bindings (the same cap Twilio Notify enforces per call).
5. Send and read the per-row result — every recipient gets its own queued/failed chip.

"Twilio Notify parity" means the call semantics match Twilio's Notify API: a single request carries a mixed binding list and one shared payload, each binding is delivered on its own channel, and per-binding failures never abort the rest of the batch. Orbit adds the cascade strategy and cost bounds on top of that baseline.

All page-level checks are advisory: a row with an empty address is dropped before dispatch, and a body left blank blocks the send with a toast. Address-shape validation happens in the delivery pipeline per channel — a bad address comes back as that row's per-binding error, not as a form error.

## Channel selection and the fallback cascade

The delivery strategy picker maps onto the `mode` field of `POST /api/v1/notify`:

* **Fan-out (default).** Every row fires immediately, in parallel — one delivery attempt per row, no retry across channels. Use it for announcements where one channel per recipient is the intent.
* **Cascade (failover).** Rows that share an address merge into one per-recipient fallback chain, in row order (richest → cheapest, e.g. RCS → WhatsApp → SMS). Only the first hop fires; the next hop fires only if the active one comes back undelivered inside the escalation window (30 seconds to 24 hours — blank uses the 24-hour default).

Cascade mode enforces **at least two channels per recipient**; a single-hop recipient gets an amber "Add another channel for failover" callout and blocks the send until fixed. The full mechanism — chain grouping, the escalation window, cost bounds per hop and per cascade — is covered in [Cascade (waterfall) fallback chains in the notify composer](/guides/notify-cascade-failover), and the pricing model behind the caps in [Notify cascade cost model](/concepts/notify-cascade-cost-model).

## Delivery tracking and retries

After dispatch the dialog renders the per-row result:

* **200 / all queued** — every row carries a green **Queued** chip. In cascade mode that means the first hop per recipient is armed; the rest escalate only on undelivered receipts.
* **207 Multi-Status** — some rows failed. Each failed row shows a **Failed** chip with a human-readable reason (opt-out, quota, bad address); hover the row for the machine-readable error code. Branch per row — never re-send the whole batch, or the queued rows go out twice.
* **422 / nothing queued** — no row was accepted. Fix the body or addresses and resend; nothing went out, so a full retry is safe.

The composer result is the synchronous queue verdict only. A queued row is **not** delivered yet — terminal delivery arrives through the usual per-channel delivery events (delivery receipts for SMS/WhatsApp/RCS, provider callbacks for email and push). For a cascade, resolve the hop chain, per-hop cost, and escalation flag by notify id with [Track a cascade](/guides/track-a-cascade); the page hosts that lookup panel below the composer. A retry that makes sense is rebuilding the failed rows as a new send — dispatch itself has no automatic cross-channel retry outside cascade mode.

## Send a notify request from code

Everything the page composes is a single `POST /api/v1/notify`, and the same call works from your own code — the composer is only a visual front on the endpoint. The deep field reference and the cascade mechanics live in [Cascade (waterfall) fallback chains](/guides/notify-cascade-failover); the samples below are the minimal form of each strategy.

Minimal fan-out (the default — `mode` can be omitted):

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/notify/" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "body": "Reminder: your appointment is 10:30 AM tomorrow.",
    "bindings": [
      { "channel": "whatsapp", "address": "+14155552671" },
      { "channel": "sms",      "address": "+14155559876" }
    ]
  }'
```

Minimal cascade — pass `mode: "waterfall"` and repeat the address once per hop, richest to cheapest:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/notify/" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "waterfall",
    "fallback_window_seconds": 300,
    "body": "Reminder: your appointment is 10:30 AM tomorrow.",
    "bindings": [
      { "channel": "rcs", "address": "+14155550001" },
      { "channel": "sms", "address": "+14155550001" }
    ]
  }'
```

Read the envelope by status. **200** means every binding queued; **207 Multi-Status** carries per-row verdicts; **422** means nothing queued:

```json theme={null}
{
  "data": {
    "notify_id": "nfy_d3q8x1",
    "summary": { "total": 2, "succeeded": 1, "failed": 1 },
    "results": [
      { "index": 0, "channel": "whatsapp", "to": "+14155552671", "status": "queued", "message_id": "msg_w41k2x" },
      {
        "index": 1,
        "channel": "sms",
        "to": "+14155559876",
        "status": "failed",
        "error": { "code": "RECIPIENT_OPTED_OUT", "message": "Recipient opted out of SMS sends." }
      }
    ]
  }
}
```

Branch per row on `status`: the queued rows already went out, so rebuild only the failed rows as a new send. On 422 nothing was accepted, so a full retry is safe.

From the SDKs, `/notify` has no typed helper — go through the low-level `orbit.request` escape hatch (same auth, retry, and error model as the typed methods):

```typescript theme={null}
// Node / TypeScript — npm install @devotel-orbit/node
import { Devotel } from '@devotel-orbit/node';

const orbit = new Devotel({ apiKey: process.env.ORBIT_API_KEY });

const res = await orbit.request('POST', '/notify', {
  mode: 'waterfall',
  fallback_window_seconds: 300,
  body: 'Reminder: your appointment is 10:30 AM tomorrow.',
  bindings: [
    { channel: 'rcs', address: '+14155550001' },
    { channel: 'sms', address: '+14155550001' },
  ],
});

console.log('notify id:', res.data.notify_id);
```

In waterfall mode, give each recipient at least two bindings — the composer enforces this per recipient, and payloads you build in code should apply the same guard. Every binding still runs through the delivery pipeline's tenant-owned controls — opt-out lists, quota, sender resolution, and compliance checks apply per hop.

## Worked example — a 3-channel notify send

A clinic reminds tomorrow's patients about their appointments. The patient list is mixed: some patients prefer WhatsApp, one only has email, and one high-priority patient should be cascaded across three channels until one delivers.

1. **Open the composer.** **Messages → Multi-channel notify** → **Multi-channel notify**. Keep the strategy on **Fan-out** first.
2. **Write the body:** "Reminder: your appointment at CareClinic is 10:30 AM tomorrow. Reply to reschedule." Leave **Sender** blank — WhatsApp and email resolve their own senders.
3. **Add three rows:** WhatsApp → `+14155552671`, SMS → `+14155559876`, Email → `patient@example.com`. Click **Send notification**.
4. **Read the result.** Two rows show **Queued**, email shows **Failed** with the reason "Recipient opted out of email sends." The total/queued/failed strip reads 3 / 2 / 1.
5. **Recover the failed row.** Re-open the composer, switch the strategy to **Cascade**, and give the high-priority patient a chain: three rows with the same address `+14155550001` on RCS, WhatsApp, and SMS, plus an **Escalation window** of 300 seconds. The chain preview under the list reads `RCS → WHATSAPP → SMS`; click **Start cascade**.
6. **Track it.** Only the RCS hop fires now. Copy the notify id from the result into the **Track a cascade** panel on the page: it shows the active hop, the armed tail, and the escalation flag once a DLR moves the chain forward.

The mixed fan-out handled the bulk; the cascade handled the one patient where delivery matters more than channel cost — both from the same composer.

## See also

* [Cascade (waterfall) fallback chains in the notify composer](/guides/notify-cascade-failover) — the cascade strategy in depth: chain grouping, the escalation window, and worked API payloads.
* [Notify cascade cost model](/concepts/notify-cascade-cost-model) — per-hop and cumulative price bounds on a cascade.
* [Track a cascade](/guides/track-a-cascade) — resolve hop chain, billed cost, and escalation flag by notify id after a send.
* [Batch SMS](/guides/messages-batch-sms) — the single-channel one-off wizard, when every recipient is on SMS.
* [Message scheduling](/guides/message-scheduling) — queue a one-off send for later.
