#[cfg(feature = "pool")] use std::sync::Arc; use std::{ fmt::{self, Debug}, marker::PhantomData, time::Duration, }; use async_trait::async_trait; #[cfg(feature = "pool")] use super::pool::async_impl::Pool; #[cfg(feature = "pool")] use super::PoolConfig; use super::{ client::AsyncSmtpConnection, ClientId, Credentials, Error, Mechanism, Response, SmtpInfo, }; #[cfg(feature = "async-std1")] use crate::AsyncStd1Executor; #[cfg(any(feature = "tokio1", feature = "async-std1"))] use crate::AsyncTransport; #[cfg(feature = "tokio1")] use crate::Tokio1Executor; use crate::{Envelope, Executor}; /// Asynchronously sends emails using the SMTP protocol #[cfg_attr(docsrs, doc(cfg(any(feature = "tokio1", feature = "async-std1"))))] pub struct AsyncSmtpTransport { #[cfg(feature = "pool")] inner: Arc>, #[cfg(not(feature = "pool"))] inner: AsyncSmtpClient, } #[cfg(feature = "tokio1")] #[async_trait] impl AsyncTransport for AsyncSmtpTransport { type Ok = Response; type Error = Error; /// Sends an email async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result { let mut conn = self.inner.connection().await?; let result = conn.send(envelope, email).await?; #[cfg(not(feature = "pool"))] conn.quit().await?; Ok(result) } } #[cfg(feature = "async-std1")] #[async_trait] impl AsyncTransport for AsyncSmtpTransport { type Ok = Response; type Error = Error; /// Sends an email async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result { let mut conn = self.inner.connection().await?; let result = conn.send(envelope, email).await?; conn.quit().await?; Ok(result) } } impl AsyncSmtpTransport where E: Executor, { /// Simple and secure transport, using TLS connections to communicate with the SMTP server /// /// The right option for most SMTP servers. /// /// Creates an encrypted transport over submissions port, using the provided domain /// to validate TLS certificates. #[cfg(any( feature = "tokio1-native-tls", feature = "tokio1-rustls-tls", feature = "async-std1-rustls-tls" ))] #[cfg_attr( docsrs, doc(cfg(any( feature = "tokio1-native-tls", feature = "tokio1-rustls-tls", feature = "async-std1-rustls-tls" ))) )] pub fn relay(relay: &str) -> Result { use super::{Tls, TlsParameters, SUBMISSIONS_PORT}; let tls_parameters = TlsParameters::new(relay.into())?; Ok(Self::builder_dangerous(relay) .port(SUBMISSIONS_PORT) .tls(Tls::Wrapper(tls_parameters))) } /// Simple and secure transport, using STARTTLS to obtain encrypted connections /// /// Alternative to [`AsyncSmtpTransport::relay`](#method.relay), for SMTP servers /// that don't take SMTPS connections. /// /// Creates an encrypted transport over submissions port, by first connecting using /// an unencrypted connection and then upgrading it with STARTTLS. The provided /// domain is used to validate TLS certificates. /// /// An error is returned if the connection can't be upgraded. No credentials /// or emails will be sent to the server, protecting from downgrade attacks. #[cfg(any( feature = "tokio1-native-tls", feature = "tokio1-rustls-tls", feature = "async-std1-rustls-tls" ))] #[cfg_attr( docsrs, doc(cfg(any( feature = "tokio1-native-tls", feature = "tokio1-rustls-tls", feature = "async-std1-rustls-tls" ))) )] pub fn starttls_relay(relay: &str) -> Result { use super::{Tls, TlsParameters, SUBMISSION_PORT}; let tls_parameters = TlsParameters::new(relay.into())?; Ok(Self::builder_dangerous(relay) .port(SUBMISSION_PORT) .tls(Tls::Required(tls_parameters))) } /// Creates a new local SMTP client to port 25 /// /// Shortcut for local unencrypted relay (typical local email daemon that will handle relaying) pub fn unencrypted_localhost() -> AsyncSmtpTransport { Self::builder_dangerous("localhost").build() } /// Creates a new SMTP client /// /// Defaults are: /// /// * No authentication /// * No TLS /// * A 60-seconds timeout for smtp commands /// * Port 25 /// /// Consider using [`AsyncSmtpTransport::relay`](#method.relay) or /// [`AsyncSmtpTransport::starttls_relay`](#method.starttls_relay) instead, /// if possible. pub fn builder_dangerous>(server: T) -> AsyncSmtpTransportBuilder { AsyncSmtpTransportBuilder::new(server) } /// Creates a `AsyncSmtpTransportBuilder` from a connection URL /// /// The protocol, credentials, host and port can be provided in a single URL. /// Use the scheme `smtp` for an unencrypted relay (optionally in combination with the /// `tls` parameter to allow/require STARTTLS) or `smtps` for SMTP over TLS. /// The path section of the url can be used to set an alternative name for /// the HELO / EHLO command. /// For example `smtps://username:password@smtp.example.com/client.example.com:465` /// will set the HELO / EHLO name `client.example.com`. /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// ///
schemetls parameterexampleremarks
smtps-smtps://smtp.example.comSMTP over TLS, recommended method
smtprequiredsmtp://smtp.example.com?tls=requiredSMTP with STARTTLS required, when SMTP over TLS is not available
smtpopportunisticsmtp://smtp.example.com?tls=opportunistic /// SMTP with optionally STARTTLS when supported by the server. /// Caution: this method is vulnerable to a man-in-the-middle attack. /// Not recommended for production use. ///
smtp-smtp://smtp.example.comUnencrypted SMTP, not recommended for production use.
/// /// ```rust,no_run /// use lettre::{ /// message::header::ContentType, transport::smtp::authentication::Credentials, /// AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor, /// }; /// # use tokio1_crate as tokio; /// /// # #[tokio::main] /// # async fn main() -> Result<(), Box> { /// let email = Message::builder() /// .from("NoBody ".parse().unwrap()) /// .reply_to("Yuin ".parse().unwrap()) /// .to("Hei ".parse().unwrap()) /// .subject("Happy new year") /// .header(ContentType::TEXT_PLAIN) /// .body(String::from("Be happy!")) /// .unwrap(); /// /// // Open a remote connection to gmail /// let mailer: AsyncSmtpTransport = /// AsyncSmtpTransport::::from_url( /// "smtps://username:password@smtp.example.com:465", /// ) /// .unwrap() /// .build(); /// /// // Send the email /// match mailer.send(email).await { /// Ok(_) => println!("Email sent successfully!"), /// Err(e) => panic!("Could not send email: {e:?}"), /// } /// # Ok(()) /// # } /// ``` #[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))] #[cfg_attr( docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))) )] pub fn from_url(connection_url: &str) -> Result { super::connection_url::from_connection_url(connection_url) } /// Tests the SMTP connection /// /// `test_connection()` tests the connection by using the SMTP NOOP command. /// The connection is closed afterward if a connection pool is not used. pub async fn test_connection(&self) -> Result { let mut conn = self.inner.connection().await?; let is_connected = conn.test_connected().await; #[cfg(not(feature = "pool"))] conn.quit().await?; Ok(is_connected) } } impl Debug for AsyncSmtpTransport { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut builder = f.debug_struct("AsyncSmtpTransport"); builder.field("inner", &self.inner); builder.finish() } } impl Clone for AsyncSmtpTransport where E: Executor, { fn clone(&self) -> Self { Self { #[cfg(feature = "pool")] inner: Arc::clone(&self.inner), #[cfg(not(feature = "pool"))] inner: self.inner.clone(), } } } /// Contains client configuration. /// Instances of this struct can be created using functions of [`AsyncSmtpTransport`]. #[derive(Debug, Clone)] #[cfg_attr(docsrs, doc(cfg(any(feature = "tokio1", feature = "async-std1"))))] pub struct AsyncSmtpTransportBuilder { info: SmtpInfo, #[cfg(feature = "pool")] pool_config: PoolConfig, } /// Builder for the SMTP `AsyncSmtpTransport` impl AsyncSmtpTransportBuilder { // Create new builder with default parameters pub(crate) fn new>(server: T) -> Self { let info = SmtpInfo { server: server.into(), ..Default::default() }; AsyncSmtpTransportBuilder { info, #[cfg(feature = "pool")] pool_config: PoolConfig::default(), } } /// Set the name used during EHLO pub fn hello_name(mut self, name: ClientId) -> Self { self.info.hello_name = name; self } /// Set the authentication mechanism to use pub fn credentials(mut self, credentials: Credentials) -> Self { self.info.credentials = Some(credentials); self } /// Set the authentication mechanism to use pub fn authentication(mut self, mechanisms: Vec) -> Self { self.info.authentication = mechanisms; self } /// Set the port to use pub fn port(mut self, port: u16) -> Self { self.info.port = port; self } /// Set the timeout duration pub fn timeout(mut self, timeout: Option) -> Self { self.info.timeout = timeout; self } /// Set the TLS settings to use #[cfg(any( feature = "tokio1-native-tls", feature = "tokio1-rustls-tls", feature = "async-std1-rustls-tls" ))] #[cfg_attr( docsrs, doc(cfg(any( feature = "tokio1-native-tls", feature = "tokio1-rustls-tls", feature = "async-std1-rustls-tls" ))) )] pub fn tls(mut self, tls: super::Tls) -> Self { self.info.tls = tls; self } /// Use a custom configuration for the connection pool /// /// Defaults can be found at [`PoolConfig`] #[cfg(feature = "pool")] #[cfg_attr(docsrs, doc(cfg(feature = "pool")))] pub fn pool_config(mut self, pool_config: PoolConfig) -> Self { self.pool_config = pool_config; self } /// Build the transport pub fn build(self) -> AsyncSmtpTransport where E: Executor, { let client = AsyncSmtpClient { info: self.info, marker_: PhantomData, }; #[cfg(feature = "pool")] let client = Pool::new(self.pool_config, client); AsyncSmtpTransport { inner: client } } } /// Build client pub struct AsyncSmtpClient { info: SmtpInfo, marker_: PhantomData, } impl AsyncSmtpClient where E: Executor, { /// Creates a new connection directly usable to send emails /// /// Handles encryption and authentication pub async fn connection(&self) -> Result { let mut conn = E::connect( &self.info.server, self.info.port, self.info.timeout, &self.info.hello_name, &self.info.tls, ) .await?; if let Some(credentials) = &self.info.credentials { conn.auth(&self.info.authentication, credentials).await?; } Ok(conn) } } impl Debug for AsyncSmtpClient { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut builder = f.debug_struct("AsyncSmtpClient"); builder.field("info", &self.info); builder.finish() } } // `clone` is unused when the `pool` feature is on #[allow(dead_code)] impl AsyncSmtpClient where E: Executor, { fn clone(&self) -> Self { Self { info: self.info.clone(), marker_: PhantomData, } } }