Skip to main content

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.
The lab never routes a send; LCR never picks a channel; the route preview never scores your copy’s filter risk. Each lens stays in its own lane, and the preview is deliberately the cheapest of the three — pass a recipient and a message shape, get the channel answer back.

What it evaluates

The preview runs the same contact-aware channel scorer the smart-send path dispatches from, so its answer is what a POST /messages/smart-send (or channel: "auto" unified send) with the same inputs would pick:
  1. 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.
  2. 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.
  3. Message-type and goal weighting. OTP biases SMS (and WhatsApp, and against email). Marketing leans toward rich channels (WhatsApp, RCS, email). goal shifts the balance — cheapest boosts low-cost channels, fastest boosts SMS and push, highest_engagement weights channel richness. cost_optimize: true further biases cheaper routes.
  4. 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.
  5. 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_policy is attached, the preview also resolves the same cascade arm the send would stamp.
Two special cases the estimator names explicitly in its 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 of sms, 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.
  • fallback and fallbackChainfallback is the next channel to try; fallbackChain is the full ordered list after the winner, in priority order. The first entry of fallbackChain always equals fallback. 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: your engagement_rates value 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 reason and the chain reflect their absence, and a 422 names 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.)
The model-level boundary: the preview never resolves a sender (sender resolution is the dedicated stage), never routes carriers (LCR), and never writes a message row — those are the other stages’ lanes. Per-message cascade arms resolve only when 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 messages insert, 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.
That posture is what lets the dashboard-composer wizard poll the endpoint on every keystroke, and what makes per-recipient planning passes safe to batch.

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 fallbackChain on hard failures — the smart-route-preview guide walks that loop end to end.
When you need legality (policy scanner) or content-risk (Deliverability Lab), the preview runs alongside, not instead.

Worked samples

Two concrete calls, both returning only the estimator’s five answers.

WhatsApp → SMS fallback chain on an all-capable recipient

The router picked WhatsApp on engagement (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:
WhatsApp drops out of the candidate set because the flag declared it unreachable; the winner is the next eligible channel that scores highest, and 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 fallbackChain intersects.
  • Deliverability Lab — the content-risk lens the preview explicitly does not own.
  • Cross-channel fallback — the org-level chain the preview’s fallbackChain composes 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.