Email Attachment Size Limits and Base64 Overhead

Why a 25 MB limit fits about 18 MB of attachments, the limits Gmail, Microsoft 365, SES, SendGrid, Mailgun, and Postmark publish, and how to test the boundary.

Most email size limits count the message as it travels, not the files you attached. Attachments are base64-encoded on the way, which makes them about 37% bigger.

The Encoding Overhead

SMTP carries text, so a binary attachment is base64-encoded: every 3 bytes become 4 characters, a third more. MIME then wraps the encoded text at 76 characters per line, and every line break adds 2 bytes:

encoded size ≈ file size × 4/3 × 78/76 ≈ file size × 1.37

The largest attachment that fits is about 73% of the limit, a little less once you count headers and the body:

Message limitLargest attachment, roughly
10 MB7.3 MB
20 MB14.6 MB
25 MB18.3 MB
30 MB21.9 MB
35 MB25.6 MB
40 MB29.2 MB

Attachments share one limit. Two 10 MB files make a message of about 27 MB.

Published Limits

ServiceLimitCounts
Gmail, personal accounts25 MBAttachments on a sent message. Over the limit, Gmail swaps them for a Google Drive link
Yahoo Mail25 MBAll attachments in one message, sent or received
Microsoft 365, Exchange Online35 MB to send, 36 MB to receiveThe whole message, by default. Admins can set anything from 1 to 150 MB
Amazon SES40 MBThe whole message after base64 encoding, over SMTP or the v2 API. The v1 API allows 10 MB
SendGridUnder 30 MBThe whole message, through the v3 Mail Send API
Mailgun25 MBThe whole message
Postmark10 MBThe whole message. The text and HTML bodies can be up to 5 MB each
From each provider's own help pages and documentation, checked November 2026. Google Workspace and Microsoft 365 admins can set different limits for their organization.

A message passes at least two of these limits: your provider's on the way out, and the recipient's on the way in. The smaller one wins. A 38 MB message that SES accepts still bounces at a Microsoft 365 mailbox on the default 36 MB receive limit.

Every SMTP server also states its own limit. Connect, send EHLO, and the SIZE line in the reply is the maximum in bytes (how). send.smtp.dev replies SIZE 20971520, which is 20 MB.

Testing the Boundary

Size bugs show up in two places: the message that's too big, and what your app does when a server refuses it. Both are easy to reproduce in the sandbox.

Make one file that fits and one that doesn't:

head -c 12000000 /dev/urandom > fits.bin
head -c 18000000 /dev/urandom > too-big.bin

Encoded, fits.bin is about 16 MB and goes through send.smtp.dev. too-big.bin is about 25 MB, so the server refuses it with 552:

swaks --server send.smtp.dev --port 587 --tls \
  --auth-user sender@test.example.com --auth-password "$SMTP_PASSWORD" \
  --from sender@test.example.com --to qa@test.example.com \
  --attach @too-big.bin

For the message that arrived, the API reports the size of the message and of each attachment. Compare them with what your app attached:

curl "https://api.smtp.dev/accounts/{accountId}/mailboxes/{mailboxId}/messages/{id}" \
  -H "X-API-KEY: $SMTP_DEV_API_KEY" | jq '{size, attachments: [.attachments[] | {filename, size}]}'

Then send the oversized message through your app and check what happens next:

  • Check before sending. Estimate the encoded size from the file sizes, and send a download link instead of an attachment past your provider's limit. That's what Gmail does with Drive.
  • Treat 552 as permanent. Retrying a message that's too big fails the same way every time.
  • Tell someone. The person who triggered the email should learn it didn't go out.