Skip to main content

SmtpClient

Struct SmtpClient 

Source
pub struct SmtpClient<T: Transport> { /* private fields */ }
Expand description

SMTP client driving a single connection.

See the module-level documentation for the full lifecycle.

Implementations§

Source§

impl<T: Transport> SmtpClient<T>

Source

pub async fn login(&mut self, user: &str, pass: &str) -> Result<(), SmtpError>

Authenticate using the best AUTH mechanism the server advertised.

PLAIN is preferred over LOGIN when both are advertised, because it completes in a single round-trip and is the IETF-standard SASL mechanism. LOGIN is used as a fallback for older servers that only advertise it. Callers that need to lock in a specific mechanism (for testing, or for known-broken servers) should call Self::login_with instead.

Returns AuthError::UnsupportedMechanism if the server’s EHLO reply did not advertise either PLAIN or LOGIN. Returns AuthError::Rejected if the server rejects the credentials.

May only be called immediately after Self::connect. Calling it a second time, or after Self::send_mail, returns InvalidInputError.

§Credential lifetime and zeroization

wasm-smtp does not retain copies of user or pass after this call returns: the credentials are passed by reference, used once to build a base64-encoded SASL payload, and dropped together with that payload at the end of the call. The crate also never includes credentials in Debug output, error messages, or Display text.

What the crate cannot do is securely erase the bytes the caller supplied — that storage belongs to the caller. If your threat model includes memory disclosure (a process dump, a debugger attached to the running Worker, etc.), wrap the password in a type that zeroes its backing memory on drop (the zeroize crate is the conventional choice) and pass &z.expose_secret() only at the call site. Concretely, avoid pulling the password out of an environment variable into a long-lived String.

Source

pub async fn login_with( &mut self, mechanism: AuthMechanism, user: &str, credential: &str, ) -> Result<(), SmtpError>

Authenticate using a specific AUTH mechanism.

Use this when Self::login’s auto-selection is not what you want — for example, when reproducing a production failure that is specific to one mechanism, or when testing against a server whose advertisement is known to be inaccurate.

credential is the secret material whose meaning depends on the mechanism: a static password for Plain and Login, or an OAuth 2.0 access token for XOAuth2 (the latter requires the xoauth2 cargo feature). The user parameter is validated against rules appropriate to the mechanism (NUL bytes rejected for SASL framing in Plain / Login, additional control bytes rejected for XOAuth2).

Returns AuthError::UnsupportedMechanism if mechanism was not advertised by the server. Returns AuthError::Rejected if the server rejects the credentials.

When the xoauth2 feature is disabled and the caller passes AuthMechanism::XOAuth2, this returns InvalidInputError without performing any I/O — the variant remains in the public enum (it is non_exhaustive) but the code path is removed.

Source

pub async fn login_xoauth2( &mut self, user: &str, access_token: &str, ) -> Result<(), SmtpError>

Authenticate with XOAUTH2, the Google / Microsoft OAuth 2.0 SASL profile.

user is the email address of the account, access_token is a short-lived OAuth 2.0 bearer token obtained via the OAuth flow for that account. This crate does not perform the OAuth dance itself — token acquisition, refresh, and storage are the caller’s responsibility.

Convenience wrapper for login_with(AuthMechanism::XOAuth2, user, access_token). Note that Self::login (the auto-selecting variant) deliberately does not pick XOAUTH2 even when the server advertises it, because the credential semantics are different from a static password.

§Errors
  • AuthError::UnsupportedMechanism if the server did not advertise AUTH XOAUTH2.
  • AuthError::Rejected if the server rejected the token. Google and Microsoft typically return a 535 with a base64- encoded JSON {"status":"401","schemes":"Bearer","scope":"..."} in the message; the parsed text is preserved in the error.

Available only with the xoauth2 cargo feature enabled (default-on).

Source

pub async fn login_oauthbearer( &mut self, user: &str, access_token: &str, ) -> Result<(), SmtpError>

Authenticate with OAUTHBEARER (RFC 7628), the IETF-standard OAuth 2.0 SASL mechanism.

user is the authorization identity (typically the account email address); access_token is a short-lived OAuth 2.0 bearer token.

