Skip to main content

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 and group sends through POST /messages/group): 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; the full field table stays in the MMS channel reference.

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 (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: 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; the transition table by the message status lifecycle reference.

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 recipientsWhatsApp or 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 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.