Message envelope model
The message envelope is the channel-agnostic shape a message carries through Orbit in both directions: the declaration a send submits to the router, and the record the messages API, webhook delivery, and the SDKs hand back. Where Pricing and rate resolution embeds the envelope inside the pricing narrative, this page defines it on its own: the canonical fields, the per-channel overrides, and thechannel_chain / costAware
declaration you meet on the omnichannel send surfaces.
Why one envelope across channels
Each channel speaks its own carrier protocol: GSM segments and UCS-2 on SMS, MIME parts on email, submitted templates on WhatsApp, rich cards on RCS, media replies on Instagram. Your code should not need a parser per channel to answer the basic questions: who sent what to whom, when, on which channel, and did it arrive. The envelope is the contract that makes those answers identical. Every message normalizes to the same top-level fields, and channel particulars ride in declared slots — the per-channel content variant on send, themetadata object on read-back — rather than in ad-hoc per-channel
shapes. One webhook consumer, one inbox, and one warehouse export then
serve every channel, and a chain that falls through from RCS to SMS
produces records that differ in channel, not in shape.
The canonical envelope
These are the top-level fields. One rule keeps the model honest: only fields every channel shares are top-level; anything channel-specific is metadata-resident (see The metadata object).
The per-channel overrides to that canonical shape are exactly the ones
the carrier matrix below pins.
Channel-specific carriers
The envelope does not change per channel; what changes is which carrier carries the content on send, and which keys surface on read-back.
A send declares the universal text plus the per-channel variant it
needs; the most-specific-wins rule below picks the variant per hop.
- SMS
- Email
- WhatsApp
Direction semantics and multi-channel threading
direction names the originator. Inbound messages arrive with
direction: inbound and land on received; outbound messages walk the
status ladder below.
Two ids do the threading, on different axes:
conversation_idpairs inbound with outbound into a thread — including across channels. A recipient who replies on WhatsApp to an SMS send continues the same conversation; the envelope’schannellabels the hop, the conversation holds the relationship. See Inbound message routing.message_group_idbinds one logical send’s attempts. When a cascade escalates a failed send to another channel, the group id ties the hops together (see Cascade failover policy and groups). The conversation answers “with whom”; the group answers “which send attempt”.
Statuses and the per-channel state machine
An outbound message starts atqueued (or scheduled, with a future
send time), moves through sending and sent, and ends in a
channel-shaped terminal state. An inbound message lands directly on
received.
The shared ladder is queued, scheduled, sending, sent,
delivered, read, failed, rejected, expired; each channel shapes
the terminal set:
- SMS and MMS have no read receipt. The success terminal is
delivered, orsubmitted_no_receiptwhen the route accepted the message but never returned a receipt at all (see Submitted vs delivered: the three successes). - WhatsApp, Instagram, Messenger, and RCS extend
deliveredwithreadwhere the carrier surfaces read receipts. - Email adds
bouncedandundeliveredfor recipient-side rejections.
status: it is preserved as
metadata.provider_status beside the normalized state. Message status
map pins the provider-to-status
translation; Message status DAG pins the
allowed transitions.
The metadata object
metadata is free-form by design and namespaced by convention. The
platform stamps utility keys into it, you may add your own keys for
correlation, and nothing inside it participates in routing or pricing.
The utility keys you will meet:
story_mention— flags an inbound Instagram reply to one of your stories.template_type— the category of the WhatsApp template that carried the send.provider_status— the carrier’s raw delivery receipt, kept beside the normalizedstatus.
metadata as additive and optional: parse it defensively, because
new keys can appear without a version bump. The top-level field set
above is the stable contract.
One envelope through SDKs and webhooks
The same normalized envelope is what the messages list endpoint returns, what each webhook delivery posts, and what the SDK message objects wrap. Write one parser against the canonical fields and it serves every channel: when a channel joins the enum, your consumer keeps working, because the new channel’s extras arrive inmetadata rather
than as new top-level fields. The webhook events
reference and the payload
cookbook carry worked payloads.
The send declaration: channel_chain and the content bag
On the send side, one envelope declaration carries everything the router needs for a single send attempt:- One recipient —
to, the destination the envelope delivers. - One content bag —
content, a universaltextbody plus optional per-channel rich variants (a WhatsApp template, an RCS rich body, MMS media, and so on). - An ordered
channel_chain— your fallback preference for this send, in the order you declared it.
- Most-specific-wins on content. A channel-specific variant beats
the universal
textbody on its channel’s hop; absent a variant, the universal body renders. A channel that can render neither is skipped silently and the walk continues rather than failing the send. - Transient failure falls through. A provider-level transient failure (a retryable error, a 5xx, a timeout) advances the walk to the next declared channel. The channel you lose on is recorded in the attempt audit; the chain only ends when every declared channel is exhausted.
Cost-aware re-sequencing
DeclaringcostAware: true on the envelope re-ranks your chain
cheapest-first before the walk starts. The preference set is yours;
the rate card only re-sequences it — channels are never added, removed,
or trimmed, only re-ordered.
Per channel, the resolver reads the per-send cost override you supplied
with the envelope when one exists, and the platform rate card
otherwise. A channel with no published price is never assumed cheapest;
equal-price channels keep your declared order, so the re-sequenced
chain is always a stable re-ordering of what you declared.
Re-sequencing happens once, at declaration time: after sorting, the
walk follows the same first-provider-and-renderable-variant rule as an
unsorted chain.
How the envelope differs from cascade and notify fallback
Orbit has three fallback planes, and all of them are escalation-by-failure — a brand-new send dispatched after the prior one failed:- The org-level cross-channel fallback chain advances on reachability and capability failures.
- The cascade policy (org default or
per-send
cascadefield) escalates on a terminal delivery receipt and binds the hops under onemessage_group_id. - The notify waterfall chases one recipient through bindings with per-hop and cumulative cost bounds.
The per-hop price-resolution contract
Every hop on the walk funnels through the same resolver pipeline a direct send on that channel uses — the precedence from Pricing and rate resolution runs identically per hop: per-operator override verbatim, then your organization’s markup, then the platform-default markup, and Contact sales when no published rate exists. One consequence: a fall-through hop is priced on the channel it lands on, so a chain declaredRCS → SMS attributes an
SMS hop as an SMS charge, never an RCS one.
The quantity multiplier is channel-shaped and fixed:
- SMS and MMS price per segment — the per-segment rate multiplied by the segment count of the rendered body.
- Every other channel prices one unit per message.
costAware
performed is fully traceable per hop.
What an envelope is NOT
- Not a cascade group. A cascade stamps one
message_group_idon the primary and every fallback hop so the run reads back as one logical message. An envelope is a single declaration on one attempt — it groups to nothing. - Not a notify waterfall. A waterfall hoists the chain per recipient
in one notify call and screens with
max_price/max_total_pricebounds. An envelope declares the chain on the send itself; the cost bounds do not apply to it, andcostAwaremerely re-orders rather than bounds. - Not the org-fallback chain. The organization-level
settings.cross_channel_fallbackconfiguration arms every send uniformly. An envelope carries no organization-level configuration — the chain is declared per send and applies to that send only.
Common misunderstandings
- Reaching for channel fields at the top level.
story_mention,template_type, andprovider_statusare metadata-resident by design; the top level is reserved for fields every channel shares. A parser that expects them top-level will simply find nothing. - Reading
body: nullas “nothing was sent”. A media-only inbound carriesattachmentsand no text, and a template send carries its structure in the template reference. Readbody,attachments, and the channel carrier together. - Inlining media into the envelope.
attachmentsholds references to hosted media; upload the media first, then reference it. The rule is the same on every channel. - Comparing statuses across channels without the terminal set.
deliveredis terminal on SMS; on WhatsApp the same message can still advance toread. Compare against the channel’s ladder, not a global one. - Confusing the envelope with the escalation planes. Cascades and waterfalls are separate sends reacting to failure (see What an envelope is NOT); the envelope is one attempt’s shape.
Related
- Pricing and rate resolution — the
resolver pipeline every hop shares, and the rate card
costAwaresorts against. - Cross-channel fallback — the org-level escalation-by-failure chain the envelope is distinct from.
- Cascade failover policy and groups
— the
message_group_id-bound escalations an envelope does not create. - Notify cascade cost model — the per-recipient waterfall with cost bounds, again distinct from the envelope’s re-ordering.
- SMS, WhatsApp, Instagram, RCS, and Email — the per-channel carrier fields and send payloads this page consolidates.
- Message status DAG and Message status map — the allowed transitions and the provider-to-status translation.
- SMS segments and encoding —
why the SMS body segments, and what
segmentsandencodingrecord.