mail-parser

Zero-copy, RFC 5322/MIME-conformant e-mail parsing library for Rust with no required dependencies.

Library
Cargo
v0.11.8
456stars
Apache-2.0 OR MIT

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
73/100Good
Development Activity80
Maintenance72
Community56
Maturity56
Momentum28

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
78/100Good
Architecture82
Code Quality85
Innovation80
Learning Curve65

mail-parser is a Rust library for parsing e-mail messages that fully conforms to the Internet Message Format (RFC 5322) and MIME (RFC 2045-2049) standards, along with more than a dozen related RFCs covering headers, character sets, and mailing-list metadata. Rather than exposing a raw nested MIME tree, it follows RFC 8621 and hands back a flattened, human-friendly view of text bodies, HTML bodies, and attachments, converting between HTML and plain text automatically when only one alternative is present.

Performance and safety were explicit design goals: nearly every string returned is a zero-copy Cow<str> over the original input, base64 decoding uses a Chromium-derived fast path, and header/charset/entity lookups use perfect hashing. The library is written in 100% safe Rust with no required dependencies, decodes 41 character sets out of the box (with legacy multi-byte sets like Shift_JIS and Big5 available via the optional encoding_rs crate), and is fuzzed and MIRI-tested. It’s the parser at the core of Stalwart Mail Server, so it has been exercised against millions of real-world messages spanning three decades.

What You Get

  • A single MessageParser::default().parse() entry point that turns raw bytes into a fully structured Message, including nested message/rfc822 attachments
  • RFC 8621-style flattened access to text_body, html_body, and attachments instead of a raw MIME part tree
  • Automatic HTML-to-plain-text (and vice versa) conversion when only one alternative body is present
  • Decoding for 41 character sets built in, plus legacy multi-byte sets (Shift_JIS, Big5, GB18030, EUC-JP/KR) via the optional full_encoding feature
  • Optional serde and rkyv integrations for serializing parsed messages to JSON or a zero-copy binary format
  • mbox and Maildir mailbox iterators for streaming messages out of local mail stores

Common Use Cases

  • Building a mail server or MTA component that needs to parse inbound SMTP payloads into structured messages (its original use case inside Stalwart Mail Server)
  • Extracting text/HTML bodies and attachments from .eml files for indexing, search, or archival pipelines
  • Writing an email client or webmail backend that needs address, subject, and threading metadata without a full MIME toolkit
  • Parsing bulk mbox or Maildir archives for migration, backup, or e-discovery tooling

Under The Hood

Architecture The crate is a layered parsing pipeline. MessageParser::default().parse() in src/lib.rs wraps the raw bytes in a MessageStream (src/parsers/mod.rs) and drives src/parsers/message.rs and src/parsers/header.rs, which recursively call field-specific parsers under src/parsers/fields/ for addresses, dates, content-type, and other structured headers. Content-transfer-encoded bodies are handed to src/decoders/ (base64, quoted-printable, encoded-word, and per-charset decoders), and src/core/builder.rs assembles the results into the flattened Message/MessagePart model defined in src/core/message.rs, which RFC 8621-style exposes text_body, html_body, and attachments as simple part-id lists rather than a nested MIME tree. src/mailbox/ (mbox.rs, maildir.rs) layers mailbox-format iteration on top of the same per-message parser, so a change to the core Message model has to stay in sync with the builder, the optional serde/rkyv codecs in src/core/rkyv.rs, and both mailbox iterators.

Tech Stack Written in Rust (2024 edition) with a deliberately minimal dependency footprint: hashify 0.2 provides the perfect-hashing tables used across header, charset, and entity lookups, and everything else — encoding_rs 0.8 (legacy charsets), serde 1.0 (JSON serialization), and rkyv 0.8 (zero-copy binary archives) — is gated behind opt-in Cargo features (full_encoding, serde, rkyv) so the default build pulls in nothing beyond hashify. Dev-dependencies (serde_json, bincode, chrono) exist only for the test/benchmark suite. CI (.github/workflows/rust.yml) runs cargo test, and the project ships a cargo-fuzz target and MIRI-driven undefined-behavior checks alongside the crate.

Code Quality The crate carries a 152-line tests/integration_test.rs plus roughly three dozen unit tests embedded in src/, backed by an extensive resources/ fixture set (address.json, content_type.json, date.json, received.json, an eml/ corpus, and a maildir/ sample tree) that exercises real-world RFC edge cases. Public API surfaces are heavily doc-commented — src/lib.rs and src/core/message.rs alone carry dozens of /// doc comments — and the crate declares #[forbid(unsafe_code)], backed by CI, fuzzing, and MIRI runs per the README. No CONTRIBUTING guide or docs/ directory exists, so onboarding leans on the doc comments and README rather than dedicated contributor docs.

API Design The public surface is small and ergonomic: MessageParser::default().parse(bytes) returns an Option<Message>, and callers navigate results through flattened text_body/html_body/attachments id lists plus convenience accessors like .from(), .subject(), and .date() rather than a raw MIME-part tree. This RFC 8621-inspired shape trades some structural fidelity for a much shorter path from raw bytes to usable content than typical nested-MIME parsers, and the optional serde/rkyv derives let the same Message type round-trip through JSON or a zero-copy binary format with no extra glue code.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search