MIME & Transfer Encodings (RFC 2045/2046)
Deliverability-focused MIME digest: structure and headers, multipart/alternative ordering, quoted-printable and base64 rules and pitfalls, size inflation, and the malformed-MIME defects that trigger filtering.
MIME extends the ASCII-only, line-oriented RFC 5322 message format to carry multiple parts, non-ASCII text, and binary data over transports that only guarantee 7-bit lines. Every HTML campaign an ESP generates is a MIME message; encoding and structure defects are a recurring cause of mangled rendering and filter penalties. RFC 2045 defines the framework and encodings; RFC 2046 defines the media types.
The five MIME header fields (RFC 2045)
| Field | Requirement | Notes |
|---|---|---|
MIME-Version: 1.0 |
MUST be present, verbatim, on every MIME message | Missing it means receivers may treat the message as pre-MIME plain text |
Content-Type: type/subtype; parameter=value |
Subtype mandatory; type/subtype/parameter names case-insensitive, parameter values case-sensitive unless defined otherwise | Default when absent: text/plain; charset=us-ascii |
Content-Transfer-Encoding: mechanism |
One of 7bit (default) / 8bit / binary / quoted-printable / base64 / extension |
See below |
Content-ID: <msg-id> |
Optional; MUST be world-unique | Used to reference inline images (cid:) from HTML parts |
Content-Description |
Optional, US-ASCII (RFC 2047 for non-ASCII) | Rarely load-bearing |
Content-Transfer-Encoding mechanisms
| Mechanism | Constraint on the data | Actually transforms? |
|---|---|---|
7bit |
Lines ≤ 998 octets between CRLFs; no octets > 127; no NULs | No — a declaration that data already conforms |
8bit |
Lines ≤ 998 octets; octets > 127 allowed; no NULs; CR/LF only as CRLF pairs | No — requires the 8BITMIME SMTP extension end-to-end |
binary |
Any octet sequence, no line-length limit | No — requires BINARYMIME/CHUNKING; essentially unused in Internet mail |
quoted-printable |
Output is 7-bit, lines ≤ 76 chars | Yes |
base64 |
Output is 7-bit, lines ≤ 76 chars | Yes |
Composite-type restriction: it is "EXPRESSLY FORBIDDEN" to use any encoding other than 7bit, 8bit, or binary on multipart/* or message/* entities — encoding applies to leaf parts only, never to a container (prevents nested encode/decode passes). (RFC 6532 later relaxes this for message/global only — see SMTPUTF8/EAI.)
Quoted-printable rules (RFC 2045 §6.7)
Designed for data that is mostly ASCII text — encoded output stays largely human-readable.
- Any octet may be represented as
=XXwith uppercase hex digits (lowercase is not permitted on generation, though robust decoders accept it). - Octets 33–60 and 62–126 (printable ASCII except
=) may appear literally. - TAB (9) and SPACE (32) may appear literally but MUST NOT be literal at the end of an encoded line — trailing whitespace must be
=09/=20(transport may strip trailing spaces silently). - A hard line break in text is represented as a real CRLF.
- Encoded lines MUST be ≤ 76 characters (not counting trailing CRLF). Longer content lines use a soft line break:
=as the last character, which the decoder deletes along with the following CRLF. - When encoding binary data in QP, CR and LF must be encoded as
=0D/=0A(a binary CRLF becomes=0D=0A) — QP for binary is legal but base64 is the right tool.
Practical: QP inflates typical English/Western-European HTML only slightly (3 bytes per encoded octet); for scripts where nearly every character is multi-byte UTF-8 (CJK, Cyrillic), QP approaches 3× inflation and base64 (~1.37×) is smaller.
Base64 rules (RFC 2045 §6.8)
- 65-character alphabet:
A–Z(0–25),a–z(26–51),0–9(52–61),+(62),/(63),=padding. - 3 input octets → 4 output characters; final partial group padded: 1 octet → 2 chars +
==, 2 octets → 3 chars +=. Any=marks end of data. - Encoded output MUST be in lines of ≤ 76 characters.
- Text line breaks must be converted to CRLF before encoding (canonical form), so a decoded text part is CRLF-delimited regardless of the composing platform.
- Size inflation: 4/3 (~33%) plus CRLFs every 76 chars ≈ 37% total. Provider message-size limits (e.g., Gmail's 25 MB) apply to the encoded message — a "20 MB attachment" is ~27 MB on the wire.
Choosing an encoding (sender guidance)
- HTML/plain-text parts in UTF-8:
quoted-printableis the safe universal default — it survives 7-bit hops, keeps diffs/DKIM canonicalization predictable, and avoids depending on 8BITMIME negotiation.8bitis fine when your MTA verifies 8BITMIME support on every hop (essentially universal today, but a 7-bit-only hop must then re-encode, which breaks DKIM signatures — sign after final encoding, or use QP so no hop ever re-encodes). - Attachments and images:
base64, always. - Never emit lines over 998 octets in any part (long unwrapped HTML lines are a classic generator bug — QP's 76-char soft-wrapping fixes this for free).
Media types (RFC 2046)
Discrete types: text, image, audio, video, application. Composite: multipart, message. Unrecognized subtypes fall back: text with known charset → text/plain; other discrete → application/octet-stream; unknown multipart/* → treated as multipart/mixed.
Multipart syntax
- The
boundaryparameter is mandatory: 1–70 characters, must not end in a space; allowed characters are DIGIT/ALPHA and'()+_,-./:=?(and space, not last). Because/,=,?,:and space can appear, the boundary value usually needs quoting in the Content-Type header. - The delimiter is a line starting
--boundary; the closing delimiter is--boundary--. The boundary must not appear in any encapsulated part — generators should use high-entropy boundaries rather than checking. - Each body part = optional headers + blank line + body. Parts with no Content-Type default to
text/plain; charset=US-ASCII(except insidemultipart/digest, where the default ismessage/rfc822). - Preamble (before first boundary) and epilogue (after closing delimiter) are ignored by MIME readers; the preamble is occasionally used for "this is a MIME message" notes to legacy clients.
Multipart subtypes that matter for senders
| Subtype | Semantics | Deliverability notes |
|---|---|---|
multipart/mixed |
Independent parts, in order | Message + attachments |
multipart/alternative |
Same information, alternative renditions. Parts MUST be in increasing order of preference/faithfulness — the best/richest version LAST. Clients display "the last part of a type supported by the recipient system's local environment." | The classic structure is text/plain first, text/html last. Reversed order makes many clients show the plain-text part; a well-formed pair (with the plain part actually matching the HTML content) is a long-standing spam-filter positive signal, while an empty or boilerplate plain part is a mild negative |
multipart/related (RFC 2387, referenced here for completeness) |
HTML + inline cid: images |
Standard structure for embedded images |
multipart/digest |
Parts default to message/rfc822 |
Rare in ESP traffic |
multipart/parallel |
Order not significant | Effectively unused |
multipart/report (RFC 6522) |
Machine-readable reports | The container for DSNs and FBL ARF reports |
Canonical campaign nesting: multipart/mixed( multipart/related( multipart/alternative( text/plain, text/html ), inline images ), attachments ) — collapse levels you don't need.
message/* subtypes
message/rfc822: a full encapsulated message (used for forwards, FBL reports, returned originals in bounces). Encoding restricted to 7bit/8bit/binary.message/partial: fragmented delivery with reassembly (parametersid,numberstarting at 1,totalon the last piece; 7bit only) — obsolete in practice, and filters treat fragmentation as an evasion technique. Never use.message/external-body: body by reference (access-types FTP/ANON-FTP/TFTP/LOCAL-FILE/MAIL-SERVER) — also effectively dead and filter-suspicious.
text/plain and charsets
Default charset is US-ASCII (meaning ANSI X3.4-1986 specifically). Always declare charset= explicitly; label content with the lowest common denominator that actually covers it. In modern practice: just use charset=utf-8 everywhere and be done.
Malformed-MIME defects that trigger filtering
Spam filters score MIME conformance because sloppy generators correlate with spamware (SpamAssassin's MIME_* rule family and equivalents in commercial filters test exactly these). Defects worth linting for in an ESP's message builder:
| Defect | RFC rule violated |
|---|---|
Missing MIME-Version: 1.0 on a message with MIME structure |
2045 §4 MUST |
Missing or unmatched final --boundary-- closing delimiter |
2046 §5.1.1 |
| Boundary string occurring inside a part's body | 2046 §5.1.1 "must not appear inside any of the encapsulated parts" |
Raw 8-bit bytes in a part declared 7bit/quoted-printable, or in headers |
2045 §2.7/6.7 |
| Lines > 998 octets (unwrapped HTML) | 2045 §2.7 / 5321 limit |
QP lines > 76 chars, lowercase =xx hex, literal trailing whitespace |
2045 §6.7 rules 3 & 5 |
| Base64 with invalid characters or broken padding | 2045 §6.8 |
| Content-Transfer-Encoding other than 7bit/8bit/binary on a multipart | 2045 §6.4 |
Charset declared but content not valid in it (e.g., us-ascii label on 8-bit body) |
2045 §5.2 / 2046 §4.1 |
multipart/alternative with HTML before plain text, or HTML-only inside an alternative |
2046 §5.1.4 ordering |
| Bare CR or LF (not CRLF pairs) in transmitted data | 2045 §2.8 / 5321 |
message/partial fragmentation |
filter-evasion heuristic |
None of these alone typically blocks delivery at major providers, but they stack with reputation, and several (boundary bugs, encoding mismatches) also cause visible rendering corruption — which drives "report spam" clicks.
See also
- RFC 5322 — Message Format — the header framework MIME extends.
- SMTPUTF8 / EAI — raw UTF-8 in headers,
message/global. - ESMTP Extensions — SIZE (declared size includes encoding inflation), CHUNKING/BINARYMIME.
- Content & Design for Deliverability — content-level signals beyond structure.