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>
impl<T: Transport> SmtpClient<T>
Sourcepub async fn login(&mut self, user: &str, pass: &str) -> Result<(), SmtpError>
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.
Sourcepub async fn login_with(
&mut self,
mechanism: AuthMechanism,
user: &str,
credential: &str,
) -> Result<(), SmtpError>
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.
Sourcepub async fn login_xoauth2(
&mut self,
user: &str,
access_token: &str,
) -> Result<(), SmtpError>
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::UnsupportedMechanismif the server did not advertiseAUTH XOAUTH2.AuthError::Rejectedif 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).
Sourcepub async fn login_oauthbearer(
&mut self,
user: &str,
access_token: &str,
) -> Result<(), SmtpError>
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
AuthError::UnsupportedMechanismif the server did not advertiseAUTH OAUTHBEARER.AuthError::Rejectedif the server rejected the token with a334error challenge followed by a535.
Available only with the oauthbearer cargo feature (default-on).
Source§impl<T: Transport> SmtpClient<T>
impl<T: Transport> SmtpClient<T>
Sourcepub async fn send_mail(
&mut self,
from: &str,
to: &[&str],
body: &str,
) -> Result<SendOutcome, SmtpError>
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.
Sourcepub async fn send_mail_bytes(
&mut self,
from: &str,
to: &[&str],
body: &[u8],
) -> Result<SendOutcome, SmtpError>
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.
Sourcepub async fn send_mail_stream<B>(
&mut self,
from: &str,
to: &[&str],
body: &mut B,
) -> Result<SendOutcome, SmtpError>where
B: MessageBody,
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::Ioifbody.read_chunkreturns an error. The session is moved toClosed.
Source§impl<T: StartTlsCapable> SmtpClient<T>
impl<T: StartTlsCapable> SmtpClient<T>
Sourcepub async fn connect_starttls(
transport: T,
ehlo_domain: &str,
) -> Result<Self, SmtpError>
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:
ProtocolError::ExtensionUnavailablewithname: "STARTTLS"if the server’s firstEHLOreply did not advertise the extension.ProtocolError::UnexpectedCodewithduring: SmtpOp::StartTlsif the server rejectedSTARTTLSitself.SmtpError::Ioif the transport-level upgrade fails.
Sourcepub async fn starttls(&mut self) -> Result<(), SmtpError>
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
ProtocolError::ExtensionUnavailablewithname: "STARTTLS"if the server did not advertise the extension. In this case the client is moved toSessionState::Closedso subsequent calls fail fast — accidentally falling back to plaintext authentication would defeat the purpose of asking for STARTTLS.ProtocolError::UnexpectedCodewithduring: SmtpOp::StartTlsif the server rejected the command.SmtpError::Ioif the transport-level upgrade fails.
Source§impl<T: Transport> SmtpClient<T>
impl<T: Transport> SmtpClient<T>
Sourcepub async fn connect(transport: T, ehlo_domain: &str) -> Result<Self, SmtpError>
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.
Sourcepub async fn connect_with(
transport: T,
ehlo_domain: &str,
options: SmtpClientOptions,
) -> Result<Self, SmtpError>
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?;Sourcepub fn capabilities(&self) -> &[String]
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".
Sourcepub fn state(&self) -> SessionState
pub fn state(&self) -> SessionState
The current session state. Mostly useful for diagnostics and tests.
Sourcepub async fn quit(self) -> Result<(), SmtpError>
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.