Connect by JBRH Open Connect

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.

Status
Reference What this means
Audience
both, developer
Last verified
Product version
6.3.2

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 attachment

Real 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#

EncodingUsed forFailure it produces when mishandled
7bitPlain ASCIINone, until a non-ASCII character appears and is truncated
8bitText with high bytes, on servers that permit itCorruption on a hop that does not support it
quoted-printableMostly-ASCII text with occasional accentsVisible =E2=80=99 where an apostrophe should be, and = at line ends
base64Binary and heavily non-ASCII textA 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.py before anything is displayed. The HTML part of a multipart/alternative is 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 remote https: 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#

  1. 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.
  2. Prefer the declared charset, then fall back deliberately. A wrong declaration is common; guessing silently is worse than a visible fallback.
  3. Treat Content-Disposition as advisory. inline and attachment are hints, and a filename in there is attacker-controlled text that must never be used as a path.
  4. Bound everything. Part count, nesting depth, decoded size. A message crafted to expand is cheap to send and expensive to open.
  5. 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.