lettre/transport/smtp/
mod.rs

1//! The SMTP transport sends emails using the SMTP protocol.
2//!
3//! This SMTP client follows [RFC
4//! 5321](https://tools.ietf.org/html/rfc5321), and is designed to efficiently send emails from an
5//! application to a relay email server, as it relies as much as possible on the relay server
6//! for sanity and RFC compliance checks.
7//!
8//! It implements the following extensions:
9//!
10//! * 8BITMIME ([RFC 6152](https://tools.ietf.org/html/rfc6152))
11//! * AUTH ([RFC 4954](https://tools.ietf.org/html/rfc4954)) with PLAIN, LOGIN and XOAUTH2 mechanisms
12//! * STARTTLS ([RFC 2487](https://tools.ietf.org/html/rfc2487))
13//!
14//! #### SMTP Transport
15//!
16//! This transport uses the SMTP protocol to send emails over the network (locally or remotely).
17//!
18//! It is designed to be:
19//!
20//! * Secured: connections are encrypted by default
21//! * Modern: unicode support for email contents and sender/recipient addresses when compatible
22//! * Fast: supports connection reuse and pooling
23//!
24//! This client is designed to send emails to a relay server, and should *not* be used to send
25//! emails directly to the destination server.
26//!
27//! The relay server can be the local email server, a specific host or a third-party service.
28//!
29//! #### Simple example with authentication
30//!
31//! A good starting point for sending emails via SMTP relay is to
32//! do the following:
33//!
34//! ```rust,no_run
35//! # #[cfg(all(feature = "builder", any(feature = "native-tls", feature = "rustls")))]
36//! # fn test() -> Result<(), Box<dyn std::error::Error>> {
37//! use lettre::{
38//!     message::header::ContentType,
39//!     transport::smtp::authentication::{Credentials, Mechanism},
40//!     Message, SmtpTransport, Transport,
41//! };
42//!
43//! let email = Message::builder()
44//!     .from("NoBody <nobody@domain.tld>".parse()?)
45//!     .reply_to("Yuin <yuin@domain.tld>".parse()?)
46//!     .to("Hei <hei@domain.tld>".parse()?)
47//!     .subject("Happy new year")
48//!     .header(ContentType::TEXT_PLAIN)
49//!     .body(String::from("Be happy!"))?;
50//!
51//! // Create the SMTPS transport
52//! let sender = SmtpTransport::relay("smtp.example.com")?
53//!     // Add credentials for authentication
54//!     .credentials(Credentials::new(
55//!         "username".to_owned(),
56//!         "password".to_owned(),
57//!     ))
58//!     // Optionally configure expected authentication mechanism
59//!     .authentication(vec![Mechanism::Plain])
60//!     .build();
61//!
62//! // Send the email via remote relay
63//! sender.send(&email)?;
64//! # Ok(())
65//! # }
66//! ```
67//!
68//! #### Shortening configuration
69//!
70//! It can be very repetitive to ask the user for every SMTP connection parameter.
71//! In some cases this can be simplified by using a connection URI instead.
72//!
73//! For more information take a look at [`SmtpTransport::from_url`] or [`AsyncSmtpTransport::from_url`].
74//!
75//! ```rust,no_run
76//! # #[cfg(all(feature = "builder", any(feature = "native-tls", feature = "rustls")))]
77//! # fn test() -> Result<(), Box<dyn std::error::Error>> {
78//! use lettre::{
79//!     message::header::ContentType,
80//!     transport::smtp::authentication::{Credentials, Mechanism},
81//!     Message, SmtpTransport, Transport,
82//! };
83//!
84//! let email = Message::builder()
85//!     .from("NoBody <nobody@domain.tld>".parse()?)
86//!     .reply_to("Yuin <yuin@domain.tld>".parse()?)
87//!     .to("Hei <hei@domain.tld>".parse()?)
88//!     .subject("Happy new year")
89//!     .header(ContentType::TEXT_PLAIN)
90//!     .body(String::from("Be happy!"))?;
91//!
92//! // Create the SMTPS transport
93//! let sender = SmtpTransport::from_url("smtps://username:password@smtp.example.com")?.build();
94//!
95//! // Send the email via remote relay
96//! sender.send(&email)?;
97//! # Ok(())
98//! # }
99//! ```
100//!
101//! #### Advanced configuration with custom TLS settings
102//!
103//! ```rust,no_run
104//! # #[cfg(all(feature = "builder", any(feature = "native-tls", feature = "rustls")))]
105//! # fn test() -> Result<(), Box<dyn std::error::Error>> {
106//! use std::fs;
107//!
108//! use lettre::{
109//!     message::header::ContentType,
110//!     transport::smtp::client::{Certificate, Tls, TlsParameters},
111//!     Message, SmtpTransport, Transport,
112//! };
113//!
114//! let email = Message::builder()
115//!     .from("NoBody <nobody@domain.tld>".parse()?)
116//!     .reply_to("Yuin <yuin@domain.tld>".parse()?)
117//!     .to("Hei <hei@domain.tld>".parse()?)
118//!     .subject("Happy new year")
119//!     .header(ContentType::TEXT_PLAIN)
120//!     .body(String::from("Be happy!"))?;
121//!
122//! // Custom TLS configuration - Use a self signed certificate
123//! let cert = fs::read("self-signed.crt")?;
124//! let cert = Certificate::from_pem(&cert)?;
125//! let tls = TlsParameters::builder(/* TLS SNI value */ "smtp.example.com".to_owned())
126//!     .add_root_certificate(cert)
127//!     .build()?;
128//!
129//! // Create the SMTPS transport
130//! let sender = SmtpTransport::relay("smtp.example.com")?
131//!     .tls(Tls::Wrapper(tls))
132//!     .build();
133//!
134//! // Send the email via remote relay
135//! sender.send(&email)?;
136//! # Ok(())
137//! # }
138//! ```
139//!
140//! #### Connection pooling
141//!
142//! [`SmtpTransport`] and [`AsyncSmtpTransport`] store connections in
143//! a connection pool by default. This avoids connecting and disconnecting
144//! from the relay server for every message the application tries to send. For the connection pool
145//! to work the instance of the transport **must** be reused.
146//! In a webserver context it may go about this:
147//!
148//! ```rust,no_run
149//! # #[cfg(all(feature = "builder", any(feature = "native-tls", feature = "rustls")))]
150//! # fn test() {
151//! use lettre::{
152//!     message::header::ContentType,
153//!     transport::smtp::{authentication::Credentials, PoolConfig},
154//!     Message, SmtpTransport, Transport,
155//! };
156//! #
157//! # type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
158//!
159//! /// The global application state
160//! #[derive(Debug)]
161//! struct AppState {
162//!     smtp: SmtpTransport,
163//!     // ... other global application parameters
164//! }
165//!
166//! impl AppState {
167//!     pub fn new(smtp_url: &str) -> Result<Self> {
168//!         let smtp = SmtpTransport::from_url(smtp_url)?.build();
169//!         Ok(Self { smtp })
170//!     }
171//! }
172//!
173//! fn handle_request(app_state: &AppState) -> Result<String> {
174//!     let email = Message::builder()
175//!         .from("NoBody <nobody@domain.tld>".parse()?)
176//!         .reply_to("Yuin <yuin@domain.tld>".parse()?)
177//!         .to("Hei <hei@domain.tld>".parse()?)
178//!         .subject("Happy new year")
179//!         .header(ContentType::TEXT_PLAIN)
180//!         .body(String::from("Be happy!"))?;
181//!
182//!     // Send the email via remote relay
183//!     app_state.smtp.send(&email)?;
184//!
185//!     Ok("The email has successfully been sent!".to_owned())
186//! }
187//! # }
188//! ```
189
190use std::time::Duration;
191
192use client::Tls;
193
194#[cfg(any(feature = "tokio1", feature = "async-std1"))]
195pub use self::async_transport::{AsyncSmtpTransport, AsyncSmtpTransportBuilder};
196#[cfg(feature = "pool")]
197pub use self::pool::PoolConfig;
198pub use self::{
199    error::Error,
200    transport::{SmtpTransport, SmtpTransportBuilder},
201};
202#[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
203use crate::transport::smtp::client::TlsParameters;
204use crate::transport::smtp::{
205    authentication::{Credentials, Mechanism, DEFAULT_MECHANISMS},
206    client::SmtpConnection,
207    extension::ClientId,
208    response::Response,
209};
210
211#[cfg(any(feature = "tokio1", feature = "async-std1"))]
212mod async_transport;
213pub mod authentication;
214pub mod client;
215pub mod commands;
216mod connection_url;
217mod error;
218pub mod extension;
219#[cfg(feature = "pool")]
220mod pool;
221pub mod response;
222mod transport;
223pub(super) mod util;
224
225// Registered port numbers:
226// https://www.iana.
227// org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml
228
229/// Default smtp port
230pub const SMTP_PORT: u16 = 25;
231/// Default submission port
232pub const SUBMISSION_PORT: u16 = 587;
233/// Default submission over TLS port
234///
235/// Defined in [RFC8314](https://tools.ietf.org/html/rfc8314)
236pub const SUBMISSIONS_PORT: u16 = 465;
237
238/// Default timeout
239const DEFAULT_TIMEOUT: Duration = Duration::from_secs(60);
240
241#[derive(Debug, Clone)]
242struct SmtpInfo {
243    /// Name sent during EHLO
244    hello_name: ClientId,
245    /// Server we are connecting to
246    server: String,
247    /// Port to connect to
248    port: u16,
249    /// TLS security configuration
250    tls: Tls,
251    /// Optional enforced authentication mechanism
252    authentication: Vec<Mechanism>,
253    /// Credentials
254    credentials: Option<Credentials>,
255    /// Define network timeout
256    /// It can be changed later for specific needs (like a different timeout for each SMTP command)
257    timeout: Option<Duration>,
258}
259
260impl Default for SmtpInfo {
261    fn default() -> Self {
262        Self {
263            server: "localhost".to_owned(),
264            port: SMTP_PORT,
265            hello_name: ClientId::default(),
266            credentials: None,
267            authentication: DEFAULT_MECHANISMS.into(),
268            timeout: Some(DEFAULT_TIMEOUT),
269            tls: Tls::None,
270        }
271    }
272}