Route preview — the pre-send what-if estimator
Before a message goes out, Orbit owns one question you can ask for free: if I dispatched this right now, what would the smart router do?POST /messages/route-preview answers it — the recommended channel, a plain-language reason, the ordered fallback chain, a per-message cost estimate, and a predicted engagement score — and it answers it without creating a message row, touching the wallet, or emitting a webhook. This page covers what the estimator evaluates, what it returns, and the boundary it holds against its neighboring pre-send tools.
Where it sits — before the send, beside the other estimator lenses
Orbit’s pre-send checks split along the axes each one owns:- Least-cost routing ranks upstream carriers for SMS — which trunk a message exits over. That is a route-internal ordering.
- Deliverability Lab scores content risk — will carrier filters treat this copy as spam-shaped.
- Route preview (this page) answers the whole-send question at the channel layer — which channel the smart router picks, what the fallback chain looks like, and what the per-message cost reads.
What it evaluates
The preview runs the same contact-aware channel scorer the smart-send path dispatches from, so its answer is what aPOST /messages/smart-send (or channel: "auto" unified send) with the same inputs would pick:
- Channel eligibility. Phone-capable channels (SMS, MMS, WhatsApp, RCS) enter candidates by phone reachability; email enters when an address is present; the social/OTT identifiers (
instagram_psid,messenger_psid,telegram_id,line_id,viber_id,device_token) resolve from the contact record you already hold. A channel with no reachable identifier scores zero and drops out. - Historical delivery and engagement. The scorer reads your own past sends and replies — a terminal-denominator delivery rate per channel, and the response-share per channel — so a contact who answers on email prefers email.
- Message-type and goal weighting. OTP biases SMS (and WhatsApp, and against email). Marketing leans toward rich channels (WhatsApp, RCS, email).
goalshifts the balance —cheapestboosts low-cost channels,fastestboosts SMS and push,highest_engagementweights channel richness.cost_optimize: truefurther biases cheaper routes. - Cost per candidate. Per-message cost comes from the tenant channel cost table, so the estimate is a per-cohort planning figure, not a guess.
- Fallback chain. The ordered fallback channels behind the winner are the same stack a live dispatch would advance through on a hard failure. When a
cascade_policyis attached, the preview also resolves the same cascade arm the send would stamp.
reason:
- OTP returns a regulatory note rather than an arbitrary pick — the scorer acknowledges SMS as the OTP primary and only falls forward to WhatsApp/voice.
- All-flags-off recipient — no detected capabilities and no email — returns
"No channel capabilities detected — defaulting to SMS", naming the router’s last-resort default rather than inventing a channel.
What it returns
One JSON envelope, five answers, plus the boundaries the estimator never crosses:channel— the router’s pick (one ofsms,whatsapp,email,viber,rcs,push,voice).reason— plain text composed from whichever factor fired (cost-optimized,high engagement (N%),fast delivery for urgent message,rich content for marketing, the OTP regulatory note, or the all-flags-off SMS default). Not a black-box score — the text tells you which of the inputs decided.fallbackandfallbackChain—fallbackis the next channel to try;fallbackChainis the full ordered list after the winner, in priority order. The first entry offallbackChainalways equalsfallback. An empty array means no reach-able next channel for this recipient — treat the winner as terminal.estimatedCostCents— per-message cost in cents on the winner, before any volume tiering. Sub-cent channels come back fractional, so multiply rather than round when you sum a cohort.engagementScore— predicted engagement 0.0–1.0: yourengagement_ratesvalue for the winner when you supplied it, otherwise the platform per-channel default.- Blocked destinations named. Per-channel ineligible channels are not silently skipped — the winner’s
reasonand the chain reflect their absence, and a422names the malformed field with a human-readable message. (This is distinct from country-level destination gating, which sits in the country allowlist — the preview reads channel eligibility, the allowlist gates destinations.)
cascade_policy is attached; otherwise the response stops at the chain.
No-side-effects contract
A preview is a pure read. Every call runs with these invariants, which the endpoint shares with the LCR dry-run quote:- No message row. Nothing persists — no
messagesinsert, no contacts upsert, no conversation entry. - No wallet touch. Costs are estimated, never deducted. The estimator is free to call in a planning loop.
- No provider or webhook emission. No provider call fires, no DLR arrives, no webhook goes out to your endpoint, and the preview never shows up in delivery stats.
- No tenant data read-across. When you omit
engagement_rates, the defaults are global per-channel constants; nothing reads another tenant’s history.
When to wire it
Four call shapes fit the estimator:- Pre-launch campaign checks. Run it over a recipient sample before you schedule, so the assumption “this blast goes on SMS” gets verified against the router’s answer for each recipient.
- CI integration gates. A pipeline that plans a rollout can assert the estimated cost stays under a ceiling, or that the fallback chain degrades the way you expect, before any traffic moves.
- Dashboard composer previews. The Outbound → Route Preview page polls this endpoint — a non-API operator can answer “does enabling cost-optimize swap SMS for WhatsApp on a Brazil recipient?” inline. (Settings → API keys grants
messages:read— the same scope the endpoint accepts.) - Send-pipeline fallback ladders. Pre-fetch capabilities per recipient, run the preview, dispatch on the winner, and advance through
fallbackChainon hard failures — the smart-route-preview guide walks that loop end to end.
Worked samples
Two concrete calls, both returning only the estimator’s five answers.WhatsApp → SMS fallback chain on an all-capable recipient
0.91 ≥ 0.70 threshold), kept RCS and SMS in reach, and priced the winner at 3¢. Wire fallbackChain into your pipeline as the fallback ladder — dispatch the primary, advance on hard failure.
Blocked field named, not a silent skip
The estimator surfaces which input governs the answer. Here, an absent capability flag defaults to “unknown”, so the recipient is not considered reachable on that channel:reason names the governing factor (for example "No channel capabilities detected — defaulting to SMS" when nothing else qualified). A 422 VALIDATION_ERROR on a malformed body likewise names the field with a human-readable message — the estimator never returns a generic failure when a specific input is the blocker.
See also
- Least-cost routing — the sibling estimator that ranks upstream carriers after sender resolution.
- Sender resolution — which sender a message goes out from; the preview is deliberately one lane downstream of that stage.
- The five fallback planes — the decision map over the planes the preview’s
fallbackChainintersects. - Deliverability Lab — the content-risk lens the preview explicitly does not own.
- Cross-channel fallback — the org-level chain the preview’s
fallbackChaincomposes with at dispatch time. - Country allowlist — the tenant-owned destination gate the preview does not replace; the preview reads channel eligibility, the allowlist gates destinations.
- Smart-route-preview guide — the endpoint’s request/response reference, the dashboard-composer walk-through, and the send-pipeline loop.
- Channel optimization model — the scorer behind the preview’s channel pick.
- Fallback on terminal DLR — the per-request fallback plane that fires after acceptance, downstream of where the preview stops.