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