Skip to main content

Sender resolution

Every outbound message needs a sender. You can name one on the request, point at a sender pool, or point at a messaging service — and you can sometimes supply more than one of these on the same POST /messages body. This page is the single source of truth for the whole outbound chain: which source wins, how the pool’s strategy picks exactly one member (and what happens when that pick fails), how per-country pool overrides work, and what happens when no source resolves. How routing picks a sender is the companion page — a compact summary of this chain plus the inbound (MO) rule routing on the other side of the send.

Where this fits

  • This page (outbound, full depth) — the complete precedence chain, all four pool strategies with their fail-safes, messaging-service defaults and the per-country country_sender_pools map, channel-specific rules, and the fallback chain. Read this when you need to know exactly which sender a specific send will use, or why a send went out from an unexpected sender. When the question is instead which construct to build — pool, service, or inline sender — that decision belongs to Choosing a sender construct.
  • How routing picks a sender — the same chain as a compact reference, plus the inbound (MO) side: tenant-level rules that match a received message and route it to a webhook, queue, inbox, team, or SMS menu. Read that page when the question is “where does a reply/inbound message land,” not “which sender goes out.”

1. sender_pool_id beats a bare from

Passing from on the request body names the sender directly — as long as no sender_pool_id travels with it. When you send both from and sender_pool_id on the same POST, the pool resolves after the explicit-from read and wins (Twilio Messaging Service parity: a pool/service reference is an explicit identity choice for the send). The message goes out from the pool’s pick and the from you passed is silently ignored.
This send uses a member of pool_abc123, not +14155559999. To pin the sender, pass from alone and drop the pool id. Three adjacent rules:
  • Empty from counts as omitted. A from: "" body behaves exactly like no from at all and falls through to the rest of the chain (this is what the dashboard’s “Use account default” option submits).
  • A validated from still needs to be deliverable. It must be a number your tenant owns, a valid short code, or a registered alphanumeric sender ID for the destination’s rule set; a from that fails that validation returns a 422 with a field-level detail naming from.
  • from_extension is a sibling of from, not of the pool. It resolves a UCaaS extension to its DID identity; it cannot be combined with a pool id on the same request — from_extension and sender_pool_id are mutually exclusive and sending both returns 422 VALIDATION_ERROR.

2. sender_pool_id resolution order per strategy

When you pass sender_pool_id (without an explicit from), the pool picks exactly one of its members at send time: Pool resolution happens before org defaults and platform fallbacks, so a pool id always wins over the fallback chain below. One override to know: if the parent messaging service has area_code_geomatch enabled, the pool resolves via geomatch even when its own stored strategy is sticky, round_robin, or random. Each pool member accumulates a health tier from delivery outcomes, and the rotation scheduler auto-swaps a degraded member for a warmed replacement, so rotation keeps preferring senders that deliver. Creating pools, previewing which sender a recipient would get, and reading health are covered in the Sender pools guide.

Common misconfigurations

  • Pool with no senders — an empty pool returns 422 SENDER_POOL_EMPTY at send time. Add at least one DID or sender ID to the pool before routing traffic through it.
  • Passing both from and sender_pool_id — the pool wins and the explicit from is silently ignored. If you expected your explicit from to beat the pool you also passed, the pool’s pick is used instead. Drop the pool when you want a fixed sender, or drop from when you want pool rotation.
  • Mixed sender types in one pool — a pool may hold E.164 long codes, short codes, and alphanumeric sender IDs together. Each pick is routed by the member’s type (numeric members go down the numeric path, alphanumeric members down the alphanumeric path), so a mixed pool does not mis-route — but check the destination’s rules before mixing: US and Canada corridors reject alphanumeric MT, so if an alphanumeric member can be picked for a +1 recipient, the carrier may drop the message. Keep per-corridor pools in regions where alphanumeric senders apply.

3. Messaging-service defaults and the per-country pool map

A messaging service binds a default sending identity plus per-message settings (status_callback, validity_period) — the entity itself is defined on Messaging services. When you pass messaging_service_id, the service’s own sender source fills in only if you did not pass a sender yourself:
  • You passed from or from_extension → the service’s sender config is ignored for the from decision.
  • You passed sender_pool_id → your pool wins; the service’s pool is ignored.
  • You passed neither → the service’s pool becomes the sender source.
