Skip to content

📱 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 mmsMessage payload on POST /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

  • mmsMessage is one of the mutually exclusive payload variants. Combining it with message, textMessage, or dataMessage is rejected with HTTP 400.
  • subject and text are optional; a valid MMS must specify a non-empty text or at least one attachment.
  • attachments is an optional array and is omitted from the JSON body entirely when empty. subject, text, and attachment name are omitted when null/empty.
  • Each attachment has contentType (required), data (required, base64-encoded content), and name (optional).
  • No MIME allowlist is enforced: any contentType is accepted, and the client is responsible for sending a type the carrier will accept (for example image/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 text or at least one attachment; a request with neither is rejected.
  • Each attachment requires contentType and base64-encoded data.
  • 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: Sent is the terminal success state for MMS — there is no Delivered state and no sms:delivered event 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 existing sms:sent, sms:failed, and sms:cancelled events. There are no mms:* outbound events and no server-side MMS webhook changes — register webhooks for sms:sent, sms:failed, and sms:cancelled to 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:downloaded uses the Android content provider row _id from content://mms, a per-device counter that restarts after a handset replacement.
  • mms:received and mms:downloaded report different messageId values 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:

Deduplication key
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:received Metadata Only: The arrival notification provides metadata only — no message text or attachment content is included
  • mms:downloaded Attachment Size: Large attachments may impact webhook payload size and delivery performance. The attachment size and data fields may be null if the content is unavailable
  • No Delivery Receipts: Android does not provide delivery receipts for MMS — Sent is the terminal success state and sms:delivered never 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