StartImplementation Guides

Sending MMS

MMS is a carrier messaging channel that delivers images, video, vCard contacts, and plain text over the same number and carrier infrastructure as SMS. This guide covers how to enable and send MMS using the Sent API.

Prerequisites

MMS requires per-account consent. The allowMms flag on your account must be enabled before any MMS message can be sent or routed. This is set by Sent: contact support@sent.dm to request it.

Without allowMms, the routing pipeline drops every MMS candidate and the channel is unavailable, regardless of whether your account has MMS-capable senders provisioned.

MMS is priced per message at a higher rate than SMS. Sent gates it per account to ensure you have explicitly agreed to the billing before any MMS traffic is routed.

Sending an MMS Message

MMS uses the same POST /v3/messages endpoint as every other channel. Set channel: ["mms"] to pin the send to MMS. Media can come from two places:

curl -X POST "https://api.sent.dm/v3/messages" \
  -H "x-api-key: $SENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": ["+1234567890"],
    "channel": ["mms"],
    "template": {
      "id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
      "parameters": {
        "customerName": "Jane"
      }
    }
  }'
import SentDm from '@sentdm/sentdm';

const client = new SentDm();

const response = await client.messages.send({
  to: ['+1234567890'],
  channel: ['mms'],
  template: {
    id: '7ba7b820-9dad-11d1-80b4-00c04fd430c8',
    parameters: { customerName: 'Jane' }
  }
});

console.log(`Message ID: ${response.data.recipients[0].message_id}`);
from sent_dm import SentDm

client = SentDm()

response = client.messages.send(
    to=["+1234567890"],
    channel=["mms"],
    template={
        "id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
        "parameters": {"customerName": "Jane"}
    }
)

print(f"Message ID: {response.data.recipients[0].message_id}")
import (
    "context"
    "github.com/sentdm/sent-dm-go"
)

client := sentdm.NewClient()

response, err := client.Messages.Send(context.Background(), sentdm.MessageSendParams{
    To:      []string{"+1234567890"},
    Channel: []string{"mms"},
    Template: sentdm.MessageSendParamsTemplate{
        ID: sentdm.String("7ba7b820-9dad-11d1-80b4-00c04fd430c8"),
        Parameters: map[string]interface{}{
            "customerName": "Jane",
        },
    },
})
import dm.sent.client.SentDmClient;
import dm.sent.client.okhttp.SentDmOkHttpClient;
import dm.sent.core.JsonValue;
import dm.sent.models.messages.MessageSendParams;

SentDmClient client = SentDmOkHttpClient.fromEnv();

MessageSendParams params = MessageSendParams.builder()
    .addTo("+1234567890")
    .addChannel("mms")
    .template(MessageSendParams.Template.builder()
        .id("7ba7b820-9dad-11d1-80b4-00c04fd430c8")
        .parameters(MessageSendParams.Template.Parameters.builder()
            .putAdditionalProperty("customerName", JsonValue.from("Jane"))
            .build())
        .build())
    .build();

var response = client.messages().send(params);
using Sentdm;
using Sentdm.Models.Messages;

SentDmClient client = new();

MessageSendParams parameters = new()
{
    To = new List<string> { "+1234567890" },
    Channel = new List<string> { "mms" },
    Template = new MessageSendParamsTemplate
    {
        Id = "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
        Parameters = new Dictionary<string, string>
        {
            { "customerName", "Jane" }
        }
    }
};

var response = await client.Messages.Send(parameters);

Two optional fields on POST /v3/messages carry MMS content:

FieldDescription
media_urlsArray holding one https URL to attach. MMS carries one attachment, so a second is rejected with 400. Replaces the template's own media rather than adding to it.
subjectSubject line shown above the body on most handsets. Maximum 80 characters.

When neither field is supplied, the template's own MMS body provides the media. When media_urls is supplied, it overrides whatever the template carries, so a template can hold a default creative while a caller sends something recipient-specific. A template saved with more than one attachment before the one-attachment limit still sends, carrying only its first.

Each media URL must be an absolute https URL on a hostname. A literal IP address is refused with 400.

Supported Media Types

These are the formats Sent supports on an outbound MMS:

FormatmediaType to declare
JPEG, PNG, GIFimage
MP4, 3GPvideo
vCard contact cardscontact
Plain textOmit the field

The carrier fetches the media from the URL at delivery time and reads the content type off the object it fetched. Keep media files publicly accessible and hosted on a stable URL.

mediaType is a label, not a guarantee. The API accepts four values (image, video, document, contact) and rejects audio when a template is saved. It does not check the label against the file: Sent reads only the size your server declares for it (see Media Size Limits), never its content. Declaring document on a PDF is accepted at send time, but PDF is not in the preceding set, and the failure surfaces at the carrier long after Sent answered 202. Send only the formats listed.

The field is optional, so omit it when none of the four describes your file. It exists so an authoring UI can preview the attachment, not to select a delivery path.

Media Size Limits

An attachment must be 600 KB or smaller. That is the size the carrier network delivers end to end. Over it, a carrier can accept and charge for a message and then discard the attachment before it reaches the handset.

Sent checks the size on the send itself, after your 202. It sends one HEAD request to the media URL and reads the Content-Length your server declares. It does not download the file.

Result of the checkAuto-detectchannel: ["mms"]
600 KB or smallerSent as MMSSent as MMS
Over 600 KBFalls to the SMS route on the same number, with the attachment as a link when it fits (see Auto-Detect Routing and MMS)Fails
Size unknown: no Content-Length, a non-2xx response, or an unreachable serverSent as MMSSent as MMS

