mailrs_smtp_client/lib.rs
1#![deny(missing_docs)]
2#![deny(rustdoc::broken_intra_doc_links)]
3
4//! Outbound SMTP client primitives: MX resolution, DANE/STARTTLS, response parsing.
5//!
6//! `mailrs-smtp-client` is the send-side counterpart to [`mailrs-smtp-proto`].
7//! It is not a full mail user agent — it is the wire-level pieces an MTA needs
8//! to deliver a message that has already been built: looking up MX records,
9//! choosing the right relay, opening a TLS connection that can be verified
10//! against DNSSEC-anchored TLSA records ([RFC 7672] DANE), and reading SMTP
11//! replies that wrap across multiple lines.
12//!
13//! Built on `tokio` + `rustls` + [`hickory_resolver`]. Extracted from
14//! [mailrs], a Rust mail server, and published independently so anyone
15//! writing an MTA, a delivery-test harness, or a bounce probe in Rust can
16//! share the same battle-tested foundation.
17//!
18//! # Quick start
19//!
20//! ```no_run
21//! use mailrs_smtp_client::{MxCache, SmtpConnection, TokioResolver, sort_mx_records};
22//! use std::time::Duration;
23//!
24//! # async fn run() -> Result<(), Box<dyn std::error::Error>> {
25//! let resolver = TokioResolver::builder_tokio()?.build()?;
26//! let cache = MxCache::new(Duration::from_secs(300));
27//!
28//! let mut records = cache.resolve(&resolver, "example.com").await?;
29//! sort_mx_records(&mut records);
30//! let primary = records.first().ok_or("no MX")?;
31//!
32//! // connect (reads the banner internally), EHLO, upgrade to TLS, then send
33//! let mut conn = SmtpConnection::connect(&primary.exchange, 25).await?;
34//! conn.ehlo("client.example.org").await?;
35//! let mut conn = conn.starttls(&primary.exchange).await?;
36//! conn.ehlo("client.example.org").await?;
37//! conn.deliver(
38//! "sender@example.org",
39//! &["bob@example.com"],
40//! b"Subject: hi\r\n\r\nhello\r\n",
41//! ).await?;
42//! conn.quit().await?;
43//! # Ok(()) }
44//! ```
45//!
46//! # Module overview
47//!
48//! - [`mx`] — DNS MX lookup with preference-sort, in-memory cache, and
49//! `A`-record fallback for domains without an MX.
50//! - [`dane`] — TLSA record resolution and certificate verification
51//! ([RFC 7672]) to defend STARTTLS against active MITM.
52//! - [`connection`] — [`SmtpConnection`] wraps the TLS-upgradable read/write
53//! loop with per-command timeouts ([`TimeoutConfig`]).
54//! - [`response`] — single- and multi-line reply parser
55//! ([RFC 5321 §4.2.1]).
56//!
57//! # What this crate does NOT do
58//!
59//! - No DKIM signing, no SPF, no DMARC. Use [`mail-auth`] upstream.
60//! - No queue / retry / DSN. See `mailrs-outbound-queue`.
61//! - No SMTP server. See [`mailrs-smtp-proto`] for the receive-side state machine.
62//!
63//! [RFC 5321 §4.2.1]: https://datatracker.ietf.org/doc/html/rfc5321#section-4.2.1
64//! [RFC 7672]: https://datatracker.ietf.org/doc/html/rfc7672
65//! [`mail-auth`]: https://crates.io/crates/mail-auth
66//! [`mailrs-smtp-proto`]: https://crates.io/crates/mailrs-smtp-proto
67//! [mailrs]: https://github.com/goliajp/mailrs
68
69/// `SmtpConnection` — async TLS / STARTTLS connection state machine + timeouts.
70pub mod connection;
71/// RFC 7672 DANE (DNS-Based Authentication of Named Entities): TLSA record
72/// lookup + TLS verification config.
73pub mod dane;
74/// MX record lookup + priority sorting + `fallback_to_domain` for
75/// MX-less destinations.
76pub mod mx;
77/// RFC 5321 multi-line response parser (`250-XXX` continuation lines).
78pub mod response;
79/// Structured outcomes of STARTTLS attempts — discriminated enum
80/// suitable for direct mapping to `mailrs_tls_rpt::FailureType`
81/// (RFC 8460 §4.3) and any downstream decision that wants more
82/// than an opaque `io::Error`.
83pub mod tls_outcome;
84
85pub use connection::{SmtpConnection, TimeoutConfig};
86pub use dane::{DaneVerifier, TlsaRecord, dane_tls_config, resolve_tlsa};
87pub use mx::{MxCache, MxRecord, TokioResolver, fallback_to_domain, resolve_mx, sort_mx_records};
88pub use response::{SmtpResponse, parse_response};
89pub use tls_outcome::{StarttlsResult, TlsOutcome};