smtp-proto
A fast, zero-copy SMTP/LMTP protocol parser for Rust covering every registered service extension.
Repository Health
Technical Analysis
smtp-proto is a Rust library that parses raw SMTP and LMTP wire traffic into strongly typed request and response structures. It implements every registered SMTP service extension from the IANA mail-parameters registry, including EHLO capability negotiation, MAIL/RCPT parameter parsing (SIZE, BODY, DSN, MT-PRIORITY, REQUIRETLS, and more), AUTH mechanism enumeration, chunked transfer with BDAT, and multi-line response parsing with enhanced status codes. The crate is built for streaming network I/O rather than parsing complete buffers up front, so it can incrementally accumulate a partial command across several reads before emitting a result.
The library underpins the SMTP and LMTP servers in Stalwart Mail Server, where correctness and throughput on the wire protocol are directly load-bearing. Its API returns borrowed Cow<str> values wherever possible to avoid unnecessary allocation, and optional serde and rkyv feature flags let consumers serialize parsed requests and responses for logging, caching, or IPC without pulling those dependencies in by default.
What You Get
- Typed
Request<T>andResponse<T>enums covering every core SMTP/LMTP command and reply, including EHLO, MAIL, RCPT, BDAT, AUTH, STARTTLS, and enhanced status codes - Streaming receivers (
RequestReceiver,DataReceiver,BdatReceiver,LineReceiver) that accumulate partial input across multiple network reads until a full command is available - Parsing for every registered SMTP service extension parameter (SIZE, BODY, DSN NOTIFY/ORCPT/RET, MT-PRIORITY, REQUIRETLS, CONPERM/CONNEG, AUTH mechanisms, and more) via IANA mail-parameters coverage
- Zero-copy parsing that returns
Cow<str>borrowed slices where possible, with aninto_owned()escape hatch when the caller needs to retain data past the input buffer’s lifetime - Optional
serdeandrkyvfeature flags to (de)serialize parsedRequest/Responsetypes for logging, persistence, or zero-copy IPC without a mandatory dependency
Common Use Cases
- Implementing the wire-protocol layer of a custom SMTP or LMTP server, replacing hand-written line parsing with typed, tested command decoding
- Building SMTP proxies or relays that need to inspect, filter, or rewrite MAIL/RCPT parameters before forwarding a session
- Generating well-formed multi-line SMTP responses (including enhanced status codes) from a server implementation via the response
generatemodule - Persisting or replaying parsed SMTP transactions using the
serde/rkyvfeature flags for testing, auditing, or crash recovery
Under The Hood
Architecture
The crate is organized into two mirrored subsystems — request (client-to-server commands) and response (server-to-client replies) — each split into a byte-level state-machine parser and either a streaming receiver (request/receiver.rs) or an output generator (response/generate.rs). Parsing is driven off a byte iterator rather than a complete buffer slice, allowing incremental consumption; RequestReceiver wraps the stateless Request::parse function with a small internal buffer so callers can feed bytes as they arrive over the network and receive Error::NeedsMoreData until a full command is available. Commands and extension keywords are represented as compact bit-packed integers generated by define_tokens_64!/define_tokens_128! macros in tokens.rs and matched via integer comparison rather than string comparison, keeping the hot parsing path allocation-free. Public types (Request<T>, Response<T>, MailFrom<T>, RcptTo<T>) are generic over a string-like T, letting the crate hand back borrowed Cow<str> slices with an into_owned() escape hatch for callers who need data to outlive the input buffer.
Tech Stack
The crate targets Rust edition 2024 with only two optional dependencies declared in Cargo.toml — rkyv 0.8 for zero-copy archival serialization and serde 1.0 with derive for conventional serialization — both gated behind matching feature flags so a default build carries zero external dependencies. There is no bundled async runtime; the crate works purely on byte slices and iterators, leaving transport and async I/O entirely to the consumer. A fuzz/ subdirectory wires up cargo-fuzz with a dedicated fuzz target exercising the parser against arbitrary byte input. CI runs cargo build and cargo test via GitHub Actions on push and pull requests against main.
Code Quality
The parser modules carry substantial #[cfg(test)] blocks with table-driven test cases pairing input strings against expected Ok/Err results, covering syntax variations for most command and parameter combinations — this doubles as the crate’s primary usage documentation given the sparse prose docs. Errors are modeled as a typed, largely non-allocating Error enum implementing std::error::Error and Display, avoiding stringly-typed or silently swallowed failures. There is no dedicated integration-test directory or linter configuration beyond #![deny(rust_2018_idioms)] in lib.rs, but formatting is consistent with default rustfmt conventions throughout.
API Design
The public surface is compact: callers either invoke Request::parse directly against a complete buffer or wrap it in RequestReceiver for streaming input, and both paths return the same Request<Cow<str>> type so switching between one-shot and streaming usage requires no downstream code changes. Naming follows SMTP RFC vocabulary directly (Ehlo, Mail, Rcpt, Bdat), which helps anyone already familiar with the protocol. Documentation is thin — the README states the crate isn’t yet documented and points readers to GitHub Discussions for help — with no dedicated examples directory, so the test suite serves as the de facto usage reference.