> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Messaging services — one named bundle of sending defaults

> What a messaging service is and when to create one: it binds a default sender pool, per-country pool overrides, a status callback, a default validity period, an inbound webhook, and a per-second throughput cap under one id — and a send that names it inherits those defaults only for fields the request left blank.

# 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 pass `messaging_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](/concepts/sender-resolution); the compact two-sided summary (outbound picker plus inbound MO rules) is [How routing picks a sender](/concepts/sender-and-routing). Request and response schemas are owned by the [Messaging credentials & services reference](/api-reference/messaging-credentials); 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](/concepts/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/services` endpoints. This page defers to it for wire detail rather than repeating it.

## 1. What a messaging service is

Create one with `POST /api/v1/messaging/services`. Only `label` is required; everything else is a default the send pipeline can inherit:

| Field                                                                    | What it defaults                                                 | When it applies                                                                                                                                                                    |
| ------------------------------------------------------------------------ | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sender_pool_id`                                                         | The sender pool that supplies the `from`                         | When the send omits every explicit sender field (below)                                                                                                                            |
| `country_sender_pools`                                                   | Per-country override pools                                       | Same precondition; map entry beats the default pool (section 4)                                                                                                                    |
| `status_callback`                                                        | Delivery-status webhook URL                                      | When the send has no `status_callback`                                                                                                                                             |
| `validity_period_seconds`                                                | How long a message stays retryable (0–14400, i.e. up to 4 hours) | When the send has no `validity_period`                                                                                                                                             |
| `opt_out_list_id`                                                        | The opt-out keyword list the send checks against                 | Every send through the service                                                                                                                                                     |
| `sticky_sender`, `area_code_geomatch`, `mms_converter`, `smart_encoding` | Behaviour flags                                                  | Every send through the service — e.g. `area_code_geomatch` overrides the pool's stored pick strategy with geo-matching, and `mms_converter` auto-upgrades an SMS with media to MMS |
| `inbound_webhook_url`, `inbound_method`, `fallback_url`                  | Where inbound replies are delivered                              | Every inbound message that arrives on a number attached to the service (section 5)                                                                                                 |
| `mps_cap`                                                                | Per-second send-rate ceiling (1–1000 messages/sec)               | Every send through the service (section 7)                                                                                                                                         |
| `external_id`, `feature_flags`                                           | Import bookkeeping                                               | Never consulted by the send path — `external_id` maps a provider ID (e.g. a Twilio `MG…` SID) to this service during migration; `feature_flags` is opaque tenant-controlled JSON   |

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_geomatch` on 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_id` and 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 `from` or `from_extension`** → the service's sender config (default pool and per-country map) is ignored for the `from` decision. 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 bare `from` sent together with `sender_pool_id` loses to the pool — see [Sender resolution](/concepts/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 default `sender_pool_id`.

The same fill-in-only-on-blank rule applies to the message-level fields: `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`, and `smart_encoding` are 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:

1. `country_sender_pools[<recipient country>]` — the per-country override pool, if an entry exists
2. `sender_pool_id` — the service's default pool
3. Nothing — the service supplies no pool and the precedence chain continues

Two rules bound the map:

* **The map never overrides you.** The override and the default pool both fill in only when the request carries no `sender_pool_id` of 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 `+1` carriers reject alphanumeric MT — for the same reasons the pools themselves are kept corridor-correct.

The end-to-end walkthrough — a GB recipient hitting a `country_sender_pools: { "GB": "pool_gb" }` entry and a US recipient falling through to the default pool — is the worked example on [Sender resolution](/concepts/sender-resolution#6-worked-example--one-send-through-the-whole-chain); 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](/concepts/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](/api-reference/messaging-credentials).

* **Create** — `POST` with a `label` and 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 /:id` is 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`) accept `null` to clear. `country_sender_pools` clears via `{}`. Updates propagate to the send path within a few seconds (the resolved config is cached briefly).
* **Delete** — `DELETE /:id` removes the service *and* its number attachments; deleting an already-deleted service returns 404. Sends still referencing the id start failing with `404 MESSAGING_SERVICE_NOT_FOUND` from the next request, so re-map or delete the usages first.
* **Number attachments** — `POST` / `DELETE` on `/:id/phone-numbers`; re-attaching the same number is idempotent.
* **Import correlation** — `external_id` maps a provider service id (a Twilio `MG…` SID or another vendor's opaque id) onto ours; `GET /:id`-shaped `GET /lookup?external_id=…` resolves one, and the migration import upserts by it, so a re-import updates rather than duplicates. `external_id` is unique per tenant — a duplicate returns `409`.

Composition with per-request fields, in one sentence: the service is the fallback layer — `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](/concepts/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](/concepts/rate-limit-and-cooldown-taxonomy).

## See also

* [Sender resolution](/concepts/sender-resolution) — the authoritative precedence chain this entity plugs into, including the per-country lookup worked example
* [How routing picks a sender](/concepts/sender-and-routing) — the compact two-sided summary (outbound sender + inbound MO rules)
* [Sender pools guide](/guides/sender-pools) — create the pools a service references, preview picks, read health
* [Messaging credentials & services](/api-reference/messaging-credentials) — the full endpoint and field reference for the service lifecycle
* [Rate-limit and cooldown taxonomy](/concepts/rate-limit-and-cooldown-taxonomy) — where the per-service MPS cap fits among all limits
* [Inbound message routing](/concepts/inbound-message-routing) — the tenant-level rule layer above the service's inbound webhook binding
* [Imports and migration model](/concepts/imports-migration-model) — how `external_id` maps a provider service id during import