Message-level fields always beat service-level config: the service is a fallback, not an override.

The per-country country_sender_pools map

A messaging service can also carry a country_sender_pools map: ISO-3166-1 alpha-2 country code (uppercase) → pool id, up to 250 entries. At send time the recipient’s country is derived from to (or taken from an explicit recipient_country you passed), the map is looked up for that country, and — if it has an entry — that pool is used before the service’s default sender_pool_id. The chain is:
  1. country_sender_pools[<recipient country>] — per-country override pool
  2. sender_pool_id — the service’s default pool
  3. Nothing — the send proceeds without a service-supplied pool and falls to the rest of the chain
Two rules bound this behaviour:
  • The map never overrides you. Both the per-country override and the default pool fill in only when you did not set sender_pool_id on the request. A pool you passed on the body always wins.
  • Keep per-country pools corridor-correct. Like pointing each country at the pool you maintain for that corridor — same-country members for geomatch, numeric-only pools for US/Canada entries (see the channel rules below).
For the full service schema, see the Messaging credentials & services reference.

4. Channel-specific rules

  • WhatsApp — Meta owns the sender: a business-initiated message must use an approved template, and only a message inside the 24-hour session window opened by the user’s last inbound may be free-form. There is no sender pool or arbitrary from on this channel; the sender is your WABA phone number, and the template/window rules are what gate the send.
  • SMS alphanumeric sender ID — in countries where local regulation or carrier practice requires a pre-registered alphanumeric sender ID (much of Europe, the Middle East, and Asia), keep that sender in a pool or pass it as from; in US/Canada corridors alphanumeric MT is not accepted, so a +1 destination falls back to a platform phone number while other destinations use the platform alphanumeric sender (see below). Keep the two pools separate per corridor.
  • Two-way SMS replies — when you reply on an existing conversation without naming a sender, Orbit pins the sender to the DID the contact originally texted, so the thread stays on one number. This only fires for SMS and only when no from, from_extension, or pool already named a sender.

5. The fallback chain, when nothing named a sender

With no from, no extension, no pool, no service, and no sticky inbound sender, the sender resolves in this order — the first source that produces a sender wins:
  1. Your organization’s default sender, if one is set for the channel.
  2. Your first active phone number.
  3. The platform default: a platform phone number for US/Canada (+1) destinations, or the Devotel alphanumeric sender ID for other destinations.
The US/Canada substitution exists because +1 carriers reject the shared alphanumeric sender, so the implicit default for a +1 recipient is always a phone number. If you confirm a shared alphanumeric sender for a US send and see a phone number on the delivered message, this is the reason — pass from or a pool to pin the identity instead.

6. Worked example — one send through the whole chain

A tenant with two sender pools and one messaging service sends an order update. The messaging service ms_ops01 carries sender_pool_id: pool_default and a country_sender_pools map { "GB": "pool_gb" }; pool_gb is configured geomatch and holds two UK numbers plus one DE number, while pool_default is sticky and holds two US numbers.
The resolution, step by step:
  1. No explicit sender on the body. No from, no from_extension, no sender_pool_id — nothing in section 1 fires, so the send falls to the messaging service.
  2. The service’s per-country map fires first. The recipient country derived from +447700900123 is GB, and country_sender_pools["GB"] is set, so pool_gb wins over the service’s default pool_default.
  3. The pool’s strategy picks one member. pool_gb runs geomatch: the recipient’s country GB matches the two UK members, and the pick lands on one of them.
  4. The fail-safe never fires here. If neither UK member had been deliverable, geomatch would have fallen back to sticky over the whole pool (the DE member included) — the send still picks a member, so it never reaches…
  5. The fallback chain — untouched. The send leaves the chain at step 2’s pool pick; the org default, first active number, and platform default are never consulted.
Change the recipient to a US number in the same request and the story changes at step 2: country_sender_pools has no US entry, so the service’s default pool_default supplies the sender via sticky — a deterministic, persisted pick that keeps the same US recipient on the same sender across sends. Two ways to short-circuit the walk: pass sender_pool_id on the body to skip the service’s pools entirely (your pool wins), or pass from alone to pin one sender and make the whole pool/service/fallback chain irrelevant.

Troubleshooting quick reference

See also