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

# Inbound email and SMS routing

> Configure inbound email parsing on your domain, route incoming SMS with match rules, auto-reply to common keywords, and honor bounces and unsubscribes — an end-to-end walkthrough of Orbit's inbound surface.

# Inbound email and SMS routing

Most of Orbit's email and SMS surface is built for sending. This guide covers the other direction: what happens when a customer writes *to* you, and how you control where that inbound traffic goes.

**You will:**

1. [Configure inbound parse on a domain](#1-configure-inbound-parse-on-a-domain)
2. [Understand the normalized inbound payload](#2-the-normalized-inbound-payload)
3. [Route inbound SMS with match rules](#3-route-inbound-sms-with-match-rules)
4. [Auto-reply with Flows](#4-auto-reply-with-flows)
5. [Handle bounces and unsubscribes](#5-bounces-and-unsubscribes)

## Prerequisites

* An API key from **Settings → API Keys** (a `dv_live_sk_…` live key — inbound routing configures real traffic).
* For email: a domain you can point MX records at Orbit (for example, a dedicated subdomain such as `inbound.your-domain.com`).
* For SMS: an inbound-capable number on your account (purchased via the [Numbers API](/api-reference/numbers) or **Dashboard → Numbers**).
* A public HTTPS URL to receive webhook forwards.

<Note>
  Inbound route management endpoints require the **owner** or **admin** role. API keys created with the member role receive `403` on the CRUD calls below.
</Note>

## 1. Configure inbound parse on a domain

Inbound parse takes mail addressed to a domain or address you control, matches it against a route you register, parses the message into structured fields, and forwards the result as a signed JSON POST to your webhook URL.

### Point MX records

Point the receiving domain's MX records at Orbit's ingress (the dashboard shows the hostname when you open **Email → Inbound routes → Add route**). Once DNS propagates, any address at that domain (`support@inbound.your-domain.com`, `billing@inbound.your-domain.com`, …) is a candidate for a route.

### Create a route

Register the destination pattern and webhook URL with `POST /email/inbound-routes`:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/email/inbound-routes \
  -H "X-API-Key: dv_live_sk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "recipientPattern": "support@inbound.your-domain.com",
    "destinationUrl": "https://api.your-app.com/hooks/orbit-inbound",
    "includeRawMime": false,
    "description": "Support mailbox → helpdesk"
  }'
```

The response `201` carries — exactly once — the route's `signingSecret`. Store it now; list and get responses mask secrets.

```json theme={null}
{
  "data": {
    "id": "einr_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
    "recipientPattern": "support@inbound.your-domain.com",
    "destinationUrl": "https://api.your-app.com/hooks/orbit-inbound",
    "signingSecret": "einrsk_c0ffee…",
    "active": true
  }
}
```

`recipientPattern` accepts three shapes:

| Pattern                           | Matches                                        |
| --------------------------------- | ---------------------------------------------- |
| `support@inbound.your-domain.com` | That exact address                             |
| `*@inbound.your-domain.com`       | Any address at the domain (catch-all)          |
| `support`                         | The local-part `support` at any inbound domain |

Recipients are matched against the SMTP envelope recipient first, then the `To` and `Cc` headers — BCC, aliased, and forwarded mail route correctly even when the `To` header omits your address. A duplicate pattern returns `409 CONFLICT`. Routes are manageable from `GET /email/inbound-routes` (list) and `DELETE /email/inbound-routes/:routeId`.

## 2. The normalized inbound payload

Each matched message is POSTed to your `destinationUrl` as JSON, signed with the route's secret:

```json theme={null}
{
  "from": "Jane Customer <jane@example.com>",
  "to": "support@inbound.your-domain.com",
  "toAddress": "support@inbound.your-domain.com",
  "matchedRecipient": "support@inbound.your-domain.com",
  "subject": "Question about my order",
  "text": "Hi — what's the status of order #1234?",
  "html": "<p>Hi — what's the status of order #1234?</p>",
  "attachments": [
    {
      "filename": "order-confirmation.pdf",
      "contentType": "application/pdf",
      "size": 84321,
      "contentId": null
    }
  ],
  "headers": {
    "from": "Jane Customer <jane@example.com>",
    "to": "support@inbound.your-domain.com",
    "subject": "Question about my order",
    "message-id": "<95b1f744e4e2@example.com>"
  }
}
```

* `text` and `html` are the decoded body parts (both can be `null`).
* `attachments` is **metadata only** — filename, MIME type, decoded byte size, and the inline-image `Content-ID`. Attachment bytes are never inlined into the JSON. Set `includeRawMime: true` on the route when you need the full original message.
* `headers` carries every top-level header as a lowercased key map.

Verify the `X-Orbit-Signature` header (`sha256=<hex>` over the raw body, HMAC-keyed by the route's `signingSecret`) before trusting a forward, as you would for any [webhook](/guides/webhook-consumer). Only 2xx responses count as a successful forward — redirects are not followed — and the route's `failureCount` / `lastFailureAt` (visible in `GET /email/inbound-routes`) track failures.

<Note>
  Every matched inbound email also opens (or threads onto) an inbox **ticket** in parallel with your webhook forward — the internal helpdesk surface works side by side with your developer integration. See the ticket fields in the [receive step](/guides/send-receive-messages) of the two-way messages guide.
</Note>

## 3. Route inbound SMS with match rules

Inbound SMS routing is an ordered rule engine over each inbound (MO) message: `POST /messages/inbound-routes` registers a rule, and inbound messages are matched in ascending `priority` (lower number wins). When no rule matches, the message falls through to the default behaviour — the tenant-wide `message.received` event and inbox persistence. Rules are matched only on enabled entries.

Match types:

| `matchType` | Matches against                                                                              | Example        |
| ----------- | -------------------------------------------------------------------------------------------- | -------------- |
| `number`    | The destination DID the message arrived on                                                   | `+18005551234` |
| `sender`    | The originating sender                                                                       | `+14155552671` |
| `keyword`   | Substring of the message body (case-insensitive)                                             | `STATUS`       |
| `regex`     | Regex over the message body (validated at write time — a bad pattern is rejected with `422`) | `^STATUS \d+$` |

Target types:

| `targetType`   | Destination                                                    | `targetConfig`                                  |
| -------------- | -------------------------------------------------------------- | ----------------------------------------------- |
| `webhook`      | Signed POST (or PUT) to your URL                               | `{ "url": …, "secret"?: …, "method"?: "POST" }` |
| `queue`/       | Route into an inbox queue                                      | `{ "queueId": "…" }`                            |
| `inbox`/`team` | Route into an inbox or team                                    | `{ "inboxId": … }` / `{ "teamId": … }`          |
| `ivr`          | Open an interactive SMS menu (keyword replies traverse a tree) | `{ "menu": { …validated menu tree… } }`         |
| `auto_reply`   | Templated reply directly to the sender                         | `{ "template": "…" }` (+ optional cooldown)     |
| `appointment`  | Self-service confirm/reschedule/cancel                         | keyword sets + reply copy                       |

### Example: keyword → webhook

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/messages/inbound-routes \
  -H "X-API-Key: dv_live_sk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Track orders",
    "matchType": "keyword",
    "matchValue": "STATUS",
    "targetType": "webhook",
    "targetConfig": {
      "url": "https://api.your-app.com/hooks/orbit-sms-status",
      "secret": "whsec_your_own_signing_secret"
    },
    "priority": 50
  }'
```

```json theme={null}
{
  "data": {
    "id": "smsr_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
    "matchType": "keyword",
    "matchValue": "STATUS",
    "targetType": "webhook",
    "priority": 50,
    "enabled": true,
    "targetConfig": { "url": "https://api.your-app.com/hooks/orbit-sms-status" }
  }
}
```

A webhook-delivered inbound message looks like:

```json theme={null}
{
  "type": "message.inbound.routed",
  "routeId": "smsr_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
  "messageId": "msg_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
  "channel": "sms",
  "from": "+14155552671",
  "to": "+18005551234",
  "body": "STATUS 1234"
}
```

The `secret` you set is used to sign the POST (`X-Orbit-Signature: sha256=<hex>`). Queue, inbox, and team targets produce no HTTP call — the assignment is published to the inbox layer directly. Each matched rule's `failureCount` and `lastFailureAt`/`lastMatchedAt` are tracked and listed via `GET /messages/inbound-routes` (webhook secrets are masked in list responses). Update a rule with `PATCH /messages/inbound-routes/:routeId`; delete with `DELETE /messages/inbound-routes/:routeId`.

## 4. Auto-reply with Flows

For canned inbound replies, prefer the `auto_reply` target type on an inbound SMS rule — the decoupling keeps reply logic in a config rule instead of a network hop. For anything more orchestrated (look up an order, branch on the customer's lifecycle stage, fan out to a channel other than the one they came in on), build a [Flow](/flows/overview) that reacts to the inbound message event and sends through the channel of your choice — the [Send & Receive Messages guide](/guides/send-receive-messages) shows how the receive → reply loop looks at the API level, and Flows externalizes that loop into the builder.

Inbound **email** rules do not have a reply path themselves — use an email channel [Flow](/flows/overview) on the inbound event to compose the response, or open a ticket (which your agents handle from the inbox).

## 5. Bounces and unsubscribes

Inbound workflows end in outbound mail, so the suppressions that matter are the ones on the outbound side:

* **Unsubscribe.** Every outbound email carries a one-click unsubscribe link (the `List-Unsubscribe` header plus the body link). A recipient click lands at a signed URL, suppression is recorded idempotently against that recipient, and an `email.unsubscribed` audit entry is appended. Suppressed recipients are skipped at send time. The recipient is redirected to your configured page, or shown an acknowledgement.
* **Bounce handling.** Hard bounces, spam complaints, and invalid addresses are tracked per message and surface with the standard [email events](/webhooks/events). Suppress addresses proactively from the [opt-out lists guide](/guides/opt-out-lists), and follow [send-best-practices](/guides/best-practices) to keep deliverability healthy.

The unsubscribe and inbound-parse surfaces are deliberately separate — inbound inbound (receiving) does not unsubscribe anyone; the outbound path does. Route bounces and unsubscribes into your suppression rules the same way you would register an inbound SMS keyword.

## Next steps

* [Send & Receive Messages](/guides/send-receive-messages) — the two-way loop across all channels
* [Webhook consumer guide](/guides/webhook-consumer) — verify forwards securely
* [Email attachments](/guides/email-attachments) — limits and MIME rules for outbound
* [Opt-out lists](/guides/opt-out-lists) — suppression management
* [Flows overview](/flows/overview) — auto-reply and richer orchestration
* [API Reference: inbound email routes](/api-reference), [inbound SMS routes](/api-reference)
