Skip to content
Novus Examples
4 min readNovus ExamplesGuideIntermediatev2026.09

Parse Email Files Without a Mail Server

Nested MIME, four charsets in one mailbox, authentication headers that pass and fail, and a bounce that looks like a reply. Test your parser against real message shapes.

You cannot test an email parser by sending yourself an email

The messages you send yourself are all produced by one client, encoded one way, with one charset and one MIME structure. Real inboxes contain thirty years of accumulated convention from clients that no longer exist, and the parsing failures that matter come from the shapes you would never think to produce.

The email catalogue is 110 fixtures, all synthetic, all with fictional addresses, so you can commit a failing message to your own repository without putting anyone's real mail in version control.

The structure group, which is where the body goes missing

  • plain-text-email-eml is the control.
  • multipart-alternative-email-eml carries the same message as text and as HTML, and the parser's job is to pick one rather than concatenate both.
  • eml-html-only-no-plain is the case that breaks summarisers: there is no plain-text part at all, and code that reaches for one gets an empty string rather than an error.
  • eml-nested-multipart nests a multipart/alternative inside a multipart/mixed, which is what a message with both formatting and attachments actually looks like. Parsers that walk only one level deep find the attachment and lose the body, or the reverse.
  • eml-cid-inline-image references an image by content ID from the HTML part. Treating it as an attachment shows the user a mysterious extra file; failing to resolve it shows a broken image.

Encoding, four ways, in one place

eml-p7-mbox-four-charsets is the single most useful fixture in this group: one mailbox, four different character encodings across its messages. A parser that reads the mailbox with one assumed encoding will produce mojibake for three quarters of it.

Underneath it, the transfer encodings are separated deliberately: eml-p7-quoted-printable-accented-utf8 and eml-p7-base64-accented-utf8 carry the same accented text through the two common encodings. Testing both matters because quoted-printable is where soft line breaks live, and a parser that forgets to remove a trailing = produces text with characters missing at line ends, roughly every 76 characters, which reads as random corruption.

eml-utf8-encoded-subject covers RFC 2047 header encoding, which is a separate mechanism from body encoding and frequently implemented separately and worse. eml-utf8-filename-attachment is the same problem on a filename, where the consequence is a saved file with an unusable name.

Authentication results, which are a parsing problem before they are a security one

Six fixtures: eml-p7-auth-results-all-pass, -all-fail, -alignment-failure, -multiple-hops, eml-p7-auth-two-dkim-signatures, and the annotated dkim-spf-annotated-eml.

Two of these are worth dwelling on.

eml-p7-auth-results-alignment-failure is the case where SPF and DKIM both pass and DMARC still fails, because the domains they authenticated are not the domain in the From header. Code that treats "SPF passed" as "this is legitimate" reports the opposite of the truth here.

eml-p7-auth-results-multiple-hops carries several Authentication-Results headers from different relays. The only one you may trust is the one added by your own trusted boundary, and a parser that reads the first or the last is trusting a header an attacker can add.

Delivery reports, which are not replies

bounce-ndr-eml, eml-delivery-receipt-mdn, eml-delivery-out-of-office and eml-p7-mbox-thread-with-bounce.

These matter to anything that threads conversations or counts replies. A bounce is a multipart/report with machine-readable status inside; an out-of-office is an ordinary message carrying Auto-Submitted. Treating either as a human reply corrupts response-rate metrics and, in a support tool, reopens tickets nobody touched. eml-header-auto-submitted isolates the header itself so you can test the check on its own.

Threading

eml-threaded-references and larger-mbox-thread exercise In-Reply-To and References. Threading by subject line is the naive approach and it fails in both directions: it merges unrelated messages that share a generic subject, and splits threads the moment someone edits the subject. The references chain is the correct mechanism and these fixtures give you a real one to walk.

eml-header-reply-to-different is the small trap where Reply-To and From disagree, which is normal for mailing lists and is the difference between replying to a person and replying to several thousand.

Calendar messages are email and also not

meeting-invite-ics-eml, eml-calendar-invite-request, eml-calendar-invite-cancel, plus the bare ics-request-sample and ics-cancel-sample.

An invite is a message with a text/calendar part whose METHOD decides the meaning: REQUEST creates, CANCEL withdraws. Having both the wrapped and bare forms lets you separate a MIME problem from an iCalendar problem in one step, which is usually the slowest part of debugging these.

Where to start

  1. eml-nested-multipart, because a lost body is the most common report.
  2. eml-p7-mbox-four-charsets, because encoding bugs look like data corruption.
  3. eml-html-only-no-plain, because empty-string handling is rarely written deliberately.
  4. bounce-ndr-eml, if you count replies for anything.
  5. eml-p7-auth-results-alignment-failure, if you make trust decisions from headers.

Every message here is synthetic and the addresses are fictional, so none of this is anyone's real correspondence.

Continue this workflow

Try the workflow

Documentation and troubleshooting

Was this article helpful?

Found an error? Send a correction.