Message send request lifecycle
Every sibling page in the outbound-status cluster owns one slice of a send — Delivery lifecycle owns the meanings, Message-status transition rules owns the merge contract, Message-envelope model owns the wire shape, and From queued to sent: the four-owner map owns the page index. None of them answers the question you actually have after your first send: what happens to myPOST /api/v1/messages call, step by step? This page composes the whole walk — API guard to billing gate to termination decision to provider handoff to receipt — and links each stage to its owning page instead of redefining it. Read this once to learn the walk; read the owning pages when a stage needs depth.
1. Request ingress — the guard stack
Your outbound POST passes through the ordered per-request guards that every Orbit API call traverses: retired-host gate, IP denylist, load-shed, rate-limit buckets, policy scan, auth, and the idempotency guard that replays a stored response for a re-sentIdempotency-Key header. The full ordered table — which guard fires first, the status code and error envelope each returns when it fires — is on API request guard pipeline; the replay contract the idempotency guard enforces is on Idempotency and safe retries. Failing a guard here means nothing was enqueued and no row exists, so a 422/429/401 answer is your signal the send never entered the pipeline.
2. Enqueue — the row lands in the send queue
On acceptance the row is INSERTed withstatus: "queued" and the response returns 202 with the message id; from that moment the row is visible GET /api/v1/messages/:id. The durable enqueue — the queue the API produces to and the worker consumes from instead of answering inline — follows the producer/consumer split on Async processing model, and the queue’s drain is owned by the worker fleet you read about on Scheduler fleet model. Do not poll the row to advance it; the pipeline advances itself, and polling a queued row tells you nothing the webhook event stream would not.
3. Sender resolution — the six-source chain picks the sender
Before the dispatching worker can hand the payload to a provider it must resolve which sender the message goes out from. For the unifiedPOST /api/v1/messages (channel: "sms") or the native POST /api/v1/messages/sms shape, resolution walks the six source types in order — an inline sender, a sender pool, a messaging service, a routable number, a BYO carrier binding, and the tenant default. The authoritative walk — every pool strategy with its fail-safes, per-country country_sender_pools overrides, health-tier swaps, and the fallback chain — is on Sender resolution; the inbound side of the same construct lives on Sender and routing. When you supplied no sender hint, resolution picks the tenant-level default and the row records which source resolved it.
4. Pricing and the billing gate — a send that cannot pay never reaches a provider
Before any provider handoff, the send is priced and then walked through the ordered billing gate. The quote comes first: the destination’s per-unit rate resolves through overrides and markups as Pricing and rate resolution describes, and every gate below checks the wallet against that priced row, never a $0 placeholder. Then the chain runs in its fixed order — the same four stages the outbound billing gate chain narrates in full:- Pre-flight balance check — an exhausted wallet rejects with
INSUFFICIENT_BALANCE(402) before the send reaches the provider. - Pause/block flags gate —
SENDING_PAUSED(soft) orSENDING_BLOCKED(hard), both 402, with the free-channel exemption for a soft pause. - Per-channel fund path — voice escrows a hold of up to two minutes at dial; SMS debits per billable segment at send time.
- Atomic check-and-decrement — the wallet never overdraws, and the idempotency key collapses retries into one ledger row.
message.created event, nothing to poll — so treat it as terminal, top up, and resend. The soft-fail shape is different: an inbound auto-reply that runs dry records the reply as failed with an insufficient-balance cause while the inbound pipeline keeps going. The zero-balance per-surface table on the billing gate chain is the full matrix; this page only places the gate in the walk.
5. The termination decision — the send may leave the channel it arrived on
Sender resolution picks who sends; the termination decision picks which channel carries the send. Between resolution and provider handoff sits the unified termination decision: every production send origin funnels through one decision point that runs before every provider attempt, and your termination rules — tenant-owned configuration — can absorb the message and deliver it on another channel, for example an SMS submit redirected onto a WhatsApp template. The rules vocabulary ofdeliver vs absorb hops, the shadow-to-enforce lifecycle, and the canonical failure modes live on Unified termination routing; this stage places the decision in the walk.
The walk is evaluate → claim → deliver. In shadow mode the rule is evaluated and the would-be channel is recorded on the intent row while the send proceeds on its original path; flip a rule to enforce and the terminator claims the intent and performs exactly one egress on the chosen channel. An absorbed send still bills exactly once: the preflight debits the charge key charge:msg:<messageId> before the routing decision runs, the claim collapses the second send, and a second deduct on the same key resolves to a no-op — the one-billed-egress guarantee. And because rules name a channel, never a provider or trunk, a rule can redirect the hop but can never take the send outside the platform’s wholesale egress. Where termination decides the first hop, Least-cost routing ranks the carriers one hop downstream.
6. Provider handoff — the softswitch or your BYO carrier
Outbound exits the platform on one of two planes, both of them part of the tenant’s own configuration. The default is the terminating softswitch; when you have brought your own carrier, the BYO-carrier binding from resolution hands the payload to your carrier connection instead. Which one fired is recorded on the row as theprovider column plus the destination mccmnc, and the BYO carrier lifecycle page owns how the BYO binding is established. Whichever plane carries the send, the per-message audit of the handoff is the Route-trace timeline — GET /api/v1/messages/:id/trace assembles the timed events (accepted → routed → sent → terminal + webhook fan-out) from rows the platform already persists, so “which provider did this send take” is a one-call answer, not a forum question.
Who carries the send: the provider chain inside a channel
Stage 6 names the two planes a send can exit on; this section names the ordering that picks the one connection inside the channel that actually carries it. Every send resolves to a single provider connection, ordered by four inputs:- Your own connection for the channel, first. If your organization has connected its own asset — a WhatsApp WABA, a Viber Business account, a Telegram bot — that connection is placed at the head of the chain and tried before anything the platform registered. This is tenant-owned configuration; nothing about it is inferred.
- Sender-based preference. The resolved sender can dictate the connector: a number purchased on Telnyx exits via the Telnyx connection, and an alphanumeric sender ID rides the SMPP bind that registered it.
- Destination prefix routing. A longest-prefix match on the destination number against the BYO carrier connectors you configured, per BYO carrier lifecycle.
- Registration order. Whatever remains, in the order the platform registered it.
422 CHANNEL_NOT_CONFIGURED or a provider error, and is never re-sent from a different identity. That is the general form of the rule each channel page states for its own surface: WhatsApp has no fallback that routes a tenant send onto the platform WABA, and Telegram dispatches under your own bot or the demo bot, never under an identity that is not yours. SMS is the contrast case that makes the rule legible: your number is yours whichever connector carries it, so connector failover under an unchanged sender is safe and is allowed — and precisely for that reason a send pinned to a provider-owned number locks to that connector rather than letting a different one answer with its own default sender. Viber is the one deliberate middle case: your Business account is tried first, and a send that it rejects — the recipient is not a Viber user, for example — degrades to the platform’s one-way Viber route instead of failing, a configured degradation where both tiers remain Viber egress rather than a substitution of identity.
The test-mode exception. Platform demo assets exist for evaluation only. The shared Telegram demo bot serves test-mode tenants and never carries production traffic: a production send resolves to the tenant’s own connected bot through input 1, and a production tenant without one does not silently borrow the demo identity. The per-channel ladder of test assets is on the channel test-account model.
Where this stage ends and upstream selection begins. The chain above picks a provider inside one channel; it neither picks the channel nor ranks the egress route the provider hands the message to downstream:
Troubleshooting: three different “wrong carrier” shapes.
- My send failed instead of failing over. The identity-bound rule did its job. The channel is identity-bound, your own connection was the only provider attempted, and it refused. Fix the connection — register the number, reconnect the bot — and resend; there is no platform-side fallback to wait on.
- My send left on an unexpected carrier. The provider was yours; what moved was the upstream route. Least-cost routing or route quality picked a different egress than you assumed; read the trace against Least-cost routing.
- My sender changed. Not this stage. Sender identity is fixed before the provider chain runs, on Sender resolution; a substituted sender means another source won the resolution walk, or an unlocked connector answered with its default.
7. Status writeback — pending → queued → sending → sent
As the drain dispatches, the row advances through the forward arc —pending → queued → sending → sent — and each transition fires the matching message.* webhook event to every subscribed endpoint, so your subscribers track the send without polling. The per-status meaning of each transition and who writes it lives on Delivery lifecycle; the legal-transition graph, precedence weight, and the recoverable/floor rules your own merge logic must copy live on Message-status transition rules; the message.created vs message.sent plane split lives on Message-status map. Your own receiver treats any status the row can reach through those pages, not just the ones this arc lists.
8. DLR arming — closing the grace window when the carrier says nothing
A wire-levelsent is a submission acknowledgement, not a delivery claim; the terminal success hop is a carrier-confirmed delivered. When the provider accepted the payload but the carrier never reports back, the no-DLR sweep promotes sent to submitted_no_receipt after a per-channel grace window — 30 minutes on SMPP-backed channels (SMS, MMS, voice, fax, RCS), and the per-channel terminal otherwise — and tags that event message.failed with state_class: "intermediate", is_terminal: false, so a later genuine delivered, read, or failure still overwrites it. The grace-window rules are on Delivery lifecycle; the wire-vs-receipt plane split the sentinel belongs to is on DLR model: two planes; the read-upsert correction rules that accept the late receipt are on Message-status transition rules. If you freeze your mirror on the first submitted_no_receipt event, you close the message on a sentinel that was never a terminal answer.
Worked example — one SMS, five events, and one 402
This is the exact sequence one well-formed SMS send produces. Your receiver must see the same statuses in the same order for the same message id, whatever the provider does on the wire:delivered supersedes submitted_no_receipt — exactly the precedence rule on the transition DAG. Replay this pair against your merge logic before you take your integration to production.
And the refused case — the same POST against an exhausted wallet:
failed — same wallet state, two refusal shapes, and the gate stage names which one you see.
See also
- Delivery lifecycle — what each status means and who writes it
- Message-status transition rules — the merge contract your receiver must implement
- Message-status map — the four owners in one index
- Route-trace timeline — per-message diagnostic timeline
- Message-envelope model — wire-shape semantics per channel
- Sender resolution — the full outbound chain
- Pricing and rate resolution — the per-unit quote the billing gate checks
- The outbound billing gate chain — the ordered four-stage gate and the 402 integration rules
- Unified termination routing — absorb vs deliver, shadow vs enforce, one billed egress
- Async processing model — queue producer/consumer split
- API request guard pipeline — the ordered ingress guards