MIME
MIME is the set of conventions that let an email carry more than seven-bit American English. A message becomes a tree: a root with a content type, children that are alternatives or attachments, and each leaf declaring its own character set and transfer encoding. Almost every mail-parsing bug is a tree walked wrongly.
The three multipart types, and what each means#
multipart/alternative- The same content in several forms — usually plain text and HTML. A client picks one, normally the last it understands. Both parts should say the same thing; when they do not, different readers get different messages.
multipart/mixed- Distinct things carried together: a body and its attachments. A client shows all of them.
multipart/related- One part plus the resources it references — an HTML body and the images it embeds by
Content-ID. Splitting these apart produces an HTML body with broken image references.
multipart/mixed
├── multipart/alternative
│ ├── text/plain <- shown by a plain-text client
│ └── multipart/related
│ ├── text/html <- shown by an HTML client
│ └── image/png <- referenced as cid:logo
└── application/pdf <- the attachmentReal messages nest these several deep, and a forwarded message adds message/rfc822 containing an entire second tree. A parser that assumes two levels works on most mail and mangles exactly the messages people care about — the forwarded chain with the contract attached.
Encodings, and where text goes wrong#
| Encoding | Used for | Failure it produces when mishandled |
|---|---|---|
7bit | Plain ASCII | None, until a non-ASCII character appears and is truncated |
8bit | Text with high bytes, on servers that permit it | Corruption on a hop that does not support it |
quoted-printable | Mostly-ASCII text with occasional accents | Visible =E2=80=99 where an apostrophe should be, and = at line ends |
base64 | Binary and heavily non-ASCII text | A wall of unreadable characters if the decode step is skipped |
Two separate declarations must both be honoured: the transfer encoding says how the bytes were packed for transport, and the charset parameter says what those bytes mean once unpacked. Decoding one and ignoring the other is how mojibake arrives — text that is technically intact and unreadable.
Does Connect use MIME?#
Used, unavoidably, on every mail path. Anything Connect fetches over IMAP is a MIME message and must be walked; anything it sends is assembled as one. The parts a workspace sees — the text of a reply, the attachments on a thread, the HTML a message was written in — are all the result of that tree being traversed correctly.
Two behaviours downstream of MIME are worth naming, because a reader notices them without knowing they are MIME decisions:
- HTML bodies are sanitised aggressively by
mail_render.pybefore anything is displayed. The HTML part of amultipart/alternativeis arbitrary markup from a stranger, and treating it as trusted is how a mail reader becomes an attack surface. - Remote images are proxied rather than fetched by the reader's browser. An embedded
cid:image is part of the message; a remotehttps:image is a request to somebody else's server, and letting the browser make it directly tells the sender the message was opened, from which address and at what time.
Attachments are handled by the same file service the rest of Connect uses, which reads the common formats with the standard library and refuses two specific hazards outright: DOCTYPE or ENTITY declarations in XML-based formats, and archives that expand far beyond their compressed size. Both are refusals rather than best-effort parses.
Parsing defensively#
- Never assume the tree's shape. Walk it. Depth, order and part count are all sender-controlled, and a malformed message is not a rare event at any volume.
- Prefer the declared charset, then fall back deliberately. A wrong declaration is common; guessing silently is worse than a visible fallback.
- Treat
Content-Dispositionas advisory.inlineandattachmentare hints, and a filename in there is attacker-controlled text that must never be used as a path. - Bound everything. Part count, nesting depth, decoded size. A message crafted to expand is cheap to send and expensive to open.
- Keep the raw message. Once a tree has been flattened into text you cannot answer a question about what the sender actually sent.
Questions#
Why do some recipients see a different message from others?
Because multipart/alternative carries more than one version and each client picks one. If the plain-text part is an afterthought — a stripped or truncated copy — then readers on a plain-text client, and several automated systems, get the afterthought. Both parts should carry the same content.
What makes an email so much larger than its attachment?
Base64, mostly. It represents three bytes as four characters, so binary content grows by roughly a third before line breaks are added. A 6 MB attachment produces a message well over 8 MB, which matters against provider size limits that are stated for the whole message.
Are inline images the same as attachments?
They are attachments with a Content-ID, referenced from the HTML body by cid:. They travel inside the message, so they display without any network request — unlike a remote image, which is a fetch from the sender's server and therefore a tracking signal.