Skip to main content

Smart routing: preview a channel before you send

POST /api/v1/messages/route-preview runs the smart router without sending anything. You pass the recipient and the message shape; it returns the channel the router would pick, a plain-language reason, the ordered fallback chain, a per-message cost estimate, and a predicted engagement score. Use it to plan campaigns, validate a channel choice before you pace a send, and sanity-check fallback behavior against real recipients — all without spending a credit or creating a message row. The same scoring logic runs on the live send path (channel: "auto" on POST /messages, and POST /messages/smart-send), so what the preview returns is what dispatch would do with the same inputs.

When to preview vs. send

Preview when the answer changes what you send or how you pace it:
  • Planning a campaign. Before you commit an audience to a channel, run the preview against a sample of recipients. If the recommendation is not the channel you assumed, adjust the audience or the capability flags — not the campaign after launch.
  • Validating channel-cost before pacing. estimatedCostCents is priced through the same channel cost table the router scores with, so a preview loop over a recipient cohort gives you a per-channel cost picture for pacing decisions (throttle, credit caps) before the first dispatch.
  • Fallback sanity checks. Toggle the capability flags (whatsapp_available, rcs_available, …) to confirm the router degrades the way you expect — e.g. that an RCS-first marketing blast lands on WhatsApp or SMS rather than voice when RCS is off.
  • Picking a send shape. If channel: "auto" outperforms your fixed channel on engagement for a cohort, switch the campaign to auto-routing before you schedule it.
Send directly (skip the preview) when the message is one-off and you already know the channel, or when the recipient has exactly one reachable channel anyway.

Endpoint shape

POST /api/v1/messages/route-preview — read-scoped, tenant-keyed rate limit at the standard read budget. Your API key needs at most the messages:read scope (a write key works too). Create or edit keys in the dashboard under Settings → API keys.

Request

All capability flags default to absent (not false) — an absent flag means “unknown,” which excludes the channel from the candidate set.

Response — 200

Nothing is created. There is no message id, no wallet deduction, no webhook, and no record of the preview anywhere but your own logs — call it on every recipient in a planning pass if you like.

Read the response, field by field

  • channel — the router’s pick. One of sms, whatsapp, email, viber, rcs, push, voice. This is the channel a dispatch with the same inputs would use.
  • reason — a plain-text explanation of why it won, composed from whichever factors fired: cost-optimized (cost-optimize is on and the pick is cheap), high engagement (N%) (the pick’s engagement meets the 70% threshold), fast delivery for urgent message, rich content for marketing. When nothing distinctive fired, the reason states the pick scored highest for that recipient and message type. Two special cases: OTP messages return the regulatory SMS rule verbatim, and a recipient with no detected capabilities returns "No channel capabilities detected — defaulting to SMS".
  • fallback and fallbackChainfallback is the next channel to try; fallbackChain is the full ordered list after the recommended channel, in priority order. The first entry always equals fallback. Wire the chain into your send logic so a hard failure on the primary hops to fallbackChain[0], then [1], and so on. An empty array means no other channel is reachable for that recipient — treat the primary pick as terminal.
  • estimatedCostCents — per-message cost in cents on the recommended channel, before any volume tiering. Sub-cent channels (email, push at 0.1¢) come back as fractional values, so multiply rather than round when you sum a cohort. Multiply by your cohort size for a planning total.
  • engagementScore — predicted engagement probability, 0.0–1.0. This is your engagement_rates value for the recommended channel when supplied, otherwise the platform default for that channel. Use it to compare channel strategies across a cohort — a marketing blast where auto-routing averages 0.7 and your fixed channel averages 0.45 is a signal to switch.

Wire the preview into a send pipeline

The pattern: prefetch recipient capabilities, preview, then dispatch with the router’s chain as your fallback ladder.
Node.js with the SDK (@devotel/sdk-node):
Two notes on the pipeline:
  • Trust the chain, not your own ladder. The returned fallbackChain already encodes capability and cost ordering. Hand-rolled ladders skip that math.
  • Org-level fallback still applies. If your tenant has cross-channel fallback rules configured (Settings → Channels → Cross-Channel Fallback), transparent retry on terminal delivery failures continues below your chain — see Messaging Best Practices.

Preview interactively: Outbound → Route Preview

The dashboard surfaces the same endpoint as an operator tool, so a non-API teammate can answer “what would Orbit do here?” without writing code. Navigate to Outbound → Route Preview. Fill in the recipient (E.164 phone), optionally an email and country, pick the message type and urgency, toggle the recipient’s reachable channels and the cost-optimize bias, then select Preview route. The result shows the recommended channel with its reason, the fallback order as a pill chain, the per-message estimate in your account currency, and an engagement bar. Owner, admin, and developer roles can run a preview; viewers cannot — matching the API’s read-scope posture. Nothing is sent from this screen either. Use the dashboard for one-off validation (“does enabling cost-optimize swap SMS for WhatsApp on this Brazil recipient?”); use the API when the preview runs inside a pipeline.

route-preview vs. the composite risk verdict

The two pre-send lookups answer different questions — use both on high-cost traffic, in this order: Run risk/score first on unfamiliar or high-cost destinations: a high-band verdict is a reason to refuse the destination regardless of what the router recommends. For destinations you accept, route-preview settles the channel. Both are read-only lookups — see the SMS pumping protection guide for the risk verdict and the cut-offs behind its bands.

Failure modes and safety properties

  • Read-scoped keys work. A messages:read key can call the endpoint; you do not need to hand a planning job a write key.
  • Nothing ever sends. The endpoint runs the scoring function and returns — it writes no message row, touches no wallet, and emits no webhook. Repeatedly calling it in a planning loop is safe and cheap.
  • No cross-tenant leakage. Responses are computed from the request body plus platform defaults. When you omit engagement_rates, the defaults are global per-channel constants — the preview never reads another tenant’s data, and it returns your own historical rates only when you supply them.
  • 422 VALIDATION_ERROR. A malformed body (missing to, body, message_type, or urgency; bad enum value; engagement rate outside 0–1) fails schema validation before any routing runs. Validation errors are reported once per field with a human-readable message — fix the named field and retry. The fix path does not need a retry loop.
  • All-capabilities-off is not an error. A recipient with no flagged capabilities and no email gets a 200 with channel: "sms" and the diagnostic reason "No channel capabilities detected — defaulting to SMS". Decide in your pipeline whether that fallback suits the recipient — it is the router’s last-resort default, not a delivery guarantee.
  • Rate limit. The route sits behind the tenant-keyed read budget (per-tenant, per-minute). A planning pass over a large cohort should still pace calls or batch over seconds — a 429 returns retry_after and is safe to retry after that window.
The preview is deterministic — same inputs, same recommendation. Change the capability flags, urgency, message type, or cost_optimize to see how the router’s answer moves; the response’s reason tells you which factor fired.