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 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.

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 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-core 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 send_mail( &mut self, from: &str, to: &[&str], body: &str, ) -> Result<(), 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-core 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 quit(self) -> Result<(), SmtpError>

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.

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

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>
where T: RefUnwindSafe,

§

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>
where T: UnwindSafe,

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, 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.