wasm-smtp 0.15.0

Environment-independent SMTP client core for WASM and other constrained runtimes.
Documentation

wasm-smtp

License
crates.io Rust Documentation Dependency Status
crates.io Rust Documentation Dependency Status crates.io Rust Documentation Dependency Status crates.io Rust Documentation Dependency Status crates.io Rust Documentation Dependency Status

Rust crates for sending mail by SMTP from WebAssembly runtimes. The project separates the protocol implementation from the runtime-specific socket code so that the same SMTP engine can be reused on every host.

Crates

Crate Role Status
wasm-smtp Environment-independent SMTP state machine and parser. Implemented
wasm-smtp-cloudflare Cloudflare Workers socket adapter for wasm-smtp. Implemented
wasm-smtp-tokio Tokio + rustls socket adapter for wasm-smtp. Implemented
wasm-smtp-wasi WASI 0.2 sockets adapter (wasm32-wasip2). Implemented
wasm-smtp-component WASM Component Model WIT interface (wit/smtp.wit). Implemented

wasm-smtp is the foundation: it implements the SMTP state machine, response parsing, command formatting, dot-stuffing, and error classification, but does no I/O of its own. Each runtime gets its own adapter crate that provides a Transport implementation.

Four adapters ship today:

  • wasm-smtp-cloudflare — Cloudflare Workers (WASM target).
  • wasm-smtp-tokio — tokio-based servers (axum, actix, warp, hyper, plain tokio, …).
  • wasm-smtp-wasi — WASI 0.2 runtimes (wasmtime, WAMR) targeting wasm32-wasip2.
  • wasm-smtp-component — WASM Component Model WIT interface (wit/smtp.wit), enabling language-neutral bindings (TypeScript, Go, Python, C, …).

Minimum usage

From a Cloudflare Worker (the production target):

use wasm_smtp_cloudflare::connect_smtps;

# async fn run() -> Result<(), wasm_smtp_cloudflare::SmtpError> {
let mut client =
    connect_smtps("smtp.example.com", 465, "client.example.com").await?;
client.login("user@example.com", "secret").await?;
client.send_mail(
    "user@example.com",
    &["recipient@example.org"],
    "From: user@example.com\r\n\
     To: recipient@example.org\r\n\
     Subject: Hello\r\n\
     \r\n\
     Body text.\r\n",
).await?;
client.quit().await?;
# Ok(())
# }

Or directly against wasm-smtp with any Transport you supply:

use wasm_smtp::{SmtpClient, Transport};

async fn send<T: Transport>(transport: T) -> Result<(), wasm_smtp::SmtpError> {
    let mut client = SmtpClient::connect(transport, "client.example.com").await?;
    client.login("user@example.com", "secret").await?;
    client.send_mail(
        "user@example.com",
        &["recipient@example.org"],
        "From: user@example.com\r\n\
         To: recipient@example.org\r\n\
         Subject: Hello\r\n\
         \r\n\
         Body text.\r\n",
    ).await?;
    client.quit().await?;
    Ok(())
}

The body argument is a fully-formed RFC 5322 message: headers, a blank line, then the body, with CRLF line endings. The library does not build MIME, attach files, or compose multipart bodies.

Connection model

Two TLS models are supported:

  • Implicit TLS on port 465 — the runtime negotiates TLS before any SMTP byte is exchanged. Use connect_smtps.
  • STARTTLS on port 587 — the connection starts plaintext and is upgraded to TLS in-place after the SMTP greeting. Use connect_smtp_starttls.

In both cases the TLS handshake is the responsibility of the Transport implementation; wasm-smtp sees an opaque byte stream and (for STARTTLS) a single upgrade_to_tls() signal.

Cargo features

wasm-smtp exposes two cargo features that allow size-sensitive deployments (Cloudflare Workers' 3 MiB cap, in particular) to opt out of functionality they will not use:

Feature Default What it adds
xoauth2 on SmtpClient::login_xoauth2, AuthMechanism::XOAuth2 code paths, OAuth 2.0 token validation helpers
oauthbearer on SmtpClient::login_oauthbearer, AuthMechanism::OAuthBearer (RFC 7628 IETF-standard OAuth 2.0 SASL)
pipelining on Batch MAIL FROM + RCPT TO + DATA when server advertises PIPELINING (RFC 2920)
smtputf8 off SmtpClient::send_mail_smtputf8, validate_address_utf8, format_mail_from_smtputf8, capability check

Defaults are chosen so that v0.3.x users see no behavior change on upgrade. To strip OAuth 2.0 support entirely (typical for transactional senders against a self-hosted Postfix or commercial relay using static passwords):

wasm-smtp = { version = "0.9", default-features = false }

To opt into international addresses while keeping the OAuth 2.0 support:

wasm-smtp = { version = "0.9", features = ["smtputf8"] }

The wasm-smtp-cloudflare adapter exposes a matching smtputf8 feature that pass-through-enables it on the core crate, so adapter- only callers do not need a direct dependency on wasm-smtp to opt in.

Acceptable use

This library must not be used to deliver unsolicited bulk mail, to impersonate other senders, or to deliver mail that violates the operating policy of any SMTP server. See TERMS_OF_USE.md.

Documentation

Long-form documentation lives in docs/src. The mdBook structure covers project architecture, the SMTP protocol surface, the error taxonomy, and end-to-end usage.