Unlike XOAUTH2, OAUTHBEARER follows the GS2 framing from RFC 5801, making it interoperable with any compliant SASL library.

Convenience wrapper for login_with(AuthMechanism::OAuthBearer, user, access_token).

§Errors

Available only with the oauthbearer cargo feature (default-on).

Source§

impl<T: Transport> SmtpClient<T>

Source

pub async fn send_mail( &mut self, from: &str, to: &[&str], body: &str, ) -> Result<SendOutcome, SmtpError>

Send a single message.

from is the envelope sender (RFC 5321 reverse-path), used in the MAIL FROM:<...> command. to is a non-empty slice of envelope recipients (forward-paths). body is the fully-formed message, including all RFC 5322 headers, separated from the body proper by a blank line, and CRLF-normalized. Any line in body whose first character is . is automatically dot-stuffed before transmission.

On success the client is left in a state where another send_mail may be issued, or quit may be called to close the session.

§Body size

wasm-smtp does not impose an upper bound on body.len(); the body is dot-stuffed into a single Vec<u8> and written in one crate::Transport::write_all call. In practice the caller (or a layer above this crate) should enforce a sane application-specific limit, both to avoid the allocation cost on a malicious body and to stay within the SIZE limit (RFC 1870) the server may have advertised in its EHLO response. A typical safe default for transactional mail is 10 MiB; submission relays such as Gmail enforce 25-50 MiB.

Source

pub async fn send_mail_bytes( &mut self, from: &str, to: &[&str], body: &[u8], ) -> Result<SendOutcome, SmtpError>

Send a single message supplied as a raw byte slice.

Identical to Self::send_mail except that body is &[u8] rather than &str. Use this when the message has already been serialised to bytes by a builder such as mail-builder or when the body may contain non-UTF-8 octets (e.g. binary attachments encoded as base64 within a MIME part that uses a legacy charset).

§Body requirements

body must be a fully composed RFC 5322 message — headers, a blank line, and content — with CRLF line endings. Self::send_mail has the same requirement; the difference is that send_mail_bytes skips the UTF-8 validity check on the input slice.

Dot-stuffing and the end-of-data terminator (\r\n.\r\n) are applied automatically, exactly as in send_mail.

§Policy and audit

The pre-send policy checks and audit events are identical to those fired by send_mail. check_message_size receives body.len() (the raw byte length before dot-stuffing).

§Errors

Same categories as Self::send_mail.

Source

pub async fn send_mail_stream<B>( &mut self, from: &str, to: &[&str], body: &mut B, ) -> Result<SendOutcome, SmtpError>
where B: MessageBody,

Send a message supplied as a [MessageBody] stream.

This is the streaming variant of Self::send_mail_bytes. The body is read in chunks of chunk_size bytes (default: 8 KB), dot-stuffed, and written to the transport incrementally. Peak memory usage is O(chunk_size) rather than O(body size), making this suitable for large messages and memory-constrained runtimes.

§Body requirements

The body must be a fully composed RFC 5322 message (headers + blank line + content) with CRLF line endings. Dot-stuffing and the end-of-data terminator are applied automatically.

§Policy and audit

check_sender and check_recipients run before any SMTP command. check_message_size is called with usize::MAX because the total body size is unknown in advance; callers that need accurate size enforcement should use Self::send_mail_bytes instead.

Audit events are identical to Self::send_mail.

§Errors

Same as Self::send_mail, plus:

  • SmtpError::Io if body.read_chunk returns an error. The session is moved to Closed.
Source§

impl<T: StartTlsCapable> SmtpClient<T>

Source

pub async fn connect_starttls( transport: T, ehlo_domain: &str, ) -> Result<Self, SmtpError>

Connect, read the greeting, send EHLO, issue STARTTLS, upgrade the transport to TLS, and re-issue EHLO on the secure stream.

This is the convenience entry point for the STARTTLS submission flow on ports 587 / 25. The returned client is in SessionState::Authentication just like one returned by Self::connect would be — meaning the caller proceeds with Self::login (or skips straight to Self::send_mail for unauthenticated submission) without observing the TLS upgrade itself.

Use Self::connect for Implicit TLS on port 465 instead. STARTTLS is appropriate when the transport must remain plaintext until the server has accepted the upgrade request.

§Errors

Returns the same error categories as Self::connect for the pre-upgrade phase. Additionally:

Source

pub async fn starttls(&mut self) -> Result<(), SmtpError>

Issue STARTTLS on an already-connected client, upgrade the transport, and re-issue EHLO per RFC 3207 §4.2.

May only be called immediately after Self::connect. Calling it after Self::login or Self::send_mail returns [InvalidInputError] without touching the wire.

§Errors
Source§

impl<T: Transport> SmtpClient<T>

Source

pub async fn connect(transport: T, ehlo_domain: &str) -> Result<Self, SmtpError>

Connect by reading the server greeting and performing the EHLO handshake.

transport must already be connected and, if Implicit TLS is in use, already past the TLS handshake. ehlo_domain is the FQDN or address literal that identifies the client to the server.

On success the client is in a state where Self::login or Self::send_mail may be called.

Uses DefaultPolicy (allow all) and NoopAuditSink (no events). To attach a policy or audit sink, use SmtpClientOptions with Self::connect_with.

Source

pub async fn connect_with( transport: T, ehlo_domain: &str, options: SmtpClientOptions, ) -> Result<Self, SmtpError>

Connect with explicit policy and audit options.

let opts = SmtpClientOptions::new()
    .with_policy(Box::new(BoundedPolicy::new().max_recipients(50)));
// let client = SmtpClient::connect_with(transport, "client.example.com", opts).await?;
Source

pub fn capabilities(&self) -> &[String]

The capability lines returned by the server in its EHLO reply.

The first reply line (the greeting) is excluded; each remaining entry is one advertised extension, for example "AUTH LOGIN PLAIN", "PIPELINING", or "8BITMIME".

Source

pub fn state(&self) -> SessionState

The current session state. Mostly useful for diagnostics and tests.

Source

pub async fn quit(self) -> Result<(), SmtpError>

Authenticate using the best AUTH mechanism the server advertised.

PLAIN is preferred over LOGIN when both are advertised, because it completes in a single round-trip and is the IETF-standard SASL mechanism. LOGIN is used as a fallback for older servers that only advertise it. Callers that need to lock in a specific mechanism (for testing, or for known-broken servers) should call Self::login_with instead.

Returns AuthError::UnsupportedMechanism if the server’s EHLO reply did not advertise either PLAIN or LOGIN. Returns AuthError::Rejected if the server rejects the credentials.

May only be called immediately after Self::connect. Calling it a second time, or after Self::send_mail, returns InvalidInputError.

§Credential lifetime and zeroization

wasm-smtp does not retain copies of user or pass after this call returns: the credentials are passed by reference, used once to build a base64-encoded SASL payload, and dropped together with that payload at the end of the call. The crate also never includes credentials in Debug output, error messages, or Display text.

What the crate cannot do is securely erase the bytes the caller supplied — that storage belongs to the caller. If your threat model includes memory disclosure (a process dump, a debugger attached to the running Worker, etc.), wrap the password in a type that zeroes its backing memory on drop (the zeroize crate is the conventional choice) and pass &z.expose_secret() only at the call site. Concretely, avoid pulling the password out of an environment variable into a long-lived String. Send QUIT and close the transport.

Consumes self so the client cannot be reused after a clean shutdown. If the underlying transport’s close fails, the SMTP QUIT may still have completed cleanly; the returned error wraps the transport-level failure.

Trait Implementations§

Source§

impl<T: Transport> Debug for SmtpClient<T>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<T> Freeze for SmtpClient<T>
where T: Freeze,

§

impl<T> !RefUnwindSafe for SmtpClient<T>

§

impl<T> Send for SmtpClient<T>
where T: Send,

§

impl<T> Sync for SmtpClient<T>
where T: Sync,

§

impl<T> Unpin for SmtpClient<T>
where T: Unpin,

§

impl<T> UnsafeUnpin for SmtpClient<T>
where T: UnsafeUnpin,

§

impl<T> !UnwindSafe for SmtpClient<T>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.