Twilio MMS image limit is 5 MB: compress a Sume image to fit

Twilio's MMS media limit is 5 MB, with image/jpeg, png, gif, heic, heif, tiff and bmp accepted. A 4K PNG may blow past it; recompress to JPEG before sending.

5 min readSume
All posts

Twilio's accepted-content-types page gives 5 MB as the MMS maximum size, and lists the image MIME types it accepts: image/jpeg, image/jpg, image/gif, image/png, image/heic, image/heif, image/tiff and image/bmp. WebP is not on that list, so a WebP from an image API needs converting. The page adds that carrier-specific limits apply to non-image files, which is a separate article.

What is on the list

The practical consequence is that the Sume output_format choice matters. Of the four formats the Image API names, PNG and JPEG are accepted by Twilio, while WebP and SVG are not.

Twilio MMS images against Sume output_format (read 2026-10-03)
Sume output_formatMIME typeAccepted by Twilio MMS
pngimage/pngYes
jpegimage/jpegYes
webpimage/webpNot on the list
svgimage/svg+xmlNot on the list

Weight rather than pixels

A 4K PNG of a photographic scene can exceed 5 MB, while the same image as a JPEG at quality 85 is a fraction of that. The Image API gives resolution tiers of 512, 1K, 2K and 4K; a 1K or 2K image is enough for a phone screen, if the model's catalog entry lists those tiers. output_compression exists in the schema but returns 400 unsupported_parameter in v1, so compress locally.

Compress until it fits

The script downloads a JPEG, then lowers quality until the file is under 5 MB. It uses 4,900,000 bytes as the ceiling to leave headroom, since the page says 5 MB without saying whether that means 5,000,000 or 5,242,880 bytes.

import os, io, requests
from PIL import Image
r = requests.post(
    "https://api.sume.com/v1/images",
    headers={"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"},
    json={"model": "bytedance-seed/seedream-4.5", "prompt": "festive window display with warm lights, photographic", "output_format": "jpeg"},
    timeout=90,
)
r.raise_for_status()
if r.status_code != 200:
    raise SystemExit("202: read the finished job from /v1/jobs/{id}/result")
img = Image.open(io.BytesIO(requests.get(r.json()["data"][0]["url"], timeout=60).content))
for q in (90, 80, 70, 60, 50):
    img.convert("RGB").save("mms.jpg", "JPEG", quality=q, optimize=True)
    if os.path.getsize("mms.jpg") <= 4_900_000:
        break
else:
    raise SystemExit("still over the MMS limit; pick a smaller resolution tier")
print(os.path.getsize("mms.jpg"), "bytes at quality", q)

Why JPEG is the safe default

For photographic content JPEG is both on Twilio's accepted list and much smaller than PNG at the same visual quality, so ask the Image API for jpeg directly, which the docs list in output_format. PNG makes sense for flat graphics, logos and screenshots where sharp edges matter, and those are usually far below 5 MB anyway. If you need to send the same image to several channels, keep a master and derive each output, because each platform has its own list. Twilio's page also names HEIC, HEIF, TIFF and BMP, but there is no reason to produce those from a Sume image; JPEG and PNG are the formats the Image API gives you. Treat the 5 MB ceiling as the outer limit and the carrier limits mentioned on the page as the reason to aim lower. Recheck the page before a campaign, since limits change.

Sending

Host the final JPEG on your own storage rather than passing the Sume URL along. The Image API docs describe those URLs as signed, and they are not documented as permanent. Whether a given carrier delivers a 4.9 MB image is a separate question the page flags; many senders aim far lower.

Log the final byte count and quality next to the message record, so a delivery problem can be matched to the file that was sent.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume