📱 MMS Support (Multimedia Messaging Service)
The SMSGate application supports both sending and receiving MMS messages. You can send multimedia content via the API and receive incoming MMS messages through webhook notifications.
📋 Overview
MMS (Multimedia Messaging Service) support enables your SMSGate to:
- Send multimedia messages (images, videos, audio, other attachments) via the Messages API by using the
mmsMessagepayload onPOST /3rdparty/v1/messages. - Receive and process incoming multimedia messages via webhook notifications.
Two distinct MMS webhook events fire at different stages of incoming message delivery:
mms:received— Fires when the MMS notification (WAP push) arrives, before the message is downloaded. Provides metadata only (transaction ID, subject, size, content class).mms:downloaded— Fires when the MMS has been fully downloaded to the device. Includes the message body, subject, and attachments with base64-encoded content.
Outgoing MMS delivery status is reported through the existing outbound SMS webhook events (sms:sent, sms:failed, sms:cancelled) — see Delivery Status and Webhooks.
📨 Sending MMS Messages
You can send MMS messages via the POST /3rdparty/v1/messages endpoint by including an mmsMessage field instead of textMessage. The request envelope must contain exactly one of message, textMessage, dataMessage, or mmsMessage.
Request Structure
{
"phoneNumbers": ["+1234567890"],
"mmsMessage": {
"subject": "Hello",
"text": "World",
"attachments": [
{
"contentType": "image/png",
"name": "picture.png",
"data": "BASE64DATA"
}
]
}
}
Wire Format Rules
mmsMessageis one of the mutually exclusive payload variants. Combining it withmessage,textMessage, ordataMessageis rejected with HTTP 400.subjectandtextare optional; a valid MMS must specify a non-emptytextor at least one attachment.attachmentsis an optional array and is omitted from the JSON body entirely when empty.subject,text, and attachmentnameare omitted when null/empty.- Each attachment has
contentType(required),data(required, base64-encoded content), andname(optional). - No MIME allowlist is enforced: any
contentTypeis accepted, and the client is responsible for sending a type the carrier will accept (for exampleimage/png,image/jpeg,video/mp4,audio/mpeg). - No size limit is enforced server-side at this time. Real-world limits come from your mobile carrier (typically 300KB-1MB per MMS) — keep attachments within carrier bounds.
Fields
| Field | Type | Required | Description |
|---|---|---|---|
subject | string | No | MMS subject line |
text | string | No | Text body of the MMS (non-empty text or >= 1 attachment required) |
attachments | array | No | Array of attachment objects (omitted when empty) |
Each attachment object:
| Field | Type | Required | Description |
|---|---|---|---|
contentType | string | Yes | MIME type (e.g. image/png, video/mp4) |
data | string | Yes | Base64-encoded content |
name | string | No | Filename |
Validation
- A valid MMS contains a non-empty
textor at least one attachment; a request with neither is rejected. - Each attachment requires
contentTypeand base64-encodeddata. - The base64 content is decoded on the device, which composes the MMS and submits it to the carrier.
Example (cURL)
curl -X POST -u <username>:<password> \
-H "Content-Type: application/json" \
-d '{
"phoneNumbers": ["+1234567890"],
"mmsMessage": {
"subject": "Hello",
"text": "World",
"attachments": [
{
"contentType": "image/png",
"name": "picture.png",
"data": "BASE64DATA"
}
]
}
}' \
https://api.sms-gate.app/3rdparty/v1/messages
💻 SDK Examples
Outbound MMS is supported by all five official SDKs. Each example below produces the same wire payload as the request structure above.
// import "github.com/android-sms-gateway/client-go/smsgateway"
subject := "Hello"
text := "World"
name := "picture.png"
msg := smsgateway.Message{
PhoneNumbers: []string{"+1234567890"},
MmsMessage: &smsgateway.MmsMessage{
Subject: &subject,
Text: &text,
Attachments: []smsgateway.MmsAttachment{
{
ContentType: "image/png",
Name: &name,
Data: "BASE64DATA",
},
},
},
}
state, err := client.Send(ctx, msg)
JSON marshaling omits subject, text, name, and attachments when they are unset, mirroring the wire rules above.
import Client, { MmsMessagePayload, MmsAttachment } from 'android-sms-gateway';
const client = new Client('username', 'password');
const attachment: MmsAttachment = {
contentType: 'image/png',
name: 'picture.png',
data: 'BASE64DATA',
};
const mms: MmsMessagePayload = {
subject: 'Hello',
text: 'World',
attachments: [attachment],
};
const state = await client.send({ phoneNumbers: ['+1234567890'], mmsMessage: mms });
The Message type is a discriminated union: exactly one of message, textMessage, dataMessage, or mmsMessage is accepted, and mixing them is a compile-time error.
use AndroidSmsGateway\Client;
use AndroidSmsGateway\Domain\MessageBuilder;
use AndroidSmsGateway\Domain\MmsAttachment;
use AndroidSmsGateway\Domain\MmsMessage;
$client = new Client('username', 'password');
$mms = new MmsMessage('Hello', 'World', [
new MmsAttachment('image/png', 'BASE64DATA', 'picture.png'),
]);
$message = (new MessageBuilder($mms, ['+1234567890']))->build();
$state = $client->SendMessage($message);
MessageBuilder accepts either a string or an MmsMessage as its message argument. Calling setMessage() again replaces the previous value. build() raises an InvalidArgumentException when both ttl and validUntil are set.
from android_sms_gateway import APIClient
from android_sms_gateway.domain import Message, MmsAttachment, MmsMessage
with APIClient("username", "password") as client:
message = Message(
phone_numbers=["+1234567890"],
mms_message=MmsMessage(
subject="Hello",
text="World",
attachments=[
MmsAttachment(
content_type="image/png",
data="BASE64DATA",
name="picture.png",
),
],
),
)
state = client.send(message)
use android_sms_gateway::{
Client, ClientConfig,
types::{MmsAttachment, MmsMessage, Message, SendOptions},
};
let client = Client::new(ClientConfig::new().with_basic_auth("username", "password"))?;
let message = Message {
phone_numbers: vec!["+1234567890".into()],
mms_message: Some(MmsMessage {
subject: Some("Hello".into()),
text: Some("World".into()),
attachments: vec![MmsAttachment {
content_type: "image/png".into(),
name: Some("picture.png".into()),
data: "BASE64DATA".into(),
}],
}),
..Default::default()
};
let state = client.send(&message, &SendOptions::new()).await?;
📊 Delivery Status and Webhooks
Outgoing MMS messages follow the same message lifecycle as SMS messages: they are enqueued with POST /3rdparty/v1/messages, tracked via GET /3rdparty/v1/messages/{id}, and can be cancelled with DELETE /3rdparty/v1/messages/{id} while pending.
Status reporting for outbound MMS differs from SMS in two important ways:
- No delivery receipts on Android:
Sentis the terminal success state for MMS — there is noDeliveredstate and nosms:deliveredevent for MMS messages. - Status arrives via the existing
sms:*events: the Android app reports outbound MMS results directly with the standard, fixed payloads of the existingsms:sent,sms:failed, andsms:cancelledevents. There are nomms:*outbound events and no server-side MMS webhook changes — register webhooks forsms:sent,sms:failed, andsms:cancelledto track outgoing MMS.
Incoming MMS events (mms:received, mms:downloaded) are unchanged.
Attachment Data Is Not Downloadable
There is no server endpoint to download attachments of outgoing MMS messages. Keep a copy of anything you send if you need it later; the server only stores the submitted base64 payload for delivery to the device.
🚀 Prerequisites
The following permissions must be granted to the SMSGate application to enable MMS support:
- RECEIVE_SMS: Required for receiving SMS messages
- RECEIVE_MMS: Required for MMS functionality - enables the app to receive multimedia messages
- READ_PHONE_STATE: Optional, for SIM card information
📊 MMS Webhook Events
mms:received — Arrival Notification
Triggered when the device receives a WAP push notification for an incoming MMS. This event fires before the message content is downloaded and contains metadata only.
{
"deviceId": "ffffffffceb0b1db0000018e937c815b",
"event": "mms:received",
"id": "Ey6ECgOkVVFjz3CL48B8C",
"payload": {
"messageId": "mms_12345abcde",
"sender": "+1234567891",
"recipient": "+1234567890",
"simNumber": 1,
"transactionId": "T1234567890ABC",
"subject": "Photo attachment",
"size": 125684,
"contentClass": "IMAGE_BASIC",
"receivedAt": "2025-08-23T05:15:30.000+07:00"
},
"webhookId": "<unique-id>"
}
Field descriptions can be found in the Webhook Supported Events section.
mms:downloaded — Full Content
Triggered when the MMS content has been fully downloaded to the device content provider. This event fires after mms:received and includes the message body and attachments.
{
"deviceId": "ffffffffceb0b1db0000018e937c815b",
"event": "mms:downloaded",
"id": "Ey6ECgOkVVFjz3CL48B8C",
"payload": {
"messageId": "mms_67890fghij",
"sender": "+1234567891",
"recipient": "+1234567890",
"simNumber": 1,
"body": "Hello! Here is the photo.",
"subject": "Photo attachment",
"attachments": [
{
"partId": 1,
"contentType": "image/jpeg",
"name": "photo.jpg",
"size": 125684,
"data": "/9j/4AAQ..."
}
],
"receivedAt": "2025-08-23T05:15:35.000+07:00"
},
"webhookId": "<unique-id>"
}
Field descriptions can be found in the Webhook Supported Events section.
Attachment Data
The data field in each attachment contains the raw content encoded in Base64 when available and may be null if the content is unavailable. This enables programmatic processing — for example, decoding and saving images, or forwarding attachments to other services.
Deduplicating Incoming MMS
Neither MMS event carries a globally unique messageId, so messageId on its own is not a safe deduplication key:
mms:downloadeduses the Android content provider row_idfromcontent://mms, a per-device counter that restarts after a handset replacement.mms:receivedandmms:downloadedreport differentmessageIdvalues for the same message, so you cannot correlate the two events by ID.
Always namespace the key by deviceId, which is present in every webhook payload:
def dedup_key(envelope: dict) -> str:
"""Stable across retries; deviceId avoids collisions after a device swap."""
payload = envelope["payload"]
return f'{envelope["deviceId"]}:{envelope["event"]}:{payload["messageId"]}'
The webhook envelope (including deviceId and the envelope id) is built once and reused verbatim for every delivery attempt, so this key suppresses retries while remaining valid if the handset is replaced.
Deduplicating on messageId Alone
Consumers that key on messageId silently drop every MMS after a handset swap, because the replacement device starts reissuing the same low content://mms row IDs.
See also the MMS messageId warning in the webhook events reference.
🚫 Limitations
mms:receivedMetadata Only: The arrival notification provides metadata only — no message text or attachment content is includedmms:downloadedAttachment Size: Large attachments may impact webhook payload size and delivery performance. The attachmentsizeanddatafields may benullif the content is unavailable- No Delivery Receipts: Android does not provide delivery receipts for MMS —
Sentis the terminal success state andsms:deliverednever fires - Outgoing Attachments Not Downloadable: The server exposes no endpoint to download attachments of outgoing messages
- Carrier Dependencies: Functionality varies by mobile carrier and network conditions
📚 See Also
- Sending Messages - API payload reference for all message types
- Webhook Configuration - Complete webhook setup guide
- Status Tracking - Message lifecycle and status monitoring
- API Documentation - Messages API reference
- Webhook Troubleshooting - Webhook-specific issues