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:
| Field | Description |
|---|---|
media_urls | Array of https URLs to attach, up to 10. Replaces the template's own media rather than adding to it. |
subject | Subject 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.
Supported Media Types
These are the formats Sent supports on an outbound MMS:
| Format | mediaType to declare |
|---|---|
| JPEG, PNG, GIF | image |
| MP4, 3GP | video |
| vCard contact cards | contact |
| Plain text | Omit 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 five values (image, video, audio, document, contact) and does not check any of them against the file, because Sent never fetches the URL: doing so would put a third-party call in front of your 202. Declaring audio on an MP3 or document on a PDF is accepted at send time, but neither is 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 five describes your file. It exists so an authoring UI can preview the attachment, not to select a delivery path.
Media Size Limits
Sent enforces no size limit of its own. The only attachment rules applied before your 202 are the count (up to 10), the URL shape (absolute https), and the caps on subject and body text. Size is the carrier's to enforce, and it does so after the message is accepted.
Budget for these ceilings:
| Ceiling | Total message size |
|---|---|
| Starting ceiling | 1 MB |
| Tier 2 carriers | 600 KB |
| Tier 3 carriers | 300 KB |
The starting ceiling is 1 MB, and tier 2 and tier 3 carriers cut it further, so the size that actually applies is a property of the recipient's carrier rather than one figure you can design against for everyone.
Oversized images and video are resized automatically on the way out, so a file over the ceiling is not necessarily a failed send. Resizing is lossy, so treat it as a safety net rather than a workflow.
Design for 600 KB. That keeps a message inside the common carrier ceiling without relying on the resize, and 300 KB is the figure to hit if you need tier 3 carriers to deliver the attachment intact.
Because Sent does not check size, an attachment that is too large produces no validation error. The send is accepted, and the outcome arrives later as a delivery status rather than as a rejected request.
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.
Pinned vs. Auto-Detect
channel: ["mms"] (pinned) | channel omitted (auto-detect) | |
|---|---|---|
| Falls back if no MMS route | No (fails with ERR_NO_ROUTE_MATCHED) | Yes (falls to SMS) |
| Requires media | No (media-less sends allowed, delivered as text MMS) | MMS only fires when message has media |
allowMms required | Yes | Yes |
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.
The attachments are not relayed. message.received has no field for media, so an inbound MMS reaches you as the message and its text alone, and the images or video the contact sent are not forwarded and not stored. A media-only inbound arrives with text: null, which is the only signal on the payload that something came with it.
{
"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",
"received_at": "2025-10-31T10:10:40Z"
}
}Build the handler around that now: read channel to know an MMS arrived, and treat text: null as a normal value rather than an error. Do not write a handler that waits for an attachment URL, and do not treat a null caption as an empty message.
Taking custody of inbound media is planned. Until it ships, a workflow that depends on receiving what a contact sends needs a channel other than MMS.