Cascade (waterfall) fallback chains in the notify composer
POST /api/v1/notify sends one body to a mixed list of recipients across SMS, WhatsApp, email, RCS, push, Viber, Telegram, and Messenger in a single API call. The Messages → Multi-channel notify page in the dashboard is a visual composer over that endpoint, so you can build the recipient list and pick how it delivers.
This guide covers the two delivery strategies the composer offers, how cascade groups your rows into a per-recipient fallback chain, how to bound the escalation window, and how to read what came back. Everything here is a tenant-owned control — the chain order and the window are choices you make per message.
Fan-out vs cascade (waterfall)
The composer asks you to pick one of two strategies. They differ in when each channel fires:- Fan-out (default). Every recipient row sends immediately, in parallel. One delivery attempt per row; there is no retry-across-channels. Use it for general announcements where one channel — maybe two for redundancy — is enough and you want the message out everywhere at once.
- Cascade (waterfall). Only the first channel in each recipient’s chain fires now. The remaining rows ride along as an ordered fallback chain, and the delivery engine advances to the next hop only when the active hop reports back undelivered inside the freshness window. Use it for time-critical messages (a one-time passcode, an appointment reminder, an outage alert) where the cheapest reliable channel should win and the expensive ones are a safety net, not a scatter-blast.
Build a cascade in the composer
- Open Messages → Multi-channel notify and click Multi-channel notify to open the composer.
- Set the Delivery strategy to Cascade (waterfall). The composer relabels the recipient list to “Recipients & chain”.
- Enter one recipient per row (channel + address). To give a recipient a fallback chain, enter the same address on two or more rows with different channels — rows that share an address merge into one chain. The leftmost column numbers each row so the escalation order is visible.
- (Optional) Set the Escalation window (seconds). This bounds how late an undelivered delivery receipt (DLR) may still trigger the next hop: minimum 30 seconds, maximum 24 hours. Leave it blank to use the 24-hour default. A value outside that range is dropped when the payload is built, so a mistyped window never blocks your send — and the API would clamp it to the floor/ceiling anyway.
- Read the per-recipient chain preview under the list. Well-formed chains (2+ hops) render as
EMAIL → SMS → WHATSAPP. A recipient entered once (or a duplicated address with only one channel) shows an amber “Add another channel for failover” callout, and the composer blocks the send with an error naming the offending address while you fix it. - Finish writing the body, optionally a sender, and click Start cascade.
The single-hop guard
Cascade without a second hop degrades silently to fan-out semantics: the first hop fires and there is nothing left to escalate to. The composer therefore requires at least two channels per recipient in waterfall mode and blocks the send otherwise. (The API’s own refine only requires ≥2 bindings total for waterfall mode — the per-recipient check here is stronger and lives in the composer.) When you build payloads in code rather than the composer, apply the same rule yourself.The API the composer calls
Both quirks are sent onPOST /api/v1/notify — the strategy as mode, the window (waterfall only) as fallback_window_seconds:
mode defaults to fanout when omitted; fallback_window_seconds accepts 30–86400 and defaults to 86400 (24h). Every hop still runs through the messaging router — sender resolution, opt-out, and compliance checks apply per hop, matching each destination’s reachability rules. The full field specification (per-binding overrides, shared template_name / template_params, response envelope) is in the /notify endpoint reference.
Worked example: an email → SMS → WhatsApp OTP reminder
An OTP flows exactly the same as the reminder above. Pick the channels by cost and reach — email → SMS → WhatsApp in this case:
Each hop is independently delivered against the per-channel validators, opt-out lists, and quiet-hours rules your tenant owns. The first hop that delivers ends the chain — later hops never fire.
Reading the result
The screen tells you whether each row queued and whether the strategy badge says Fan-out or Cascade:- 200 OK — every binding succeeded (fan-out), or at least one binding queued (waterfall). The row list shows a per-row chip: Queued (fan-out) — it fired now; Queued (waterfall) — this hop is the one that fires first, and any delayed deliveries for still-pending hops have not happened yet.
- 207 Multi-Status (fan-out only) — some rows queued and some failed. The row chips show Failed with a human-readable reason (opt-out, quota, bad address); hover a row to see the machine-readable code. Branch per row — never blanket-retry the batch.
- 422 — in fan-out every binding failed; in waterfall every binding was rejected synchronously. Nothing was queued; fix the body/addresses and try again.
The dashboard badge “cascade queued — only the first hop fired” means exactly that. A queued row next to an overwrite from an earlier run is fine; treat delivery (confirmed DLR) as the terminal signal, and use fan-out when you cannot tolerate an ambiguous queued-only result.
When to use which — a decision shortcut
See also
- /notify endpoint reference — full request/response specification for both strategies.
- Verify fallback chains — ordered channel fallbacks for OTP verification, as a reusable profile rather than a per-message chain.