mail-builder
A flexible, dependency-light Rust library for building RFC 5322 compliant, MIME-encoded email messages.
Repository Health
Technical Analysis
mail-builder is a Rust library for constructing RFC 5322 compliant e-mail messages with full MIME support (RFC 2045-2049). It automatically selects the most compact valid encoding — 7bit, quoted-printable, or base64 — for each message part, so callers don’t have to reason about encoding rules themselves.
The API is a chainable MessageBuilder that accepts plain-text and HTML bodies, inline content, and binary attachments, then writes a complete, spec-compliant message to any std::io::Write sink. For more complex layouts, a lower-level MimePart/body() API lets callers construct arbitrary nested multipart trees directly.
It has no required runtime dependencies (an optional gethostname feature aids Message-ID generation) and forbids unsafe code. It’s a companion to the mail-parser and mail-send crates from the same maintainers — mail-builder only builds messages; parsing and sending are handled by those sibling crates.
What You Get
- A fluent
MessageBuilderAPI for headers, plain-text/HTML bodies, inline content, and attachments - Automatic MIME encoding selection (7bit, quoted-printable, base64) per message part
- A fast, table-based base64 encoder adapted from Chromium’s implementation
- Support for arbitrarily nested multipart MIME structures via a lower-level
MimePartAPI - RFC 2047-encoded, internationalized header support for names and grouped address lists
- Automatic generation of Message-ID, Date, and MIME-Version headers when not explicitly set
Common Use Cases
- Building transactional or notification e-mails in a Rust backend service before handing them to an SMTP client
- Composing multipart messages with plain-text and HTML alternatives plus inline images
- Attaching binary or text files (with internationalized filenames) to outgoing messages
- Generating raw
.emlfiles for testing, archival, or message-queue payloads - Constructing complex nested MIME structures (e.g. RFC 8621-style multipart trees) for interoperability testing
Under The Hood
Architecture
The library is organized into three clean modules — encoders (base64, quoted-printable, and an encode module that picks the optimal encoding), headers (address, content_type, date, message_id, raw, text, url — each implementing a shared Header trait), and mime.rs, which defines the MimePart/BodyPart tree — with lib.rs’s MessageBuilder acting as the top-level, self-consuming builder that orchestrates all three. Data flows one way: MessageBuilder accumulates headers and body parts, then write_to/write_body walks the MimePart tree recursively, selecting an encoding for each part before writing headers and encoded content to any io::Write sink. Separation of concerns is clean — encoders are unaware of MIME structure, headers are unaware of encoding, and MimePart composes both — a straightforward layered design with no dependency injection, since the domain is a one-shot builder pipeline. Changing the core MimePart/BodyPart enum would ripple into every header’s write_header implementation and the recursive write logic in mime.rs.
Tech Stack
Pure Rust (edition 2024) with a single optional dependency, gethostname, used only to generate a hostname-qualified Message-ID (enabled by default, can be disabled). Dev-dependencies include mail-parser for round-trip test verification and serde/serde_yaml/serde_json for test fixtures. There is no database, web framework, or build tooling beyond standard cargo build/cargo test; the crate is published to crates.io and consumed as a building block by sibling Stalwart Labs crates such as mail-send. CI runs on GitHub Actions (ubuntu-latest), executing cargo build and cargo test on every push and pull request.
Code Quality
The test suite covers base64/quoted-printable encoding edge cases and full message construction, including deeply nested multipart trees, and round-trips generated output through mail-parser to confirm it parses as valid mail — a stronger check than isolated unit assertions. Error handling is idiomatic: functions return io::Result and propagate failures with ?; the crate additionally enforces #![forbid(unsafe_code)] and #![deny(rust_2018_idioms)] at the compiler level. One explicit, documented panic exists (unwrap_address, for callers who assert a specific Address variant) rather than any silent failure. Naming is consistent snake_case throughout, lifetimes are threaded through nearly every type to minimize allocation, and CI runs the full suite on every change. No separate linter config is present beyond the compiler’s deny/forbid attributes.
API Design
MessageBuilder exposes a chainable, self-consuming builder (.from().to().subject().text_body().attachment().write_to_vec()) that needs almost no boilerplate to emit a valid RFC 5322 message — Message-ID, Date, and MIME-Version headers are generated automatically when omitted. Extensive Into<T> conversions let callers pass raw tuples like ("name", "email") or nested Vec structures directly as header values without constructing intermediate types first. Documentation is doc-included from the README via #![doc = include_str!("../README.md")], so the crate’s published docs mirror the same usage examples developers read on GitHub — a small but genuinely useful DX touch.