> ## 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.

# SMS sending services: bundle numbers, opt-out list, and inbound webhook

> Create a reusable messaging service in the console or via the API, attach sender numbers plus a STOP/HELP opt-out list and an inbound-reply webhook, then send through it from every message.

# SMS sending services

A messaging service is a reusable sender profile: one container that bundles the sender pool (which of your numbers send), the opt-out list (which STOP/HELP keywords and auto-replies apply), and the inbound-reply webhook (where incoming messages land). Once a service exists, you point sends at its id and get the same configuration on every message instead of repeating `from` numbers and webhook URLs per send.

This guide covers the full lifecycle: create a service in the console or with `POST /api/v1/messaging/services`, attach numbers, wire the opt-out list and inbound webhook, and send through it. For the endpoint shapes, see the [Messaging Services API reference](/api-reference/endpoints/messaging-services); for the sender-pool rotation strategies a service's pool can use, see [Sender pools](/guides/sender-pools).

## 1. When to create a service vs an ad-hoc sender

Create a service when any of these hold:

* **More than one sender per channel** — a single hardcoded `from` number throttles and burns carrier reputation; a service with a pool rotates senders per recipient.
* **Branded compliance** — if you send under more than one brand, you want that brand's STOP/HELP copy on every message, not the platform defaults.
* **Two-way SMS** — inbound replies need one webhook URL that every sender number in the service points at.
* **Migration from Twilio** — if you're mapping a `MessagingServiceSid` (`MGxxx`) over, the Orbit service is the equivalent container.

If a send is genuinely one-off (one number, default STOP/HELP copy, no inbound replies), an ad-hoc `from` is fine. The service exists to stop the "same four fields on every send" repetition once you're past that.

## 2. Create a service

### In the console

Open **Messages → SMS → Services** (`/messages/sms/services`) and click **New service**. The list page creates, edits, and deletes services; drilling into a service opens its **Config**, **Phone Numbers**, and **Inbound Webhook** tabs. Anything you can do in those tabs is available through the API below on the same service id.

### Via the API

Only `label` is required — the rest of the config attaches later:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/messaging/services \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "Acme Travel alerts",
    "sender_pool_id": "pool_abc123",
    "opt_out_list_id": "ool_abc123",
    "inbound_webhook_url": "https://your-app.example.com/sms/inbound"
  }'
```

The response carries the service's Orbit id (`msvc_xxx`), which is what sends reference. Owner or admin scope is required; a duplicate `external_id` (used for Twilio `MGxxx` migration) returns 409.

## 3. Attach numbers, opt-out list, and inbound webhook

### Sender numbers

You have two ways to give a service senders, and most services want the pool:

* **Sender pool (recommended)** — set `sender_pool_id` (or per-country `country_sender_pools`) so the service resolves `from` through a named pool's strategy. See [Sender pools](/guides/sender-pools) for `sticky` / `round_robin` / `random` / `geomatch` behavior.
* **Direct phone-number attachment** — for a fixed DID rather than a pool, attach specific tenant-owned numbers. SMS-capable DIDs only; voice/MMS-only DIDs are rejected with 422, and re-attaching the same number is idempotent:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/messaging/services/msvc_abc123/phone-numbers" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "phone_number_id": "pn_abc123" }'
```

A service with no sender at all resolves with a 4xx at send time, so attach at least one before you send through it.

### Opt-out (STOP/HELP) list

Set `opt_out_list_id` on the service to point at a custom list — extra keyword aliases plus branded STOP/HELP/START auto-reply text. The platform's carrier-mandated defaults always merge in; your list can add to but never remove `STOP` itself. For list creation and field details, see [Custom Opt-Out Keyword Lists](/guides/opt-out-lists). Set `opt_out_list_id` back to `null` to fall back to the platform defaults.

### Inbound-reply webhook

Set `inbound_webhook_url` (and optional `inbound_method`, default `POST`) so every inbound reply to any sender in the service POSTs to your endpoint. Optional `fallback_url` and `status_callback` fields further direct delivery-status events. Webhook URLs are validated server-side to block internal/private addresses — use a public HTTPS URL.

## 4. Send through the service

Pass `messaging_service_id` on the message instead of per-field `from`/webhook values. Any field you set explicitly on the message still wins; the service supplies the rest, matching Twilio's MessagingServiceSid contract:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/messages \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "messaging_service_id": "msvc_abc123",
    "to": "+14155550199",
    "body": "Your Acme Travel trip starts tomorrow."
  }'
```

The send path resolves the service once at send time and stamps the resolved sender onto the message, so a later edit to the service never retroactively changes already-sent messages. Throughput and routing guardrails (per-service `mps_cap`, quiet hours, send gates) apply the same way as any other send; per-service `mps_cap` lets you throttle one profile below the tenant limit.

## 5. Multi-sender rotation and fallback

Rotation behavior is the sender pool's strategy, not the service's — the service just resolves through the pool each send:

* **`sticky`** keeps a recipient on the same sender so two-way threads stay on one number.
* **`round_robin`** spreads volume evenly across pool members.
* **`geomatch`** picks a sender from the recipient's country when one exists in the pool, falling back to the pool's remaining senders rather than failing the send.
* **`random`** rotates statelessly per send.

If every sender in the pool degrades or a pool goes empty, the send fails with a sender-resolution error — fix it from the pool health readout rather than reworking your send code. To preempt, preview which sender a recipient would get (`GET /messaging/sender-pools/{id}/preview`) before routing traffic.

## 6. Troubleshooting

| Symptom                                                      | Likely cause                                                                                                | Fix                                                                                                                  |
| ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `404` creating/updating/sending with a service id            | The id is not a messaging service in this tenant (or was deleted)                                           | `GET /api/v1/messaging/services` to list ids; deleted services respond `404` on new sends immediately                |
| `409` on create                                              | `external_id` (Twilio `MGxxx`) already taken by another service in this tenant                              | Reuse that service, or look it up by `GET /services/lookup?external_id=`                                             |
| `422 SENDER_POOL_EMPTY` or sender-resolution failure on send | Service has no pool, or the pool has no eligible sender                                                     | Attach a pool or a phone number; preview the pool's sender for a recipient                                           |
| `422` when attaching a phone number                          | The DID isn't SMS-capable (voice/fax/MMS-only)                                                              | Attach an SMS-capable DID                                                                                            |
| Inbound replies never hit your endpoint                      | `inbound_webhook_url` unset on the service, or the URL failed validation (private/internal address blocked) | Set a public HTTPS webhook on the service                                                                            |
| Messages throttled / `429` events on one service             | The service's `mps_cap` is below your send rate                                                             | `GET /services/{id}/throughput-stats` to confirm, then raise or clear the cap with `PATCH /services/{id}/throughput` |
| STOP replies use the generic copy                            | No `opt_out_list_id` on the service                                                                         | Attach a custom opt-out list                                                                                         |
| Edited config not applied                                    | Service cache busts on PATCH; a send during the same second may see the old value                           | Re-send — the next send reads the update                                                                             |

## See also

* [Messaging Services API reference](/api-reference/endpoints/messaging-services) — endpoint contract for every operation above
* [Sender pools](/guides/sender-pools) — rotation strategies a service's pool can use
* [Custom Opt-Out Keyword Lists](/guides/opt-out-lists) — branded STOP/HELP copy and keyword aliases
* [Batch SMS](/guides/messages-batch-sms) — the one-off multi-recipient send surface that can reference a service
* [Sending and receiving messages](/guides/send-receive-messages) — the `POST /messages` flow that carries `messaging_service_id`
