@mailkite/mail-parse — an MIT streaming MIME parser, and why it isn't serving your mail yet #4
bucabay
announced in
Announcements
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Shipped June 30, 2026 — published as
@mailkite/mail-parse0.1.0, MIT.MIME parsing is where inbound email quietly breaks. Real mail is thirty years of mailer bugs, wrong charsets, broken boundaries, and encodings nobody would design today — and a parser either handles the long tail or you lose messages.
So we wrote one, released it under MIT, and we are not running it on your mail yet. Here's why that second part is the interesting bit.
The parser
@mailkite/mail-parse— streaming, memory-efficient, fully typed.Two design choices worth knowing about:
Extensible without forking. Behaviour comes from a declarative-match middleware registry, PostCSS-style. A weird mailer's quirk becomes a narrowly-matched middleware, not a patch to the core. You can add your own without touching ours.
It emits a failure signature. When the parser hits something it can't handle, it produces a PII-free structural signature of the failure — the shape of what broke, never the content that broke it. That's what makes the next section possible, and it's why the signature is a first-class output rather than a log line.
There are ports with cross-language hash parity — mail-parse-py and mail-parse-go — so the same broken message produces the same signature in all three. Failures dedupe across languages.
We don't swap parsers on faith
postal-mimeis still the trusted parser serving production. Ours runs beside it in shadow.On every inbound message we parse with both and record a structural comparison:
ctx.waitUntilafter the response is already sent, so it can never affect serving. It has its own try/catch too — our parser throwing just records anerrorrow.agreewhen subject, from-address, to/cc counts, and attachment count and names all match.What gets recorded is counts, lengths, and diagnostic codes only —
to_count_delta,attach_names_match,text_len_delta,our_ms, and so on. Never message content. No subject text, no addresses, no bodies. The comparison table is structural by construction, not by policy — there is no field for content to leak into.Ours gets promoted to serving when the disagreement rate says it's earned it, and not before.
Why we're telling you this
Two reasons.
It's genuinely useful on its own. If you parse email anywhere — not necessarily with MailKite — it's MIT and on npm. Streaming and typed, with a real extension seam. Take it.
It's how we think inbound should be handled. Parsers are the part of an email platform you never see and always depend on. Running a new one in shadow against real traffic, comparing PII-free structure, and only promoting on evidence is more work than swapping the dependency and watching the error rate. We think it's the difference between losing your mail and not.
Where to take it
All reactions