An unknown size never blocks a send, because a check that learned nothing about the file is no reason to fail it. The carrier still applies its own limits to that message after acceptance. Host media on a server that answers HEAD with a Content-Length, as most CDNs and object stores do, so the check can protect the send.

An oversized attachment produces no validation error on the request. The send is accepted, and the outcome arrives later as a delivery status, or on auto-detect as an SMS.

The ceiling a carrier delivers intact depends on its tier:

CarriersDelivered intact up to
Tier 2 carriers600 KB
Tier 3 carriers300 KB

Images and video over a carrier's ceiling are resized automatically on the way out. Resizing is lossy, so treat it as a safety net rather than a workflow, and design for 300 KB if you need tier 3 carriers to deliver the attachment intact.

Auto-Detect Routing and MMS

When you omit channel, auto-detect selects the best-matching route per recipient based on your routing rules. MMS routes sit above SMS routes in priority for the same sender number, so a message with media prefers MMS over SMS when both are available. RCS and WhatsApp routes typically outrank MMS when the recipient is reachable there. The exact channel always depends on which routes are configured for your account and the recipient.

MMS only enters auto-detect consideration when the send carries an attachment. A text-only send never produces an MMS candidate, regardless of routing rules or allowMms status: the pipeline drops MMS candidates without media before routing resolves. This prevents a plain-text message from being billed at MMS rates.

Attachments reach the send from either the template's mms body (see Template Definition) or the per-send media_urls field. If neither provides media, auto-detect skips MMS and falls to SMS.

When a message with an attachment falls to SMS, including when its MMS candidate fails after routing chose it (for example, an attachment over 600 KB), the attachment URL is appended to the text as a link. The link is added only when it fits without adding a billed segment. Otherwise the SMS carries the text alone.

Pinned vs. Auto-Detect

channel: ["mms"] (pinned)channel omitted (auto-detect)
Falls back if no MMS routeNo (fails with ERR_NO_ROUTE_MATCHED)Yes (falls to SMS)
Requires mediaNo (media-less sends allowed, delivered as text MMS)MMS only fires when message has media
allowMms requiredYesYes
Attachment over 600 KBFailsFalls to SMS, with the attachment as a link when it fits

Pinning channel: ["mms"] without an attachment is valid. The carrier delivers it as a text-only MMS, which renders differently from SMS on the handset. If you pin MMS and the recipient has no MMS-capable route, the message fails rather than falling back.

MMS Is Not Approved by Meta

MMS content is approved by Sent compliance only. A template approved via a Meta WhatsApp verdict does not extend approval to the MMS channel: MMS has its own content, including media that Meta has never seen. Your MMS template must go through Sent's compliance review separately.

Quiet Hours

MMS follows the same quiet-hours rules as SMS. Sends scheduled inside a recipient's legally protected quiet-hours window are deferred rather than failed.

What allowMms Controls

allowMms is the only per-customer gate. It controls whether the pipeline routes any message over MMS: it is not inferred from whether your account has MMS-capable senders provisioned. The two are kept separate so a market can be prepared before any customer is billed for it.

If you send to a recipient while allowMms is off, the MMS candidate is refused with channel_unavailable and, on auto-detect, the message falls to the next channel. On a pinned channel: ["mms"] send, the message fails.

Delivery Reports

MMS delivery reports arrive on the same webhook you configure for other channels. The message moves through the normal lifecycle (QUEUED → ROUTED → SENT → DELIVERED / FAILED) and fires the same message.delivered and message.failed events.

Inbound MMS

Sent identifies an inbound MMS as one: the message is recorded with channel: "mms" and relayed to your webhook as an ordinary message.received event, including when it carries no caption.

Attachments arrive as links to the carrier's copy. message.received carries a media array with one entry per attachment. Sent records where the carrier holds each file; it does not download or store the file itself.

{
  "field": "message",
  "event": "message.received",
  "timestamp": "2025-10-31T10:10:42Z",
  "payload": {
    "message_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
    "updated_at": "2025-10-31T10:10:40Z",
    "account_id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
    "inbound_number": "+1234567890",
    "outbound_number": "+1987654321",
    "text": null,
    "channel": "mms",
    "media": [
      {
        "url": "https://media.example-carrier.com/7f3c2a9e/photo.jpg",
        "mime_type": "image/jpeg",
        "size_bytes": 245760,
        "hash_sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
      }
    ],
    "received_at": "2025-10-31T10:10:40Z"
  }
}
FieldDescription
urlWhere the carrier hosts the file. Always present
mime_typeThe content type the carrier reported, for example image/jpeg. Omitted when the carrier declared none
size_bytesThe size the carrier declared. Omitted when it declared none
hash_sha256The SHA-256 the carrier declared for the file. Omitted when it declared none

Only url is guaranteed. Some carriers declare the type, size, and hash; others send only the address. Sent never guesses a missing value, so read the type from the response when you fetch the file. Only https links are relayed.

Download attachments when they arrive. The url is the carrier's own link. It needs no authentication, so anyone holding it can fetch the file, and it expires on the carrier's schedule, which varies: assume days, not months. The webhook is the only place Sent gives you the link, and Sent can't fetch the file once the link lapses. Copy anything you need to keep into your own storage, and verify it against hash_sha256 when that's present.

media is omitted when a message carries no attachments, so an SMS payload keeps exactly the shape it has always had. A media-only MMS arrives with text: null; treat that as a normal value rather than an empty message.

Next Steps

On this page