{"slug":"mms-handling","title":"MMS Handling — Multimedia Messaging on SignalWire","tags":["mms","sms","messaging","signalwire","media","carrier"],"agent_summary":"MMS (Multimedia Messaging Service) sends images, audio, video, and vCards alongside text. Max file size 5 MB on most US carriers. Supported MIME types: image/jpeg, image/png, image/gif, audio/mpeg, video/mp4. Carriers transcode aggressively. Send via SignalWire LaML with the `MediaUrl` parameter or via REST `/Messages` with `MediaUrl[]`. Subject to the same A2P 10DLC campaign registration as SMS.","trigger_phrases":["send MMS SignalWire","MMS media attachment","image text message","MMS file size limit","multimedia messaging","MediaUrl SignalWire","MMS not delivering"],"runnable":true,"markdown":"\n# MMS Handling\n\nMMS (Multimedia Messaging Service) is the protocol for sending images, audio, video, and other attachments via the carrier messaging network. From a SignalWire developer's perspective MMS is sent the same way as SMS — you just add a `MediaUrl` parameter. Underneath, carrier transcoding, file-size limits, and message ID handling are noticeably different.\n\n## When to use MMS vs SMS\n\n- **Use SMS** for text-only confirmations, OTP codes, reminders, opt-in/opt-out flows.\n- **Use MMS** for receipts, before/after photos, product images, vCards, short voice clips, signed agreements (PDF rendered as image), promotional graphics.\n- **Use a link to a hosted asset** when the asset is larger than 1 MB, when delivery reliability matters more than inline preview, or when you need analytics on view rates.\n\n## File size and format limits\n\nCarrier limits dominate. SignalWire's outbound limit is 5 MB, but most US carriers reject anything above 1.2 MB. Practical safe target: **600 KB**.\n\n| Carrier | Practical inbound limit | Practical outbound limit |\n|---|---|---|\n| Verizon | 1.2 MB | 600 KB |\n| AT&T | 1.0 MB | 600 KB |\n| T-Mobile | 1.5 MB | 800 KB |\n| US Cellular | 1.0 MB | 500 KB |\n| iMessage (Apple) | 100 MB | 100 MB (but only between iMessage users) |\n\nWhen in doubt, send 600 KB or less. Anything over is transcoded down to 600 KB or rejected.\n\n## Supported MIME types\n\n| Type | MIME | Notes |\n|---|---|---|\n| Image | `image/jpeg`, `image/png`, `image/gif` | GIFs animate on most modern phones |\n| Audio | `audio/mpeg`, `audio/mp4`, `audio/wav` | Often transcoded to AMR |\n| Video | `video/mp4`, `video/3gpp` | Often transcoded to 320x240 |\n| Contact | `text/x-vcard`, `text/vcard` | Renders as \"Add contact\" button on most clients |\n| SMIL | `application/smil` | Slideshow layout — rare, mostly legacy |\n\nPDFs are not natively supported in MMS. Render the first page as PNG and send that, or send a link.\n\n## Sending MMS via SignalWire LaML\n\n```python\nimport requests\n\nPROJECT = \"your-project-id\"\nTOKEN = \"your-auth-token\"\nSPACE = \"your-space.signalwire.com\"\n\nresponse = requests.post(\n    f\"https://{SPACE}/api/laml/2010-04-01/Accounts/{PROJECT}/Messages.json\",\n    auth=(PROJECT, TOKEN),\n    data={\n        \"From\": \"+12125551111\",\n        \"To\": \"+13105552222\",\n        \"Body\": \"Here's the photo you requested.\",\n        \"MediaUrl\": \"https://cdn.example.com/photo.jpg\",\n    },\n)\n```\n\nFor multiple media items, pass `MediaUrl` repeatedly:\n\n```python\ndata = [\n    (\"From\", \"+12125551111\"),\n    (\"To\", \"+13105552222\"),\n    (\"Body\", \"Before and after\"),\n    (\"MediaUrl\", \"https://cdn.example.com/before.jpg\"),\n    (\"MediaUrl\", \"https://cdn.example.com/after.jpg\"),\n]\n```\n\nMax 10 media items per message on SignalWire. Most carriers cap at 4 displayed inline; the rest may render as attachments.\n\n## Sending MMS via REST\n\nThe same parameters work on the SignalWire REST API:\n\n```bash\ncurl -X POST https://{space}/api/laml/2010-04-01/Accounts/{project}/Messages.json \\\n  -u \"${PROJECT}:${TOKEN}\" \\\n  -d \"From=+12125551111\" \\\n  -d \"To=+13105552222\" \\\n  -d \"Body=Here's the photo\" \\\n  -d \"MediaUrl=https://cdn.example.com/photo.jpg\"\n```\n\n## Receiving MMS\n\nWebhook URL configured on the phone number fires for both SMS and MMS. The webhook receives:\n\n| Parameter | Type | Notes |\n|---|---|---|\n| `From`, `To`, `Body` | string | Same as SMS |\n| `NumMedia` | int | Number of media attachments (0 for text-only SMS) |\n| `MediaUrl0`, `MediaUrl1`, ... | URL | Signed URLs for each attachment, expire after 24 hours |\n| `MediaContentType0`, ... | MIME | Content type of each attachment |\n\nFetch each MediaUrl with the SignalWire credentials and move the asset to your own storage immediately — the signed URLs expire.\n\n```python\nimport requests\n\nnum_media = int(form[\"NumMedia\"])\nfor i in range(num_media):\n    url = form[f\"MediaUrl{i}\"]\n    content_type = form[f\"MediaContentType{i}\"]\n    response = requests.get(url, auth=(PROJECT, TOKEN))\n    # Save response.content to your own storage\n```\n\n## A2P 10DLC and MMS\n\nMMS sent to US numbers requires the same A2P 10DLC campaign registration as SMS. The campaign use case applies — there is no separate \"MMS campaign\" tier. Throughput limits include MMS in the messages-per-second budget.\n\nToll-free MMS does not require TCR registration but is subject to Toll-Free Verification (TFV). Unverified toll-free MMS is heavily rate-limited and may be blocked entirely on T-Mobile.\n\n## Cost model\n\nMMS costs more than SMS per segment:\n\n| Channel | SMS outbound | MMS outbound |\n|---|---|---|\n| 10DLC | ~$0.0079 / segment | ~$0.0200 / message |\n| Toll-free | ~$0.0090 / segment | ~$0.0225 / message |\n| Short code | ~$0.0049 / segment | ~$0.0150 / message |\n\nPricing accurate as of 2025-11. Check the current SignalWire pricing page for live rates. SMS is billed per 160-character segment; MMS is billed per message regardless of size.\n\n## Common failure modes\n\n| Symptom | Cause | Fix |\n|---|---|---|\n| Image arrives blurry or pixelated | Carrier transcoded to low resolution | Pre-size to 640×480 max, JPEG quality 80% |\n| GIF arrives as still image | Carrier dropped animation frames | Use very short GIFs (under 10 frames, 2 seconds) |\n| MMS not delivering to some carriers | File over 1 MB | Compress to 600 KB |\n| MMS delivers as SMS with link | Recipient on a non-MMS plan or feature phone | Fall back to SMS + link automatically |\n| Inbound MMS missing | Webhook only checking `Body`, not `NumMedia` | Read `NumMedia` and iterate MediaUrl* |\n| Recipient on iPhone shows green bubble | RCS/iMessage not engaged, MMS fell through | Expected — MMS is the green-bubble path |\n\n## Group MMS\n\nSending to multiple recipients with a shared media attachment creates a group MMS. Most carriers support up to 10 recipients per group MMS. Replies go to all participants.\n\n```python\ndata = {\n    \"From\": \"+12125551111\",\n    \"To\": \"+13105552222,+14155553333,+16175554444\",\n    \"Body\": \"Team update\",\n    \"MediaUrl\": \"https://cdn.example.com/photo.jpg\",\n}\n```\n\nUse group MMS sparingly — replies fan out to all members, which surprises recipients and can trigger spam complaints.\n\n## Related patterns\n\n- [SMS best practices](/topic/sms-best-practices) — opt-in, opt-out, segment math\n- [A2P 10DLC campaign registry](/topic/a2p-10dlc-campaign-registry) — TCR registration\n- [Toll-free and hosted messaging](/topic/toll-free-and-hosted-messaging) — non-TCR alternative\n- [RCS messaging](/topic/rcs-messaging) — the modern MMS replacement\n\n## References\n\n- 3GPP TS 23.140 — Multimedia Messaging Service (MMS) functional description\n- RFC 4356 — Mapping Between the SDP Bandwidth Modifiers\n- SignalWire docs — Messaging API, MediaUrl parameter\n","html":"<h1>MMS Handling</h1>\n<p>MMS (Multimedia Messaging Service) is the protocol for sending images, audio, video, and other attachments via the carrier messaging network. From a SignalWire developer's perspective MMS is sent the same way as SMS — you just add a <code>MediaUrl</code> parameter. Underneath, carrier transcoding, file-size limits, and message ID handling are noticeably different.</p>\n<h2>When to use MMS vs SMS</h2>\n<ul>\n<li><strong>Use SMS</strong> for text-only confirmations, OTP codes, reminders, opt-in/opt-out flows.</li>\n<li><strong>Use MMS</strong> for receipts, before/after photos, product images, vCards, short voice clips, signed agreements (PDF rendered as image), promotional graphics.</li>\n<li><strong>Use a link to a hosted asset</strong> when the asset is larger than 1 MB, when delivery reliability matters more than inline preview, or when you need analytics on view rates.</li>\n</ul>\n<h2>File size and format limits</h2>\n<p>Carrier limits dominate. SignalWire's outbound limit is 5 MB, but most US carriers reject anything above 1.2 MB. Practical safe target: <strong>600 KB</strong>.</p>\n<p>| Carrier | Practical inbound limit | Practical outbound limit |\n|---|---|---|\n| Verizon | 1.2 MB | 600 KB |\n| AT&#x26;T | 1.0 MB | 600 KB |\n| T-Mobile | 1.5 MB | 800 KB |\n| US Cellular | 1.0 MB | 500 KB |\n| iMessage (Apple) | 100 MB | 100 MB (but only between iMessage users) |</p>\n<p>When in doubt, send 600 KB or less. Anything over is transcoded down to 600 KB or rejected.</p>\n<h2>Supported MIME types</h2>\n<p>| Type | MIME | Notes |\n|---|---|---|\n| Image | <code>image/jpeg</code>, <code>image/png</code>, <code>image/gif</code> | GIFs animate on most modern phones |\n| Audio | <code>audio/mpeg</code>, <code>audio/mp4</code>, <code>audio/wav</code> | Often transcoded to AMR |\n| Video | <code>video/mp4</code>, <code>video/3gpp</code> | Often transcoded to 320x240 |\n| Contact | <code>text/x-vcard</code>, <code>text/vcard</code> | Renders as \"Add contact\" button on most clients |\n| SMIL | <code>application/smil</code> | Slideshow layout — rare, mostly legacy |</p>\n<p>PDFs are not natively supported in MMS. Render the first page as PNG and send that, or send a link.</p>\n<h2>Sending MMS via SignalWire LaML</h2>\n<pre><code class=\"language-python\">import requests\n\nPROJECT = \"your-project-id\"\nTOKEN = \"your-auth-token\"\nSPACE = \"your-space.signalwire.com\"\n\nresponse = requests.post(\n    f\"https://{SPACE}/api/laml/2010-04-01/Accounts/{PROJECT}/Messages.json\",\n    auth=(PROJECT, TOKEN),\n    data={\n        \"From\": \"+12125551111\",\n        \"To\": \"+13105552222\",\n        \"Body\": \"Here's the photo you requested.\",\n        \"MediaUrl\": \"https://cdn.example.com/photo.jpg\",\n    },\n)\n</code></pre>\n<p>For multiple media items, pass <code>MediaUrl</code> repeatedly:</p>\n<pre><code class=\"language-python\">data = [\n    (\"From\", \"+12125551111\"),\n    (\"To\", \"+13105552222\"),\n    (\"Body\", \"Before and after\"),\n    (\"MediaUrl\", \"https://cdn.example.com/before.jpg\"),\n    (\"MediaUrl\", \"https://cdn.example.com/after.jpg\"),\n]\n</code></pre>\n<p>Max 10 media items per message on SignalWire. Most carriers cap at 4 displayed inline; the rest may render as attachments.</p>\n<h2>Sending MMS via REST</h2>\n<p>The same parameters work on the SignalWire REST API:</p>\n<pre><code class=\"language-bash\">curl -X POST https://{space}/api/laml/2010-04-01/Accounts/{project}/Messages.json \\\n  -u \"${PROJECT}:${TOKEN}\" \\\n  -d \"From=+12125551111\" \\\n  -d \"To=+13105552222\" \\\n  -d \"Body=Here's the photo\" \\\n  -d \"MediaUrl=https://cdn.example.com/photo.jpg\"\n</code></pre>\n<h2>Receiving MMS</h2>\n<p>Webhook URL configured on the phone number fires for both SMS and MMS. The webhook receives:</p>\n<p>| Parameter | Type | Notes |\n|---|---|---|\n| <code>From</code>, <code>To</code>, <code>Body</code> | string | Same as SMS |\n| <code>NumMedia</code> | int | Number of media attachments (0 for text-only SMS) |\n| <code>MediaUrl0</code>, <code>MediaUrl1</code>, ... | URL | Signed URLs for each attachment, expire after 24 hours |\n| <code>MediaContentType0</code>, ... | MIME | Content type of each attachment |</p>\n<p>Fetch each MediaUrl with the SignalWire credentials and move the asset to your own storage immediately — the signed URLs expire.</p>\n<pre><code class=\"language-python\">import requests\n\nnum_media = int(form[\"NumMedia\"])\nfor i in range(num_media):\n    url = form[f\"MediaUrl{i}\"]\n    content_type = form[f\"MediaContentType{i}\"]\n    response = requests.get(url, auth=(PROJECT, TOKEN))\n    # Save response.content to your own storage\n</code></pre>\n<h2>A2P 10DLC and MMS</h2>\n<p>MMS sent to US numbers requires the same A2P 10DLC campaign registration as SMS. The campaign use case applies — there is no separate \"MMS campaign\" tier. Throughput limits include MMS in the messages-per-second budget.</p>\n<p>Toll-free MMS does not require TCR registration but is subject to Toll-Free Verification (TFV). Unverified toll-free MMS is heavily rate-limited and may be blocked entirely on T-Mobile.</p>\n<h2>Cost model</h2>\n<p>MMS costs more than SMS per segment:</p>\n<p>| Channel | SMS outbound | MMS outbound |\n|---|---|---|\n| 10DLC | ~$0.0079 / segment | ~$0.0200 / message |\n| Toll-free | ~$0.0090 / segment | ~$0.0225 / message |\n| Short code | ~$0.0049 / segment | ~$0.0150 / message |</p>\n<p>Pricing accurate as of 2025-11. Check the current SignalWire pricing page for live rates. SMS is billed per 160-character segment; MMS is billed per message regardless of size.</p>\n<h2>Common failure modes</h2>\n<p>| Symptom | Cause | Fix |\n|---|---|---|\n| Image arrives blurry or pixelated | Carrier transcoded to low resolution | Pre-size to 640×480 max, JPEG quality 80% |\n| GIF arrives as still image | Carrier dropped animation frames | Use very short GIFs (under 10 frames, 2 seconds) |\n| MMS not delivering to some carriers | File over 1 MB | Compress to 600 KB |\n| MMS delivers as SMS with link | Recipient on a non-MMS plan or feature phone | Fall back to SMS + link automatically |\n| Inbound MMS missing | Webhook only checking <code>Body</code>, not <code>NumMedia</code> | Read <code>NumMedia</code> and iterate MediaUrl* |\n| Recipient on iPhone shows green bubble | RCS/iMessage not engaged, MMS fell through | Expected — MMS is the green-bubble path |</p>\n<h2>Group MMS</h2>\n<p>Sending to multiple recipients with a shared media attachment creates a group MMS. Most carriers support up to 10 recipients per group MMS. Replies go to all participants.</p>\n<pre><code class=\"language-python\">data = {\n    \"From\": \"+12125551111\",\n    \"To\": \"+13105552222,+14155553333,+16175554444\",\n    \"Body\": \"Team update\",\n    \"MediaUrl\": \"https://cdn.example.com/photo.jpg\",\n}\n</code></pre>\n<p>Use group MMS sparingly — replies fan out to all members, which surprises recipients and can trigger spam complaints.</p>\n<h2>Related patterns</h2>\n<ul>\n<li><a href=\"/topic/sms-best-practices\">SMS best practices</a> — opt-in, opt-out, segment math</li>\n<li><a href=\"/topic/a2p-10dlc-campaign-registry\">A2P 10DLC campaign registry</a> — TCR registration</li>\n<li><a href=\"/topic/toll-free-and-hosted-messaging\">Toll-free and hosted messaging</a> — non-TCR alternative</li>\n<li><a href=\"/topic/rcs-messaging\">RCS messaging</a> — the modern MMS replacement</li>\n</ul>\n<h2>References</h2>\n<ul>\n<li>3GPP TS 23.140 — Multimedia Messaging Service (MMS) functional description</li>\n<li>RFC 4356 — Mapping Between the SDP Bandwidth Modifiers</li>\n<li>SignalWire docs — Messaging API, MediaUrl parameter</li>\n</ul>\n"}