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

# MMS composition model

> What an MMS message is composed of — body plus attachments — how every attachment moves through a three-step media pipeline (SSRF/HTTPS preflight, carrier-compatibility policy, optional re-encode) before dispatch, how the NANP and media-type rejection taxonomy is structured, and where MMS sits in the shared delivery lifecycle as an SMPP-backed channel.

# MMS composition model

Orbit MMS is not a separate messaging engine — it is an extension of the send pipeline that adds an **attachment pipeline** around the ordinary message body. This page owns the concept behind both MMS surfaces ([single sends through `POST /messages/sms`](/channels/sms) and [group sends through `POST /messages/group`](/channels/mms)): how a message is composed, how each attachment is validated and optionally re-encoded before dispatch, and how to read the rejection taxonomy. The step-by-step send recipe stays in the [API recipes cookbook](/guides/api-recipes#recipe-c-mms-send-one-multimedia-message-with-attachments); the full field table stays in the [MMS channel reference](/channels/mms).

## Composition shapes

An MMS carries any of three compositions, and at least one of the two ingredients must be present:

* **Body + media** — the ordinary shape: text plus up to **10 attachment URLs**.
* **Media-only** — no body. The send upgrades to MMS purely because an attachment exists.
* **Group envelope** — one payload (body, media, or both) fanned out to up to **20 recipients** in a single call, with per-recipient rows so individual pre-send rejections never drop a recipient silently.

There is no single-MMS endpoint. `POST /messages/sms` auto-upgrades the send to MMS the moment `media_url` or a non-empty `media_urls` array is present, and the response reports `channel: "mms"`. Treat MMS composition, therefore, as something the pipeline *derives* from the presence of attachments — not a channel you select.

## The attachment pipeline

Every attachment URL passes through the same three-step pipeline before a carrier ever sees it:

1. **URL gates (fail fast).** Each URL must be **HTTPS** and must resolve through the send-time SSRF allowlist. URLs that point at internal addresses, fail the allowlist, or break the DNS-rebind check are rejected before dispatch, and no carrier call is attempted.
2. **HEAD-preflight assessment.** Orbit issues an HTTPS `HEAD` request against the URL and classifies it from the cheap `Content-Type` + `Content-Length` signals into one of three carrier outcomes:
   * `ok` — a carrier-native type (JPEG, PNG, GIF, WebP images; MP4/3GPP video; MPEG/OGG/AAC audio) under the recommended soft cap of **1 MB**. Sent as-is.
   * `optimize` — a carrier-native type between the 1 MB soft cap and the **5 MB hard ceiling**. The send is accepted, and an advisory is stamped onto the message metadata flagging which attachments a carrier may downscale or drop.
   * `reject` — un-sendable as-is: either a content type no carrier MMSC accepts, or a payload over 5 MB. The rejection names the carrier-native format to convert to, so the fix is concrete rather than a flat allowed-types dump.
3. **Optional re-encode.** Non-canonical image types (HEIC, HEIF, BMP, TIFF) and oversized images can be re-encoded to a carrier-native image before dispatch instead of being rejected — either inline on the send path or ahead of time through the asynchronous `POST /messages/media/convert` endpoint (poll `GET /messages/media/convert/:jobId` until `completed`, then send with the returned URL). Images only: video and audio conversion is not supported, and SVG is never rasterized server-side. The re-encode changes bytes, never transport — outbound MMS still exits via the named Telnyx exception to Orbit's outbound-termination policy, exactly as documents (PDFs) or contact cards (vCards) remain out of scope entirely.

A vocabulary note: this attachment pipeline is unrelated to the realtime-call [media planes](/concepts/media-planes) (the SFU versus carrier-plane split for voice and video). The word *media* is doing double duty on those pages; the MMS pipeline is only about message attachments.

## The rejection taxonomy

MMS failures cluster into a small, decision-grade taxonomy. Branch on the error class, not the message text:

| Class         | Code                               | Meaning                                                                                        | Correct response                                                                                                                                  |
| ------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| Coverage      | `MMS_NANP_ONLY`                    | Recipient is not a US/Canada `+1` number                                                       | Route picture/video content to that recipient over [WhatsApp](/channels/whatsapp) or [RCS](/channels/rcs) — a routing hint, not a retryable error |
| Media type    | `unsupported_type`                 | Carrier MMSC rejects the attachment's content type                                             | Re-encode to the named carrier-native target (or send an HTTPS link in the body)                                                                  |
| Size          | `oversized`                        | Attachment over the 5 MB hard ceiling                                                          | Downscale below the 1 MB soft cap — or let the pipeline re-encode an image                                                                        |
| URL           | SSRF / allowlist / HTTPS rejection | Unsafe or non-HTTPS attachment URL                                                             | Serve the file from an allowlisted HTTPS origin                                                                                                   |
| Payload shape | `422` (schema)                     | Empty `to` array, duplicates in a group send, more than 10 attachments, neither body nor media | Fix the request — these are rejected at validation, before any queueing                                                                           |

Two invariants make this taxonomy safe to build on: a rejected MMS **never silently falls back to SMS as text-only**, and group fan-out **never aborts the whole batch on one bad recipient** — a failure lands on that recipient's row alone.

## Group MMS shape

Group MMS is a composition-time fan-out, not a campaign. The caps are enforced in one step:

* **20 recipients** maximum per call (duplicates rejected at `422`). Beyond 20, chunk or move to the campaign or batch surfaces.
* **Per-recipient rows.** Every recipient is pre-inserted as a `pending` row **before** the per-recipient pipeline runs, so a pre-send rejection (coverage, media, quota) flips that one row to `failed` instead of dropping it.
* **Per-leg idempotency.** Recipients dedupe under the key `group-mms:{group_id}:{to}`, so a retried group call replays cleanly instead of double-sending.
* **Status shape.** `200` when every recipient succeeded; `207` with a per-recipient `messages` array when any failed. Read `error_code` per recipient rather than relying on the envelope status.

## Where MMS sits in the delivery lifecycle

Once an attachment passes the pipeline, the message joins the shared destination lifecycle every messaging channel uses — the same state machine, the same webhook vocabulary, the same debugging surfaces. MMS is an **SMPP-backed** channel, so the lifecycle details that matter most to reporting are the SMPP-backed ones:

* The no-receipt grace window is **30 minutes**. Thirty minutes after a provider accept, a message with no delivery receipt moves from `sent` to `submitted_no_receipt`.
* `submitted_no_receipt` for MMS is **genuinely ambiguous** — the handset may have received the media with no receipt reported, or the carrier may never emit receipts on that route. It is a wire-intermediate state (`is_terminal: false`), not a delivery signal. Track it separately from your delivered rate; a rising share on one destination points at a non-cooperating route.
* Telnyx returns carrier-style receipts for MMS legs, so a `delivered` state is a carrier-confirmed outcome exactly as it is for SMS.

Group MMS attaches **one lifecycle stream per recipient**. A 20-recipient call produces 20 webhook event streams, each carrying its own message `id` — diagnose them per recipient, not as a batch. The state-machine concept is owned by [Delivery lifecycle](/concepts/delivery-lifecycle); the transition table by the [message status lifecycle reference](/api-reference/messages-status-lifecycle).

## Choosing MMS — and its buddy channels

MMS is the right surface for inline image, video, or audio to US/Canada mobile numbers. When a recipient sits outside NANP, or the content is a document type no MMSC accepts, MMS's rejection is a routing decision input — pick the buddy channel and move on:

* **Non-NANP picture/video recipients** — [WhatsApp](/channels/whatsapp) or [RCS](/channels/rcs) carry rich content internationally.
* **Documents and contact cards** — link an HTTPS-hosted file in the SMS or WhatsApp body rather than trying to attach it.

Because MMS hops into one of the closed cascade-eligible channel set (`sms`, `whatsapp`, `rcs`, `viber`, `telegram`, `messenger`, `instagram`, `line`, `apple_messages`), any of the five [fallback and cascade planes](/concepts/fallback-and-cascade-planes) can move a rejected or undeliverable MMS onto a buddy channel — from an org-wide chain to a per-send `fallback_channels` field. MMS itself is a first-class internal channel surface, but the fallback machinery treats the hop as a fresh send on the buddy channel with the same consent, quiet-hours, and billing gates every hop of every plane re-runs.

## Related

* [MMS channel](/channels/mms) — endpoint reference, media table, caps, rate limits
* [MMS recipe](/guides/api-recipes#recipe-c-mms-send-one-multimedia-message-with-attachments) — the send → webhook → error-branch flow, end to end
* [Media planes](/concepts/media-planes) — the realtime voice/video planes (not the attachment pipeline)
* [Fallback and cascade planes](/concepts/fallback-and-cascade-planes) — moving a rejected MMS onto a buddy channel
* [Delivery lifecycle](/concepts/delivery-lifecycle) — the shared status machine and the SMPP-backed 30-minute window
* [SMS channel](/channels/sms) — the single-send surface MMS auto-upgrades from
