Skip to main content

Fallback on terminal DLR

When a delivery receipt (DLR) lands with a terminal failure — failed, undelivered, or rejected — Orbit can dispatch a new send on the next channel you named, without your integration calling the API again. You opt in per message by declaring fallback metadata on the send; when the terminal DLR commits, the fallback hook re-sends the message on the next channel in your list. This page explains the model: the two trigger planes, the three opt-in shapes, why a webhook replay can’t double-send, how hops chain, what each hop costs, and how this layer composes with the org-level chain described in Cross-channel fallback. The channel-specific send envelopes (per-channel endpoints, template rules) live in the Messaging API reference; this page covers the escalation model those endpoints carry.

The two trigger planes

Orbit has two distinct ways a send can move to another channel, separated by when the failure is known:
  • Synchronous capability failure (reachability). The failure is known before any delivery receipt exists — for example, an RCS provider raises a not-supported error mid-send because the recipient’s device has no RCS capability. This plane is driven by the org-level cross-channel fallback chain and is already covered in Cross-channel fallback.
  • Post-DLR escalation. The send was accepted and submitted; the carrier then returned a terminal DLR — failed, undelivered, or rejected. Only then does this page’s mechanism fire: after the status update commits, a hook reads the original message’s metadata and, if you declared a fallback path, dispatches a brand-new send on the next channel. The failure signal arrives asynchronously, which is why this plane is opt-in per request rather than org-configured.
The two planes never fight over the same send: the org chain is the reachability surface (can the channel carry this recipient at all?), the DLR hook is the auto-escalation surface (the channel tried and the handset said no). The Delivery lifecycle page covers DLR timing and what makes a status terminal.

Opting in: metadata on the send

Post-DLR fallback is per-request and opt-in. Nothing escalates unless the message carries fallback intent in its metadata. Three shapes work:
  1. Explicit: auto_fallback: true together with fallback_channels: ["sms", "whatsapp"] — the flag plus a list of channel names to escalate through, in order.
  2. Implicit: fallback_channels: ["sms"] on its own. The list itself is the opt-in — if you stamp a fallback chain, you asked for fallback behavior.
  3. Opt back out: auto_fallback: false suppresses escalation even when a fallback_channels list is present (useful when your send pipeline stamps a chain by default and a particular send must not escalate).
One optional knob: fallback_window_seconds caps how fresh the original send must be for the terminal DLR to trigger escalation (default 24 hours). A terminal DLR that arrives later than the window is honoured as a status update but never escalates — re-sending hours or days later would surprise the recipient. This contrasts with the org-level chain: the org chain is configured once per organization and fires on reachability failures with no send-side involvement; post-DLR fallback rides on the metadata of individual sends and only applies where you stamp it. Fallback on terminal DLR applies to the rich-messaging family — RCS, WhatsApp, Viber, Telegram, Messenger, Instagram, LINE, and Apple Messages. Email, voice, fax, and MMS never auto-escalate from a terminal DLR: their billing and consent semantics differ too much for an automatic hop (an undelivered email turning into an SMS would be a TCPA surface, not a convenience).

The idempotency lock

DLR webhooks can replay. Carriers re-send receipts, and statuses can arrive out of order — an undelivered followed by a failed on the same message id is common. If every replay triggered a new send, you would bill (and spam) the recipient multiple times. The guard is fallback_attempted_at, stamped atomically on the original message row the first time a fallback fires. The stamp is written with a conditional update that only succeeds while the field is still empty, so two concurrent DLR callbacks race and exactly one wins. Every later replay — any later terminal DLR against the same message — sees the stamp and stops. The net guarantee: at most one fallback dispatch per message, regardless of how many receipts the carrier sends. The lock engages even when the fallback send itself fails, so a lost claim is never silently retried.

Chaining: each hop carries the rest of the chain

When a fallback fires, the new message is a first-class send with the same recipient and body, plus two metadata fields that make the chain work:
  • The remaining tail of the chain. The new message carries the still-unconsumed portion of fallback_channels. If you sent with fallback_channels: ["viber", "sms"] and the primary channel fails, the Viber hop is dispatched with fallback_channels: ["sms"] — a second terminal DLR on the Viber send escalates to SMS, and so on until the chain is exhausted. (The list may also be re-ordered on each hop by learned per-segment delivery outcomes; the tail semantics are unchanged.)
  • fallback_of: <original_message_id>. Every hop stamps the id of the message it fell back from, so billing reports and the dashboard group primary + fallback pairs — you can reconcile “the SMS send that followed the failed RCS send” instead of seeing two unconnected messages.
The chain terminates when the tail is empty, so a fallback_channels list with a single entry escalates once and is done.

Billing: each hop is a new send on its own channel

Every hop is priced and charged on the channel it lands on — the same rule the org-level chain states as “the fallback hop bills on its own channel” in Cross-channel fallback. A fallback from RCS to SMS incurs an SMS charge; a fallback to WhatsApp incurs a WhatsApp charge. There is no separate fallback fee class, and there is no double-charge: the original send is billed exactly once, and the fallback send pays only for its own hop. Any refund on the original send’s failed delivery runs through the same terminal-DLR refund path it always does — the fallback layer neither refunds nor re-bills the original.

How this composes with the org-level chain

The two fallback layers coexist and never both fire on the same send, because they answer different questions: If both could plausibly apply to a send, the org chain resolves first — a capability failure short-circuits before any DLR exists, so the DLR hook’s trigger never arises on that attempt. The DLR hook’s surface is the set of failures that only the carrier can report back asynchronously. The campaign-level channels[] ladder described in Cross-channel fallback is a third, campaign-scoped shape and is unrelated to this metadata convention.