D-11 · CONTENT · ATTACHMENTS
Attachment size, encoding, and API delays
Every megabyte attached multiplies across recipients, queues, and filters. This scenario sets size budgets and a link-first policy for API mail.
Symptom
A monthly invoice run with PDF attachments takes nine hours; some recipients get clipped messages, others get deferred deliveries, and a few providers reject oversized messages outright. API timeouts rise because payload assembly and base64 encoding bloat each request, workers retry, and duplicates follow. The team scales workers, but throughput barely moves — the bottleneck is bytes, not concurrency.
Deliverability degrades quietly too: attachment-heavy bulk trips content filters tuned for malware vectors, and Gmail clips messages over roughly 102KB, hiding tracking pixels and CTAs below the clip.
Cause
Base64 encoding inflates attachments by ~37%, so a 5MB PDF becomes ~7MB on the wire — per recipient. Queues, provider ingress, and recipient servers each enforce size caps, and large messages consume disproportionate filtering time. Executable-adjacent types and macro-enabled documents face outright blocks. API designs that embed files inline force synchronous assembly, ballooning request latency past timeout thresholds and triggering unsafe retries.
The product cause is habit: attaching what could be linked. Statements, reports, and assets rarely need to ride inside the message when a signed download link offers fresher content, access control, and analytics.
Fix
Adopt link-first: host files behind authenticated short-lived links, keep the email under 100KB total, and reserve true attachments for the rare cases requiring offline copies (certain invoices, legal notices). Where attachments are mandatory, cap single files near 1–2MB, compress before attach, prefer PDF over Office formats, and pre-validate MIME types against provider blocklists.
Rework the API path: upload assets asynchronously, reference them by ID in send calls, and separate assembly from delivery so timeouts never duplicate sends. Idempotency keys remain mandatory regardless.
Prevention
Enforce payload budgets in CI — reject templates or jobs exceeding size thresholds — and monitor per-message byte distributions, not just counts. Log clip-rate proxies (truncated opens) and attachment-driven deferrals per stream. Review the “must attach” exception list quarterly; most entries migrate to links once analytics show downloads actually get measured.
Worked example
A billing run attaches 4MB statements for 20,000 customers. Base64 inflation turns each message into roughly 5.5MB on the wire — over 100GB of transfer for one run — and the API workers time out assembling payloads synchronously. Half the batch defers, retries duplicate, and Gmail clips the heavy HTML wrappers so tracking and CTAs vanish below the fold.
The fixed run hosts statements behind signed links expiring in 14 days and sends 40KB emails with balance, due date, and one download button. The full batch clears in under an hour, deferrals drop to baseline, downloads become measurable per customer, and support stops fielding “my statement looks broken” tickets. Link-first also enables corrected re-issues without resending: replace the file, keep the audit trail, and notify only affected recipients.