Messaging services
A messaging service is a named configuration bundle you reference from a send. Instead of repeating a sender pool, a status callback, a validity period, and an inbound webhook on every request, you store them once on a service and passmessaging_service_id on the send. The service is also the unit that carries per-country sender routing, the per-second throughput cap, and the direct DID attachments that decide where inbound replies are delivered.
This page defines the entity: what it bundles, how it differs from a sender pool, exactly when its defaults fill in on a send, and how it is created, changed, and retired. The full sender-precedence chain the service plugs into lives on Sender resolution; the compact two-sided summary (outbound picker plus inbound MO rules) is How routing picks a sender. Request and response schemas are owned by the Messaging credentials & services reference; this page covers the model.
Where this fits
- This page (the entity) — what a messaging service is, what its fields mean, when its defaults apply, and its lifecycle. Read this when deciding whether to create a service, or when a field behaves unexpectedly.
- Sender resolution — the authoritative precedence chain across all sender sources (explicit
from, pools, services, sticky senders, fallbacks). Read that page when the question is “which sender will this one send use.” - The API reference — every field, enum value, and response shape for the
/api/v1/messaging/servicesendpoints. This page defers to it for wire detail rather than repeating it.
1. What a messaging service is
Create one withPOST /api/v1/messaging/services. Only label is required; everything else is a default the send pipeline can inherit:
Two properties of the entity worth internalising:
- Defaults, not overrides. Everything a send inherits from a service fills in only where the request left a blank. A messaging service never wins over a value you set on the send itself.
- One id, one lookup. The send pipeline resolves the service once per message; the service id is stamped onto the message so delivery callbacks and inbound replies trace back to it.
2. A service references pools; it does not replace them
A sender pool owns one job: pick exactly one of its member senders per message, by strategy (sticky, round_robin, random, geomatch), with health-based member swaps and fail-safes. A messaging service has no member-selection logic of its own — it points at a pool (sender_pool_id) or at several (the country_sender_pools map), and the pool it lands on does the picking.
That separation has two consequences:
- The pool’s strategy is still the pool’s. If the service’s pool is
sticky, sends through the service keep each recipient on a stable sender. Changing rotation behaviour means editing the pool, not the service. (One exception:area_code_geomatchon the service overrides the pool’s stored strategy with geo-matching — the one flag the service does push into pool resolution.) - A service without a pool is still valid. With no
sender_pool_idand no per-country entry for the recipient, the service simply supplies no sender source, and the send continues down the rest of the precedence chain — sticky conversation sender, org default, platform fallback. A service is a bundle of any subset of its defaults, not an all-or-nothing wrapper.
3. How messaging_service_id resolves on a send
Passing messaging_service_id on POST /api/v1/messages/sms makes the send pipeline read the service and fill in the blanks the request left. The rule is strict about where the blanks are:
- You passed
fromorfrom_extension→ the service’s sender config (default pool and per-country map) is ignored for thefromdecision. You pinned a sender; the service cannot displace it. - You passed
sender_pool_id→ your pool wins; the service’s pool and per-country map are not consulted. (And a barefromsent together withsender_pool_idloses to the pool — see Sender resolution for that precedence rule.) - You passed neither → the service’s sender source fills in: first the recipient-country’s override pool from
country_sender_pools, then the service’s defaultsender_pool_id.
status_callback and validity_period on the service copy onto the send only when the request itself carries neither. Whatever the merge produces, the service id is stamped on the message so status callbacks and inbound replies can be attributed back to it.
Two boundary rules:
- The service must exist. Referencing an unknown id fails the send with
404 MESSAGING_SERVICE_NOT_FOUND— there is no silent skip-a-service path. - Flags are unconditional.
sticky_sender,area_code_geomatch,mms_converter, andsmart_encodingare not “fill in on blank”: they are on or off on the service and apply to every send routed through it.
4. The per-country country_sender_pools map
A service can carry a map from ISO 3166-1 alpha-2 country codes (uppercase, e.g. US, GB, DE) to sender-pool ids, up to 250 entries — room for the UN’s 195 countries plus non-recognised territories. Values must be pool ids in the same format as sender_pool_id.
At send time the recipient’s country is resolved — taken from an explicit recipient_country on the request, otherwise derived from to — and the chain is:
country_sender_pools[<recipient country>]— the per-country override pool, if an entry existssender_pool_id— the service’s default pool- Nothing — the service supplies no pool and the precedence chain continues
- The map never overrides you. The override and the default pool both fill in only when the request carries no
sender_pool_idof its own. - Keep entries corridor-correct. Point each country at the pool you maintain for that corridor — numeric-only pools for US/Canada entries, since
+1carriers reject alphanumeric MT — for the same reasons the pools themselves are kept corridor-correct.
country_sender_pools: { "GB": "pool_gb" } entry and a US recipient falling through to the default pool — is the worked example on Sender resolution; it is the canonical illustration of this lookup and is not repeated here. Update the map with PATCH /api/v1/messaging/services/:id: send the full replacement map, send {} to clear it, or omit the field to leave it untouched.
5. Inbound binding: where replies to the service’s numbers go
The inbound fields set the destination for replies, not for sends. Attach tenant-owned numbers to a service (POST /api/v1/messaging/services/:id/phone-numbers with a phone_number_id — the same number may be attached to several services, and attaching a number that reports no SMS capability is refused with 422 NOT_SMS_CAPABLE). When an inbound SMS arrives on any attached number, Orbit delivers it to the service’s inbound_webhook_url using inbound_method (POST by default, or GET), and calls fallback_url once if the primary fails.
Delivery of that webhook is fail-soft by design: a slow, down, or misconfigured endpoint never blocks the inbound message itself from persisting, and the service-level webhook sits underneath the tenant-level inbound rules — the rules that match on number, sender, keyword, or regex and route to a webhook, queue, inbox, team, or SMS menu. Routing precedence between the two layers belongs to Inbound message routing; this page only notes that the service binding is the per-numbers web address you manage under the service’s lifecycle.
6. Lifecycle: create, update, delete, attach numbers
All endpoints live under/api/v1/messaging/services; the full schemas are in the Messaging credentials & services reference.
- Create —
POSTwith alabeland any subset of defaults. Creating is additive: a service with no pool and no callback is a legal starting point, and you backfill fields later. - Update —
PATCH /:idis partial: omitted fields are untouched, an empty body is rejected, and nullable fields (sender_pool_id,status_callback,validity_period_seconds,mps_cap,external_id,inbound_webhook_url,fallback_url) acceptnullto clear.country_sender_poolsclears via{}. Updates propagate to the send path within a few seconds (the resolved config is cached briefly). - Delete —
DELETE /:idremoves the service and its number attachments; deleting an already-deleted service returns 404. Sends still referencing the id start failing with404 MESSAGING_SERVICE_NOT_FOUNDfrom the next request, so re-map or delete the usages first. - Number attachments —
POST/DELETEon/:id/phone-numbers; re-attaching the same number is idempotent. - Import correlation —
external_idmaps a provider service id (a TwilioMG…SID or another vendor’s opaque id) onto ours;GET /:id-shapedGET /lookup?external_id=…resolves one, and the migration import upserts by it, so a re-import updates rather than duplicates.external_idis unique per tenant — a duplicate returns409.
sender_pool_id and bare from on the request always beat it, and the service’s pool/callback/validity defaults fill in only where the request is silent. The full precedence table, including what happens when you pass both from and sender_pool_id, is section 1–3 of Sender resolution.
7. The per-service throughput cap
mps_cap is the operator-set, per-messaging-service ceiling on send rate: an integer between 1 and 1000 messages per second, or no cap when unset. When a service carries a cap, every send through that service increments a per-second counter; crossing the cap rejects the send with 429 MESSAGING_SERVICE_MPS_EXCEEDED and Retry-After: 1, so one bursty integration cannot starve the tenant’s other services. Test-mode sends bypass the cap so you can probe the pipeline from a staging tenant without tripping it.
Because the cap is per service, it is the service — not the tenant — that owns this knob: GET and PATCH /api/v1/messaging/services/:id/throughput read and write just the cap (sending null clears it), and GET /:id/throughput-stats plus the account-wide GET /throughput-stats report live send rate, over-cap queue depth, and recent throttle events per service with per-service labels. Where this cap sits relative to every other limit in the platform — tenant ceilings, provider limits, cooldowns — is the rate-limit and cooldown taxonomy.
See also
- Sender resolution — the authoritative precedence chain this entity plugs into, including the per-country lookup worked example
- How routing picks a sender — the compact two-sided summary (outbound sender + inbound MO rules)
- Sender pools guide — create the pools a service references, preview picks, read health
- Messaging credentials & services — the full endpoint and field reference for the service lifecycle
- Rate-limit and cooldown taxonomy — where the per-service MPS cap fits among all limits
- Inbound message routing — the tenant-level rule layer above the service’s inbound webhook binding
- Imports and migration model — how
external_idmaps a provider service id during import