# 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
- **Audience:** both, developer
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/docs/technology/mime/

## 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.

```text
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

| 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.

> **Note** Headers have their own scheme. A non-ASCII subject is encoded per RFC 2047 as `=?UTF-8?B?…?=` and must be decoded separately from the body. A subject line displaying that pattern is a decoder that stopped at the body.

## 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.

> **Careful** A filename inside a MIME part is not a safe filename. Path separators, traversal sequences and right-to-left override characters all appear in real mail, and the last of those makes an executable look like a document in a file list.

## 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.

## Related

- [HTML email](https://connectbyjbrh.com/docs/technology/html-email/)
- [SMTP](https://connectbyjbrh.com/docs/technology/smtp/)
- [IMAP](https://connectbyjbrh.com/docs/technology/imap/)
- [Unicode, scripts and transliteration](https://connectbyjbrh.com/docs/technology/unicode-and-scripts/)
- [PDF text extraction](https://connectbyjbrh.com/docs/technology/pdf-text/)
- [Email in Connect](https://connectbyjbrh.com/docs/email/)

## What this page is based on

- RFC 2045–2047 — MIME message bodies, media types and header extensions, https://www.rfc-editor.org/rfc/rfc2045
- RFC 5322 — Internet Message Format, https://www.rfc-editor.org/rfc/rfc5322
- Connect channel source pack — docs-source/sources/CHANNELS.md §1, `mail_render.py` sanitising and remote-image proxying
- Connect architecture source pack — docs-source/sources/GENERAL.md §9, file formats read with the standard library; DOCTYPE/ENTITY declarations and zip bombs refused
