wasm_smtp_wasi/lib.rs
1//! # wasm-smtp-wasi
2//!
3//! WASI sockets adapter for [`wasm-smtp`], targeting `wasm32-wasip2`
4//! (WASI 0.2 Component Model) runtimes such as wasmtime and WAMR.
5//!
6//! ## Quick start
7//!
8//! ```rust,no_run
9//! use wasm_smtp_wasi::connect_smtps;
10//!
11//! # async fn run() -> Result<(), wasm_smtp::SmtpError> {
12//! // Implicit TLS on port 465 (recommended).
13//! let mut client = connect_smtps(
14//! "smtp.example.com",
15//! 465,
16//! "client.example.com",
17//! ).await?;
18//!
19//! client.login("user@example.com", "secret").await?;
20//! client.send_mail(
21//! "user@example.com",
22//! &["recipient@example.org"],
23//! "Subject: hello\r\n\r\nBody.\r\n",
24//! ).await?;
25//! client.quit().await?;
26//! # Ok(())
27//! # }
28//! ```
29//!
30//! ## TLS
31//!
32//! TLS is handled by [rustls] on top of the WASI byte streams. Certificate
33//! validation is enforced and cannot be disabled through the public API.
34//! Trust anchors come from the Mozilla root CA set (`webpki-roots` feature,
35//! default) or from the OS at runtime (`native-roots` feature, not useful on
36//! most WASI runtimes).
37//!
38//! ## STARTTLS
39//!
40//! Port 587 STARTTLS is supported via [`connect_smtp_starttls`].
41//! The TLS upgrade is performed by rustls after the SMTP `STARTTLS`
42//! handshake.
43//!
44//! ## Build target
45//!
46//! This crate is designed for `wasm32-wasip2`. Building for other targets
47//! is only useful for running unit tests; the connection helpers will
48//! return a compile-time error on non-WASM targets in release builds.
49//!
50//! ```text
51//! cargo build --target wasm32-wasip2 -p wasm-smtp-wasi
52//! ```
53//!
54//! To run tests on native (without a WASI runtime):
55//!
56//! ```text
57//! cargo test -p wasm-smtp-wasi
58//! ```
59//!
60//! ## Feature flags
61//!
62//! | Flag | Default | Description |
63//! |---|---|---|
64//! | `webpki-roots` | ✅ | Bundle Mozilla root CA set |
65//! | `native-roots` | ❌ | Use OS trust store (falls back to webpki-roots on WASI) |
66//! | `plaintext-only` | ❌ | **Test / proxy-offload only.** No TLS. |
67//!
68//! ## Limitations
69//!
70//! - `wasm32-wasip2` target stdlib must be installed.
71//! - No connection pooling (one transport = one TCP connection).
72//! - No built-in retry or timeout logic.
73//!
74//! [rustls]: https://docs.rs/rustls
75
76mod error;
77mod tls;
78
79#[cfg(target_arch = "wasm32")]
80mod wasi_impl;
81
82mod tests;
83
84// Public re-exports.
85pub use error::WasiSmtpError;
86pub use tls::ConnectOptions;
87
88#[cfg(target_arch = "wasm32")]
89use wasm_smtp::SmtpClient;
90
91#[cfg(target_arch = "wasm32")]
92use wasm_smtp::SmtpError;
93
94#[cfg(target_arch = "wasm32")]
95use wasi_impl::WasiTlsTransport;
96
97// ---------------------------------------------------------------------------
98// Public connect helpers
99// ---------------------------------------------------------------------------
100
101/// Connect with Implicit TLS (port 465) and complete the SMTP greeting
102/// and `EHLO` exchange.
103///
104/// Returns a ready-to-use [`SmtpClient`] after the greeting and `EHLO`.
105/// Call [`SmtpClient::login`] next.
106///
107/// # Errors
108///
109/// - [`SmtpError::Io`] on DNS lookup failure, TCP connect failure, or TLS
110/// handshake failure.
111/// - [`SmtpError::Protocol`] if the server greeting or `EHLO` response is
112/// unexpected.
113#[cfg(target_arch = "wasm32")]
114pub async fn connect_smtps(
115 host: &str,
116 port: u16,
117 ehlo_domain: &str,
118) -> Result<SmtpClient<WasiTlsTransport>, SmtpError> {
119 let transport = WasiTlsTransport::connect_implicit_tls(host, port, ConnectOptions::default())
120 .await
121 .map_err(|e| SmtpError::Io(e.into()))?;
122 SmtpClient::connect(transport, ehlo_domain).await
123}
124
125/// Connect with Implicit TLS using custom [`ConnectOptions`].
126#[cfg(target_arch = "wasm32")]
127pub async fn connect_smtps_with(
128 host: &str,
129 port: u16,
130 ehlo_domain: &str,
131 options: ConnectOptions,
132) -> Result<SmtpClient<WasiTlsTransport>, SmtpError> {
133 let transport = WasiTlsTransport::connect_implicit_tls(host, port, options)
134 .await
135 .map_err(|e| SmtpError::Io(e.into()))?;
136 SmtpClient::connect(transport, ehlo_domain).await
137}
138
139/// Connect with STARTTLS (port 587): plaintext TCP → `STARTTLS` upgrade.
140///
141/// Drives the SMTP greeting, `EHLO`, and `STARTTLS` upgrade. Returns a
142/// ready-to-use [`SmtpClient`] after the post-TLS `EHLO`.
143#[cfg(target_arch = "wasm32")]
144pub async fn connect_smtp_starttls(
145 host: &str,
146 port: u16,
147 ehlo_domain: &str,
148) -> Result<SmtpClient<WasiTlsTransport>, SmtpError> {
149 let transport = WasiTlsTransport::connect_plain(host, port)
150 .await
151 .map_err(|e| SmtpError::Io(e.into()))?;
152 SmtpClient::connect_starttls(transport, ehlo_domain).await
153}
154