daaki-smtp 0.2.0

An async SMTP client library
Documentation
//! `daaki` SMTP client library.
//!
//! An async SMTP client (RFC 5321) built on tokio and rustls.
//! Handles connection, authentication, and message transmission. Does NOT
//! construct RFC 5322 messages — it sends raw bytes provided by the caller.
//!
//! # Connection ownership
//!
//! [`SmtpConnection`] methods take `&self` — the connection can be shared
//! across async tasks via `Arc<SmtpConnection>`. An internal
//! `tokio::sync::Mutex` serializes operations, since SMTP is a strictly
//! serial protocol (RFC 5321 Section 3.1) with no command tagging.
//!
//! [`daaki_imap::ImapConnection`] also takes `&self` but achieves
//! concurrency-safety through a driver-task architecture instead: a
//! dedicated tokio task owns the stream, and the handle communicates
//! via channels (RFC 3501 Section 5.5 permits overlapping commands via
//! unique tags, so true pipelining is possible).

// `fuzzing` cfg is set by cargo-fuzz (nightly) — not known to check-cfg.
#![cfg_attr(not(fuzzing), allow(unexpected_cfgs))]

pub mod error;
pub mod types;

mod codec;
mod connection;
mod deliver_by;
mod future_release;

pub use connection::{SmtpConnection, TlsMode};
/// Re-export the canonical `Address` type from `daaki-message` so consumers
/// can use a single `Address` type across the IMAP, SMTP, and message crates
/// without manual field-by-field conversion.
pub use daaki_message::Address;
pub use error::Error;
pub use types::{
    AddressLiteral, AuthMechanism, BodyType, DeliverBy, DeliverByMode, Domain, DomainOrLiteral,
    DsnNotify, DsnRet, EnhancedStatusCode, EnvidValue, ForwardPath, LmtpSendResult, MailFromParams,
    Mailbox, Protocol, RcptToParams, RecipientResult, RejectedRecipient, ReversePath, SendResult,
    ServerCapabilities, SmtpAuthParam, SmtpExtension, SmtpResponse, ValidationError, XtextSafe,
};

/// Result type alias for SMTP operations.
pub type Result<T> = std::result::Result<T, Error>;

/// Fuzz-only entry points. Not part of the public API.
///
/// Exposed behind `#[cfg(fuzzing)]` (set automatically by `cargo-fuzz`) so
/// that out-of-crate fuzz harnesses can reach the `pub(crate)` codec parsers.
#[allow(unexpected_cfgs)]
#[cfg(fuzzing)]
#[doc(hidden)]
pub mod fuzz {
    use crate::codec::decode;

    /// Thin wrapper around [`decode::parse_response`].
    ///
    /// Discards nom's remaining-input slice and returns just the parsed
    /// [`SmtpResponse`](crate::types::SmtpResponse), or `None` on any
    /// parse failure.
    pub fn parse_response(input: &[u8]) -> Option<crate::types::SmtpResponse> {
        decode::parse_response(input).ok().map(|(_, r)| r)
    }

    /// Thin wrapper around [`decode::parse_ehlo_capabilities`].
    ///
    /// Parses EHLO response lines into structured server capabilities
    /// per RFC 5321 Section 4.1.1.1.
    pub fn parse_ehlo_capabilities(
        response: &crate::types::SmtpResponse,
    ) -> crate::types::ServerCapabilities {
        decode::parse_ehlo_capabilities(response)
    }

    /// Thin wrapper around [`decode::strip_enhanced_code`].
    ///
    /// Extracts an RFC 2034 enhanced status code from the beginning of
    /// a response text line, returning the code and the remaining text.
    pub fn strip_enhanced_code(
        text: &str,
        reply_code: u16,
    ) -> Option<(crate::types::EnhancedStatusCode, String)> {
        decode::strip_enhanced_code(text, reply_code).map(|(esc, rest)| (esc, rest.to_owned()))
    }

    /// Thin wrapper around [`decode::parse_enhanced_code_from_str`].
    ///
    /// Parses a standalone RFC 2034 enhanced status code string
    /// (e.g. `"2.1.0"`).
    pub fn parse_enhanced_code_from_str(s: &str) -> Option<crate::types::EnhancedStatusCode> {
        decode::parse_enhanced_code_from_str(s)
    }
}

/// Consumer-facing README examples must compile against the current public API.
///
/// This turns the README into executable doctests so stale helper signatures
/// and example code are caught during `cargo test --doc`.
#[cfg(doctest)]
#[doc = include_str!("../README.md")]
mod readme_doctests {}

#[cfg(test)]
mod tests {
    /// Consumer-facing README claims must stay aligned with the implemented
    /// SMTP extension surface. RFC 4954 Section 4 already allows initial
    /// responses on AUTH, so the legacy `SASL-IR` EHLO keyword must not be
    /// presented as a distinct SMTP extension in the support table.
    #[test]
    fn readme_does_not_advertise_sasl_ir_as_smtp_extension() {
        let readme = include_str!("../README.md");
        let auth_row = readme
            .lines()
            .find(|line| line.contains("| **Auth** |"))
            .unwrap_or_default();
        assert!(
            !auth_row.contains("SASL-IR"),
            "README must not present legacy SASL-IR as a standard SMTP extension"
        );
    }
}