Skip to main content

How routing picks a sender — and where a reply lands

Routing is two-sided. Outbound, Orbit resolves which sender a message goes out from. Inbound (MO — mobile-originated), tenant-level rules decide where a received message lands. This page covers the inbound rule model end to end and keeps the outbound side in compact form; the full outbound chain lives on Sender resolution.

Which page is authoritative for what

  • This page is authoritative on inbound. Tenant-level MO rule dispatch — match types, dispatch targets, priority and conflict resolution, and the default-inbox fall-through — is covered here in full. The tenant-ownership layer underneath it (which tenant a received message resolves to, before any rules run) is Inbound message resolution; the full target vocabulary, failure counters, and validation guarantees are on Inbound message routing.
  • Sender resolution is authoritative on outbound. Its chain of six source types, all four pool strategies with fail-safes, the per-country country_sender_pools map, channel-specific rules, and the fallback chain all live there. This page keeps only a compact index of that chain so it stays readable; when the two disagree, that page wins. Choosing between a pool, a service, and an inline sender in the first place is the decision model on Choosing a sender construct.

Outbound: pick a sender (summary)

On POST /api/v1/messages/sms, Orbit resolves the sender from the first source that produces one, in this order:
  1. from_extension — a UCaaS extension identity; resolves to the DID assigned to that extension.
  2. sender_pool_id — a rotating pool; the pool’s strategy picks one member and overrides a bare from on the same body.
  3. from — pin the sender directly, honoured only when no pool id rode along.
  4. messaging_service_id — the service’s own sender source fills in when the body carries neither a pool nor an explicit sender.
  5. Conversation sticky sender — two-way replies pin the sender to the DID the contact originally texted.
  6. Fallback chain — org default sender → your first active number → the platform default number for +1 destinations or the Devotel alphanumeric sender ID for international destinations.
The full treatment — every pool strategy with its fail-safe, the per-country override map, health-tier member swaps, channel-specific rules, and a line-by-line worked example — is on Sender resolution. Which construct to build in the first place (pool vs messaging service vs inline sender) is the decision model on Choosing a sender construct.

Inbound: route a received message (MO)

Tenant-level inbound rules are evaluated in priority order (lower value first; ties break by creation time, oldest first) and match on:
  • number — the destination DID the message arrived on
  • sender — the originating address
  • keyword — case-insensitive substring of the message body
  • regex — a regular expression tested against the body
The first enabled rule that matches dispatches to one of: a webhook (signed HTTPS POST), a queue, an inbox, a team, or an ivr keyword menu tree. Evaluation is first-match-wins: once a rule matches, later rules are never consulted, so ordering matters — put specific exceptions at lower priority values than broad catch-alls, and leave gaps (10, 20, 30) so you can insert a rule between two existing ones without renumbering. When no rule matches, the message falls through to the default inbox and the message.received event, so rules are an overlay on your baseline handling, never a replacement for it. Manage rules under /api/v1/messages/inbound-routes; the full target vocabulary (auto_reply, appointment), failure counters, and validation guarantees are on Inbound message routing. This tenant-level rule set sits above the per-DID inbound hooks you may also have configured: pools own the “which sender” decision, these rules own the “where the reply lands” decision.

Worked example — inbound MO keyword match into a queue

An inbound-routing tenant has an existing catch-all rule — keyword support into the support team at priority: 30 — and wants customer replies containing urgent to reach the on-call queue first. Create the specific rule at a lower priority value:
Now the message please call support — this is urgent arrives on your DID:
  1. Rules load in priority order. Rule “Urgent escalations to on-call queue” (priority: 10) evaluates before the catch-all “support” rule (priority: 30) — lower value first.
  2. The keyword matches. urgent is a case-insensitive substring match, so the body containing “urgent” hits rule 1.
  3. The message dispatches to the queue. The matched conversation is handed to queue_oncall for the on-call team — first-match-wins, so the priority: 30 catch-all is never consulted for this message.
  4. If it had matched nothing. A message containing neither keyword — say ok thanks — skips both rules and falls through to the default inbox, firing the tenant-wide message.received event exactly as if no rules existed.
The tie-break matters when two rules share a priority value: evaluation breaks ties by creation time, oldest first, so the rule created earlier wins. Rely on distinct priority values rather than the tie-break — distinct values make the intended order explicit in one number.

See also