I/O SMTP

SMTP client library, written in Rust
This library is composed of 3 feature-gated layers:
- Low-level I/O-free coroutines: these
no_std-compatible state machines contain the whole SMTP logic and can be used anywhere - Mid-level light client: a standard, blocking SMTP client using a
Stream: Read + Write - High-level full client: light client + TCP connections and TLS negotiations handled for you
Table of contents
Features
- I/O-free coroutines:
no_stdstate machines; no sockets, no async runtime, nostdrequired, drive against any blocking, async, or fuzz harness. - Light standard, blocking client (requires
clientfeature) - Full standard, blocking client with TLS support:
- Rustls with ring crypto (requires
rustls-ringfeature) - Rustls with aws crypto (requires
rustls-awsfeature) - Native TLS (requires
native-tlsfeature)
- Rustls with ring crypto (requires
- SASL mechanisms:
ANONYMOUS,LOGIN,PLAIN,XOAUTH2andOAUTHBEARERbuilt-inSCRAM-SHA-256(requiresscramfeature)
- SMTP extensions:
STARTTLS,AUTH,SIZE,DSN,ENHANCEDSTATUSCODES(see RFC coverage)
[!TIP] I/O SMTP is written in Rust and uses cargo features to gate backend support. The default feature set is declared in Cargo.toml or on docs.rs.
RFC coverage
| Module | What it covers |
|---|---|
| 1870 | SIZE: maximum message size declaration |
| 3207 | STARTTLS: upgrade a plain connection to TLS |
| 3461 | DSN: RET, ENVID, NOTIFY, ORCPT ESMTP parameters for MAIL FROM / RCPT TO |
| 3463 | Enhanced status codes: EnhancedStatusCode type |
| 4954 | AUTH: SASL exchange protocol (verb, command, continuation data) |
| 5321 | SMTP: greeting, EHLO, HELO, MAIL FROM, RCPT TO, DATA, NOOP, RSET, QUIT |
| 7628 | OAUTHBEARER: OAuth 2.0 bearer token SASL mechanism |
| 7677 | SCRAM-SHA-256: SASL SCRAM-SHA-256 mechanism (feature scram) |
sasl::auth_anonymous (4505) |
ANONYMOUS: SASL ANONYMOUS mechanism |
sasl::auth_login |
LOGIN: legacy de-facto AUTH mechanism (no RFC) |
sasl::auth_plain (4616) |
PLAIN: SASL PLAIN authentication mechanism |
sasl::auth_xoauth2 |
XOAUTH2: Google's pre-standard OAuth 2.0 SASL mechanism (no RFC) |
Usage
I/O-SMTP can be consumed three ways, depending on how much of the I/O stack you want to own. Each mode is gated by cargo features.
Whichever mode you pick, every coroutine implements the SmtpCoroutine trait. Its resume(Option<&[u8]>) method returns SmtpCoroutineState<Yield, Return> with two shapes:
Yielded(Yield): intermediate progress. Every coroutine in this crate picks the standardSmtpYield(WantsRead/WantsWrite(Vec<u8>)); the caller reads or writes bytes accordingly. PassSome(&[])to signal EOF on the next resume.Complete(Return): terminal value. By conventionReturn = Result<Output, Error>where the ok arm carries the coroutine's final output and the error arm carries the cause.SmtpStartTlsusesResult<Vec<u8>, _>: the ok arm'sVec<u8>carries any bytes the coroutine pre-read past the220reply (a non-empty value signals STARTTLS-injection per RFC 3207 §6).
Each higher-level coroutine internally delegates to a shared SendSmtpCommand<Cmd> base coroutine (in crate::send) that owns the serialise → write → read → parse loop.
Coroutine
No features required: works in #![no_std], no sockets, no async runtime. You own the loop and the bytes; the library only produces command bytes and consumes server responses.
Read the SMTP greeting against a blocking TCP socket (the same shape works under async, fuzzing, or in-memory replay):
use ;
use ;
let mut stream = connect.unwrap;
let mut buf = ;
let mut coroutine = new;
let mut arg: = None;
let greeting = loop ;
println!;
Drive a multi-step command (EHLO) the same way:
use ;
use ;
# let mut stream = connect.unwrap;
# let mut buf = ;
let domain = Domain;
let mut coroutine = new;
let mut arg: = None;
let capabilities = loop ;
for line in capabilities
Light client
Enable the client feature. SmtpClientStd::new(stream) wraps any blocking Read + Write and exposes one method per SMTP command. You still open the TCP socket, run TLS / STARTTLS yourself, and hand over a ready-to-talk stream; the client takes it from there.
[]
= { = "0.1.0", = false, = ["client"] }
use ;
use ;
Full client
Enable one of the TLS feature flags: rustls-ring (default), rustls-aws, or native-tls. SmtpClientStd::connect(url, tls, starttls, domain, sasl) opens smtp:// (plain TCP) or smtps:// (implicit TLS) via pimalaya/stream, reads the greeting, sends the initial EHLO, drives the optional STARTTLS upgrade plus a fresh EHLO over TLS, then runs the chosen SASL mechanism, returning a ready-to-use authenticated client.
[]
= { = "0.1.0", = false, = ["rustls-ring"] }
use ;
use ;
use ;
use SecretString;
use Url;
The sasl argument is Option<impl Into<Sasl>>, so any of the per-mechanism structs (SaslLogin, SaslPlain, SaslOauthbearer, SaslScramSha256 behind the scram feature) can be passed in Some(...) directly without wrapping in a Sasl variant. SaslAnonymous and SaslXoauth2 are not supported by SMTP.
Examples
See complete examples at ./examples.
Have also a look at real-world projects built on top of this library:
- Himalaya CLI: CLI to manage emails
- Himalaya TUI: TUI to manage emails
- Sirup: CLI to spawn pre-authenticated IMAP/SMTP sessions and expose them via Unix sockets
AI disclosure
This project is developed with AI assistance. This section documents how, so users and downstream packagers can make informed decisions.
-
Tools: Claude Code (Anthropic), Opus 4.7, invoked locally with a persistent project-scoped memory and a small set of repo-specific rules.
-
Used for: Refactors, mechanical multi-file edits, boilerplate (feature gates, error enums, derive macros, trait impls), test scaffolding, doc polish, exploratory design conversations.
-
Not used for: Engineering, critical code, git manipulation (commit, merge, rebase…), real-world tests.
-
Verification: Every AI-assisted change is read, compiled, tested, and formatted before commit (
nix develop --command cargo check / cargo test / cargo fmt). Behavioural correctness is verified against the relevant RFC or upstream spec, not assumed from the model output. Tests are never adjusted to fit AI-generated code; the code is adjusted to fit correct behaviour. -
Limitations: AI models occasionally produce code that compiles and passes tests but is subtly wrong: off-by-one errors, missed edge cases, plausible but nonexistent APIs, stale RFC references. The verification workflow catches most of this; it does not catch all of it. Bug reports are welcome and taken seriously.
-
Last reviewed: 03/06/2026
License
This project is licensed under either of:
at your option.
Social
- Chat on Matrix
- News on Mastodon or RSS
- Mail at pimalaya.org@posteo.net
Sponsoring
Special thanks to the NLnet foundation and the European Commission that have been financially supporting the project for years:
- 2022 → 2023: NGI Assure
- 2023 → 2024: NGI Zero Entrust
- 2024 → 2026: NGI Zero Core
- 2027 in preparation…
If you appreciate the project, feel free to donate using one of the following providers:
