Compare commits

...

13 Commits

Author SHA1 Message Date
Alexis Mousset
b594945695 Prepare 0.10.0-beta.1 (#555) 2021-02-27 16:54:32 +00:00
Paolo Barbolini
5c83120986 Executor refactor (#545)
* Executor
* Move transports inside the transport module
* AsyncTransport refactor
* Update examples
* Update docs
* impl Default for AsyncSendmailTransport
* Implement AsyncFileTransport::read
* Generalize AsyncFileTransport AsyncTransport implementation
* Remove remaining uses of AsyncSmtpConnector
2021-02-27 16:36:59 +00:00
Alexis Mousset
d4df9a2965 feat(transport): Add SMTPUTF8 handling (#540) 2021-02-27 16:23:48 +00:00
Paolo Barbolini
d2aa959845 Remove deprecated SinglePart methods (#549) 2021-02-19 19:18:38 +01:00
Paolo Barbolini
d1f016e8e2 Fix minimal-version of the mime crate (#548) 2021-02-16 09:50:45 +01:00
konomith
9146212a3e fix(transport-smtp): Fix max_size setter for PoolConfig (#546)
Currently the `max_size` setter method incorrectly assigns the
new value to `self.min_idle` instead of `self.max_size`. This
change fixes the issue.
2021-02-15 21:19:37 +00:00
Alexis Mousset
a04866acfb Improve doc formatting (#539) 2021-02-05 08:39:00 +01:00
Alexis Mousset
be88aabae2 Make ClientCodec private (#541) 2021-02-04 11:11:30 +01:00
Alexis Mousset
6fbb3bf440 feat(transport): Read messages from FileTransport (#516)
* feat(transport): Read messages from FileTransport

* Style improvements
2021-02-03 10:25:47 +01:00
Alexis Mousset
9d8c31bef8 Fix smtp doc examples (#536)
* Fix smtp examples

Make TlsParametersBuilder a consuming builder
as `build()` consumes it. It allows chaining methods.

* Format doc examples
2021-02-03 10:23:08 +01:00
Alexis Mousset
0ea3bfbd13 Remove file and sendmail transport by default (#537)
We can consider the smtp transport as the main
use-case. Let's keep TLS through native-tls
and connection pooling for fast ans secure
defaut feature set.
2021-02-03 10:17:53 +01:00
Alexis Mousset
a0980d017b Make EmailFormat trait private (#535)
It does not need to be exposed.
2021-02-01 10:11:25 +00:00
Paolo Barbolini
40c8a9d000 Better seal AsyncSmtpConnector (#534) 2021-01-31 16:42:37 +00:00
43 changed files with 866 additions and 623 deletions

View File

@@ -1,7 +1,7 @@
[package] [package]
name = "lettre" name = "lettre"
# remember to update html_root_url and README.md (Cargo.toml example and deps.rs badge) # remember to update html_root_url and README.md (Cargo.toml example and deps.rs badge)
version = "0.10.0-alpha.5" version = "0.10.0-beta.1"
description = "Email client" description = "Email client"
readme = "README.md" readme = "README.md"
homepage = "https://lettre.rs" homepage = "https://lettre.rs"
@@ -23,7 +23,7 @@ tracing = { version = "0.1.16", default-features = false, features = ["std"], op
# builder # builder
hyperx = { version = "1", optional = true, features = ["headers"] } hyperx = { version = "1", optional = true, features = ["headers"] }
mime = { version = "0.3", optional = true } mime = { version = "0.3.4", optional = true }
uuid = { version = "0.8", features = ["v4"] } uuid = { version = "0.8", features = ["v4"] }
rand = { version = "0.8", optional = true } rand = { version = "0.8", optional = true }
quoted_printable = { version = "0.4", optional = true } quoted_printable = { version = "0.4", optional = true }
@@ -80,7 +80,7 @@ harness = false
name = "transport_smtp" name = "transport_smtp"
[features] [features]
default = ["file-transport", "file-transport-envelope", "smtp-transport", "native-tls", "hostname", "r2d2", "sendmail-transport", "builder"] default = ["smtp-transport", "native-tls", "hostname", "r2d2", "builder"]
builder = ["mime", "base64", "hyperx", "rand", "quoted_printable"] builder = ["mime", "base64", "hyperx", "rand", "quoted_printable"]
# transports # transports

View File

@@ -28,8 +28,8 @@
</div> </div>
<div align="center"> <div align="center">
<a href="https://deps.rs/crate/lettre/0.10.0-alpha.4"> <a href="https://deps.rs/crate/lettre/0.10.0-beta.1">
<img src="https://deps.rs/crate/lettre/0.10.0-alpha.4/status.svg" <img src="https://deps.rs/crate/lettre/0.10.0-beta.1/status.svg"
alt="dependency status" /> alt="dependency status" />
</a> </a>
</div> </div>
@@ -66,7 +66,7 @@ To use this library, add the following to your `Cargo.toml`:
```toml ```toml
[dependencies] [dependencies]
lettre = "0.10.0-alpha.4" lettre = "0.10.0-beta.1"
``` ```
```rust,no_run ```rust,no_run

View File

@@ -1,5 +1,5 @@
use lettre::{ use lettre::{
transport::smtp::authentication::Credentials, AsyncSmtpTransport, AsyncStd1Connector, transport::smtp::authentication::Credentials, AsyncSmtpTransport, AsyncStd1Executor,
AsyncStd1Transport, Message, AsyncStd1Transport, Message,
}; };
@@ -18,10 +18,11 @@ async fn main() {
let creds = Credentials::new("smtp_username".to_string(), "smtp_password".to_string()); let creds = Credentials::new("smtp_username".to_string(), "smtp_password".to_string());
// Open a remote connection to gmail using STARTTLS // Open a remote connection to gmail using STARTTLS
let mailer = AsyncSmtpTransport::<AsyncStd1Connector>::starttls_relay("smtp.gmail.com") let mailer: AsyncSmtpTransport<AsyncStd1Executor> =
.unwrap() AsyncSmtpTransport::<AsyncStd1Executor>::starttls_relay("smtp.gmail.com")
.credentials(creds) .unwrap()
.build(); .credentials(creds)
.build();
// Send the email // Send the email
match mailer.send(email).await { match mailer.send(email).await {

View File

@@ -1,5 +1,5 @@
use lettre::{ use lettre::{
transport::smtp::authentication::Credentials, AsyncSmtpTransport, AsyncStd1Connector, transport::smtp::authentication::Credentials, AsyncSmtpTransport, AsyncStd1Executor,
AsyncStd1Transport, Message, AsyncStd1Transport, Message,
}; };
@@ -18,10 +18,11 @@ async fn main() {
let creds = Credentials::new("smtp_username".to_string(), "smtp_password".to_string()); let creds = Credentials::new("smtp_username".to_string(), "smtp_password".to_string());
// Open a remote connection to gmail // Open a remote connection to gmail
let mailer = AsyncSmtpTransport::<AsyncStd1Connector>::relay("smtp.gmail.com") let mailer: AsyncSmtpTransport<AsyncStd1Executor> =
.unwrap() AsyncSmtpTransport::<AsyncStd1Executor>::relay("smtp.gmail.com")
.credentials(creds) .unwrap()
.build(); .credentials(creds)
.build();
// Send the email // Send the email
match mailer.send(email).await { match mailer.send(email).await {

View File

@@ -1,5 +1,7 @@
use lettre::message::{header, MultiPart, SinglePart}; use lettre::{
use lettre::{FileTransport, Message, Transport}; message::{header, MultiPart, SinglePart},
FileTransport, Message, Transport,
};
fn main() { fn main() {
// The html we want to send. // The html we want to send.

View File

@@ -1,5 +1,7 @@
use lettre::message::{header, MultiPart, SinglePart}; use lettre::{
use lettre::{FileTransport, Message, Transport}; message::{header, MultiPart, SinglePart},
FileTransport, Message, Transport,
};
use maud::html; use maud::html;
fn main() { fn main() {

View File

@@ -1,8 +1,10 @@
use std::fs; use std::fs;
use lettre::{ use lettre::{
transport::smtp::authentication::Credentials, transport::smtp::{
transport::smtp::client::{Certificate, Tls, TlsParameters}, authentication::Credentials,
client::{Certificate, Tls, TlsParameters},
},
Message, SmtpTransport, Transport, Message, SmtpTransport, Transport,
}; };
@@ -20,9 +22,10 @@ fn main() {
// Use a custom certificate stored on disk to securely verify the server's certificate // Use a custom certificate stored on disk to securely verify the server's certificate
let pem_cert = fs::read("certificate.pem").unwrap(); let pem_cert = fs::read("certificate.pem").unwrap();
let cert = Certificate::from_pem(&pem_cert).unwrap(); let cert = Certificate::from_pem(&pem_cert).unwrap();
let mut tls = TlsParameters::builder("smtp.server.com".to_string()); let tls = TlsParameters::builder("smtp.server.com".to_string())
tls.add_root_certificate(cert); .add_root_certificate(cert)
let tls = tls.build().unwrap(); .build()
.unwrap();
let creds = Credentials::new("smtp_username".to_string(), "smtp_password".to_string()); let creds = Credentials::new("smtp_username".to_string(), "smtp_password".to_string());

View File

@@ -4,7 +4,7 @@
use tokio02_crate as tokio; use tokio02_crate as tokio;
use lettre::{ use lettre::{
transport::smtp::authentication::Credentials, AsyncSmtpTransport, Message, Tokio02Connector, transport::smtp::authentication::Credentials, AsyncSmtpTransport, Message, Tokio02Executor,
Tokio02Transport, Tokio02Transport,
}; };
@@ -23,10 +23,11 @@ async fn main() {
let creds = Credentials::new("smtp_username".to_string(), "smtp_password".to_string()); let creds = Credentials::new("smtp_username".to_string(), "smtp_password".to_string());
// Open a remote connection to gmail using STARTTLS // Open a remote connection to gmail using STARTTLS
let mailer = AsyncSmtpTransport::<Tokio02Connector>::starttls_relay("smtp.gmail.com") let mailer: AsyncSmtpTransport<Tokio02Executor> =
.unwrap() AsyncSmtpTransport::<Tokio02Executor>::starttls_relay("smtp.gmail.com")
.credentials(creds) .unwrap()
.build(); .credentials(creds)
.build();
// Send the email // Send the email
match mailer.send(email).await { match mailer.send(email).await {

View File

@@ -4,7 +4,7 @@
use tokio02_crate as tokio; use tokio02_crate as tokio;
use lettre::{ use lettre::{
transport::smtp::authentication::Credentials, AsyncSmtpTransport, Message, Tokio02Connector, transport::smtp::authentication::Credentials, AsyncSmtpTransport, Message, Tokio02Executor,
Tokio02Transport, Tokio02Transport,
}; };
@@ -23,10 +23,11 @@ async fn main() {
let creds = Credentials::new("smtp_username".to_string(), "smtp_password".to_string()); let creds = Credentials::new("smtp_username".to_string(), "smtp_password".to_string());
// Open a remote connection to gmail // Open a remote connection to gmail
let mailer = AsyncSmtpTransport::<Tokio02Connector>::relay("smtp.gmail.com") let mailer: AsyncSmtpTransport<Tokio02Executor> =
.unwrap() AsyncSmtpTransport::<Tokio02Executor>::relay("smtp.gmail.com")
.credentials(creds) .unwrap()
.build(); .credentials(creds)
.build();
// Send the email // Send the email
match mailer.send(email).await { match mailer.send(email).await {

View File

@@ -4,7 +4,7 @@
use tokio1_crate as tokio; use tokio1_crate as tokio;
use lettre::{ use lettre::{
transport::smtp::authentication::Credentials, AsyncSmtpTransport, Message, Tokio1Connector, transport::smtp::authentication::Credentials, AsyncSmtpTransport, Message, Tokio1Executor,
Tokio1Transport, Tokio1Transport,
}; };
@@ -23,10 +23,11 @@ async fn main() {
let creds = Credentials::new("smtp_username".to_string(), "smtp_password".to_string()); let creds = Credentials::new("smtp_username".to_string(), "smtp_password".to_string());
// Open a remote connection to gmail using STARTTLS // Open a remote connection to gmail using STARTTLS
let mailer = AsyncSmtpTransport::<Tokio1Connector>::starttls_relay("smtp.gmail.com") let mailer: AsyncSmtpTransport<Tokio1Executor> =
.unwrap() AsyncSmtpTransport::<Tokio1Executor>::starttls_relay("smtp.gmail.com")
.credentials(creds) .unwrap()
.build(); .credentials(creds)
.build();
// Send the email // Send the email
match mailer.send(email).await { match mailer.send(email).await {

View File

@@ -4,7 +4,7 @@
use tokio1_crate as tokio; use tokio1_crate as tokio;
use lettre::{ use lettre::{
transport::smtp::authentication::Credentials, AsyncSmtpTransport, Message, Tokio1Connector, transport::smtp::authentication::Credentials, AsyncSmtpTransport, Message, Tokio1Executor,
Tokio1Transport, Tokio1Transport,
}; };
@@ -23,10 +23,11 @@ async fn main() {
let creds = Credentials::new("smtp_username".to_string(), "smtp_password".to_string()); let creds = Credentials::new("smtp_username".to_string(), "smtp_password".to_string());
// Open a remote connection to gmail // Open a remote connection to gmail
let mailer = AsyncSmtpTransport::<Tokio1Connector>::relay("smtp.gmail.com") let mailer: AsyncSmtpTransport<Tokio1Executor> =
.unwrap() AsyncSmtpTransport::<Tokio1Executor>::relay("smtp.gmail.com")
.credentials(creds) .unwrap()
.build(); .credentials(creds)
.build();
// Send the email // Send the email
match mailer.send(email).await { match mailer.send(email).await {

View File

@@ -103,6 +103,14 @@ impl Envelope {
pub fn from(&self) -> Option<&Address> { pub fn from(&self) -> Option<&Address> {
self.reverse_path.as_ref() self.reverse_path.as_ref()
} }
/// Check if any of the addresses in the envelope contains non-ascii chars
pub(crate) fn has_non_ascii_addresses(&self) -> bool {
self.reverse_path
.iter()
.chain(self.forward_path.iter())
.any(|a| !a.is_ascii())
}
} }
#[cfg(feature = "builder")] #[cfg(feature = "builder")]

View File

@@ -1,8 +1,12 @@
//! Email addresses
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
mod serde; mod serde;
mod envelope; mod envelope;
mod types; mod types;
pub use self::envelope::Envelope; pub use self::{
pub use self::types::{Address, AddressError}; envelope::Envelope,
types::{Address, AddressError},
};

View File

@@ -173,6 +173,11 @@ impl Address {
Err(AddressError::InvalidDomain) Err(AddressError::InvalidDomain)
} }
/// Check if the address contains non-ascii chars
pub(super) fn is_ascii(&self) -> bool {
self.serialized.is_ascii()
}
} }
impl Display for Address { impl Display for Address {
@@ -211,6 +216,7 @@ impl AsRef<OsStr> for Address {
} }
#[derive(Debug, PartialEq, Clone, Copy)] #[derive(Debug, PartialEq, Clone, Copy)]
/// Errors in email addresses parsing
pub enum AddressError { pub enum AddressError {
MissingParts, MissingParts,
Unbalanced, Unbalanced,

View File

@@ -1,3 +1,5 @@
//! Error type for email messages
use std::{ use std::{
error::Error as StdError, error::Error as StdError,
fmt::{self, Display, Formatter}, fmt::{self, Display, Formatter},

232
src/executor.rs Normal file
View File

@@ -0,0 +1,232 @@
use async_trait::async_trait;
#[cfg(feature = "file-transport")]
use std::io::Result as IoResult;
#[cfg(feature = "file-transport")]
use std::path::Path;
#[cfg(all(
feature = "smtp-transport",
any(feature = "tokio02", feature = "tokio1", feature = "async-std1")
))]
use crate::transport::smtp::client::AsyncSmtpConnection;
#[cfg(all(
feature = "smtp-transport",
any(feature = "tokio02", feature = "tokio1", feature = "async-std1")
))]
use crate::transport::smtp::client::Tls;
#[cfg(all(
feature = "smtp-transport",
any(feature = "tokio02", feature = "tokio1", feature = "async-std1")
))]
use crate::transport::smtp::extension::ClientId;
#[cfg(all(
feature = "smtp-transport",
any(feature = "tokio02", feature = "tokio1", feature = "async-std1")
))]
use crate::transport::smtp::Error;
#[async_trait]
pub trait Executor: Send + Sync + private::Sealed {
#[doc(hidden)]
#[cfg(feature = "smtp-transport")]
async fn connect(
hostname: &str,
port: u16,
hello_name: &ClientId,
tls: &Tls,
) -> Result<AsyncSmtpConnection, Error>;
#[doc(hidden)]
#[cfg(feature = "file-transport-envelope")]
async fn fs_read(path: &Path) -> IoResult<Vec<u8>>;
#[doc(hidden)]
#[cfg(feature = "file-transport")]
async fn fs_write(path: &Path, contents: &[u8]) -> IoResult<()>;
}
#[allow(missing_copy_implementations)]
#[non_exhaustive]
#[cfg(feature = "tokio02")]
#[cfg_attr(docsrs, doc(cfg(feature = "tokio02")))]
pub struct Tokio02Executor;
#[async_trait]
#[cfg(feature = "tokio02")]
impl Executor for Tokio02Executor {
#[doc(hidden)]
#[cfg(feature = "smtp-transport")]
async fn connect(
hostname: &str,
port: u16,
hello_name: &ClientId,
tls: &Tls,
) -> Result<AsyncSmtpConnection, Error> {
#[allow(clippy::match_single_binding)]
let tls_parameters = match tls {
#[cfg(any(feature = "tokio02-native-tls", feature = "tokio02-rustls-tls"))]
Tls::Wrapper(ref tls_parameters) => Some(tls_parameters.clone()),
_ => None,
};
#[allow(unused_mut)]
let mut conn =
AsyncSmtpConnection::connect_tokio02(hostname, port, hello_name, tls_parameters)
.await?;
#[cfg(any(feature = "tokio02-native-tls", feature = "tokio02-rustls-tls"))]
match tls {
Tls::Opportunistic(ref tls_parameters) => {
if conn.can_starttls() {
conn.starttls(tls_parameters.clone(), hello_name).await?;
}
}
Tls::Required(ref tls_parameters) => {
conn.starttls(tls_parameters.clone(), hello_name).await?;
}
_ => (),
}
Ok(conn)
}
#[doc(hidden)]
#[cfg(feature = "file-transport-envelope")]
async fn fs_read(path: &Path) -> IoResult<Vec<u8>> {
tokio02_crate::fs::read(path).await
}
#[doc(hidden)]
#[cfg(feature = "file-transport")]
async fn fs_write(path: &Path, contents: &[u8]) -> IoResult<()> {
tokio02_crate::fs::write(path, contents).await
}
}
#[allow(missing_copy_implementations)]
#[non_exhaustive]
#[cfg(feature = "tokio1")]
#[cfg_attr(docsrs, doc(cfg(feature = "tokio1")))]
pub struct Tokio1Executor;
#[async_trait]
#[cfg(feature = "tokio1")]
impl Executor for Tokio1Executor {
#[doc(hidden)]
#[cfg(feature = "smtp-transport")]
async fn connect(
hostname: &str,
port: u16,
hello_name: &ClientId,
tls: &Tls,
) -> Result<AsyncSmtpConnection, Error> {
#[allow(clippy::match_single_binding)]
let tls_parameters = match tls {
#[cfg(any(feature = "tokio1-native-tls", feature = "tokio1-rustls-tls"))]
Tls::Wrapper(ref tls_parameters) => Some(tls_parameters.clone()),
_ => None,
};
#[allow(unused_mut)]
let mut conn =
AsyncSmtpConnection::connect_tokio1(hostname, port, hello_name, tls_parameters).await?;
#[cfg(any(feature = "tokio1-native-tls", feature = "tokio1-rustls-tls"))]
match tls {
Tls::Opportunistic(ref tls_parameters) => {
if conn.can_starttls() {
conn.starttls(tls_parameters.clone(), hello_name).await?;
}
}
Tls::Required(ref tls_parameters) => {
conn.starttls(tls_parameters.clone(), hello_name).await?;
}
_ => (),
}
Ok(conn)
}
#[doc(hidden)]
#[cfg(feature = "file-transport-envelope")]
async fn fs_read(path: &Path) -> IoResult<Vec<u8>> {
tokio1_crate::fs::read(path).await
}
#[doc(hidden)]
#[cfg(feature = "file-transport")]
async fn fs_write(path: &Path, contents: &[u8]) -> IoResult<()> {
tokio1_crate::fs::write(path, contents).await
}
}
#[allow(missing_copy_implementations)]
#[non_exhaustive]
#[cfg(feature = "async-std1")]
#[cfg_attr(docsrs, doc(cfg(feature = "async-std1")))]
pub struct AsyncStd1Executor;
#[async_trait]
#[cfg(feature = "async-std1")]
impl Executor for AsyncStd1Executor {
#[doc(hidden)]
#[cfg(feature = "smtp-transport")]
async fn connect(
hostname: &str,
port: u16,
hello_name: &ClientId,
tls: &Tls,
) -> Result<AsyncSmtpConnection, Error> {
#[allow(clippy::match_single_binding)]
let tls_parameters = match tls {
#[cfg(any(feature = "async-std1-native-tls", feature = "async-std1-rustls-tls"))]
Tls::Wrapper(ref tls_parameters) => Some(tls_parameters.clone()),
_ => None,
};
#[allow(unused_mut)]
let mut conn =
AsyncSmtpConnection::connect_asyncstd1(hostname, port, hello_name, tls_parameters)
.await?;
#[cfg(any(feature = "async-std1-native-tls", feature = "async-std1-rustls-tls"))]
match tls {
Tls::Opportunistic(ref tls_parameters) => {
if conn.can_starttls() {
conn.starttls(tls_parameters.clone(), hello_name).await?;
}
}
Tls::Required(ref tls_parameters) => {
conn.starttls(tls_parameters.clone(), hello_name).await?;
}
_ => (),
}
Ok(conn)
}
#[doc(hidden)]
#[cfg(feature = "file-transport-envelope")]
async fn fs_read(path: &Path) -> IoResult<Vec<u8>> {
async_std::fs::read(path).await
}
#[doc(hidden)]
#[cfg(feature = "file-transport")]
async fn fs_write(path: &Path, contents: &[u8]) -> IoResult<()> {
async_std::fs::write(path, contents).await
}
}
mod private {
use super::*;
pub trait Sealed {}
#[cfg(feature = "tokio02")]
impl Sealed for Tokio02Executor {}
#[cfg(feature = "tokio1")]
impl Sealed for Tokio1Executor {}
#[cfg(feature = "async-std1")]
impl Sealed for AsyncStd1Executor {}
}

View File

@@ -30,7 +30,7 @@
//! * **serde**: Serialization/Deserialization of entities //! * **serde**: Serialization/Deserialization of entities
//! * **hostname**: Ability to try to use actual hostname in SMTP transaction //! * **hostname**: Ability to try to use actual hostname in SMTP transaction
#![doc(html_root_url = "https://docs.rs/crate/lettre/0.10.0-alpha.5")] #![doc(html_root_url = "https://docs.rs/crate/lettre/0.10.0-beta.1")]
#![doc(html_favicon_url = "https://lettre.rs/favicon.ico")] #![doc(html_favicon_url = "https://lettre.rs/favicon.ico")]
#![doc(html_logo_url = "https://avatars0.githubusercontent.com/u/15113230?v=4")] #![doc(html_logo_url = "https://avatars0.githubusercontent.com/u/15113230?v=4")]
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
@@ -46,6 +46,8 @@
pub mod address; pub mod address;
pub mod error; pub mod error;
#[cfg(all(any(feature = "tokio02", feature = "tokio1", feature = "async-std1")))]
mod executor;
#[cfg(feature = "builder")] #[cfg(feature = "builder")]
#[cfg_attr(docsrs, doc(cfg(feature = "builder")))] #[cfg_attr(docsrs, doc(cfg(feature = "builder")))]
pub mod message; pub mod message;
@@ -55,13 +57,31 @@ pub mod transport;
#[macro_use] #[macro_use]
extern crate hyperx; extern crate hyperx;
#[cfg(feature = "async-std1")]
pub use self::executor::AsyncStd1Executor;
#[cfg(all(any(feature = "tokio02", feature = "tokio1", feature = "async-std1")))]
pub use self::executor::Executor;
#[cfg(feature = "tokio02")]
pub use self::executor::Tokio02Executor;
#[cfg(feature = "tokio1")]
pub use self::executor::Tokio1Executor;
#[cfg(all(any(feature = "tokio02", feature = "tokio1", feature = "async-std1")))]
pub use self::transport::AsyncTransport;
pub use crate::address::Address; pub use crate::address::Address;
use crate::address::Envelope;
use crate::error::Error;
#[cfg(feature = "builder")] #[cfg(feature = "builder")]
pub use crate::message::Message; pub use crate::message::Message;
#[cfg(all(
feature = "file-transport",
any(feature = "tokio02", feature = "tokio1", feature = "async-std1")
))]
pub use crate::transport::file::AsyncFileTransport;
#[cfg(feature = "file-transport")] #[cfg(feature = "file-transport")]
pub use crate::transport::file::FileTransport; pub use crate::transport::file::FileTransport;
#[cfg(all(
feature = "sendmail-transport",
any(feature = "tokio02", feature = "tokio1", feature = "async-std1")
))]
pub use crate::transport::sendmail::AsyncSendmailTransport;
#[cfg(feature = "sendmail-transport")] #[cfg(feature = "sendmail-transport")]
pub use crate::transport::sendmail::SendmailTransport; pub use crate::transport::sendmail::SendmailTransport;
#[cfg(all( #[cfg(all(
@@ -69,103 +89,32 @@ pub use crate::transport::sendmail::SendmailTransport;
any(feature = "tokio02", feature = "tokio1") any(feature = "tokio02", feature = "tokio1")
))] ))]
pub use crate::transport::smtp::AsyncSmtpTransport; pub use crate::transport::smtp::AsyncSmtpTransport;
pub use crate::transport::Transport;
use crate::{address::Envelope, error::Error};
#[doc(hidden)]
#[allow(deprecated)]
#[cfg(all(feature = "smtp-transport", feature = "async-std1"))] #[cfg(all(feature = "smtp-transport", feature = "async-std1"))]
pub use crate::transport::smtp::AsyncStd1Connector; pub use crate::transport::smtp::AsyncStd1Connector;
#[cfg(feature = "smtp-transport")] #[cfg(feature = "smtp-transport")]
pub use crate::transport::smtp::SmtpTransport; pub use crate::transport::smtp::SmtpTransport;
#[doc(hidden)]
#[allow(deprecated)]
#[cfg(all(feature = "smtp-transport", feature = "tokio02"))] #[cfg(all(feature = "smtp-transport", feature = "tokio02"))]
pub use crate::transport::smtp::Tokio02Connector; pub use crate::transport::smtp::Tokio02Connector;
#[doc(hidden)]
#[allow(deprecated)]
#[cfg(all(feature = "smtp-transport", feature = "tokio1"))] #[cfg(all(feature = "smtp-transport", feature = "tokio1"))]
pub use crate::transport::smtp::Tokio1Connector; pub use crate::transport::smtp::Tokio1Connector;
#[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio1"))] #[doc(hidden)]
use async_trait::async_trait;
/// Blocking Transport method for emails
pub trait Transport {
/// Response produced by the Transport
type Ok;
/// Error produced by the Transport
type Error;
/// Sends the email
#[cfg(feature = "builder")]
#[cfg_attr(docsrs, doc(cfg(feature = "builder")))]
fn send(&self, message: &Message) -> Result<Self::Ok, Self::Error> {
let raw = message.formatted();
self.send_raw(message.envelope(), &raw)
}
fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error>;
}
/// async-std 1.x based Transport method for emails
#[cfg(feature = "async-std1")] #[cfg(feature = "async-std1")]
#[cfg_attr(docsrs, doc(cfg(feature = "async-std1")))] pub use crate::transport::AsyncStd1Transport;
#[async_trait] #[doc(hidden)]
pub trait AsyncStd1Transport {
/// Response produced by the Transport
type Ok;
/// Error produced by the Transport
type Error;
/// Sends the email
#[cfg(feature = "builder")]
#[cfg_attr(docsrs, doc(cfg(feature = "builder")))]
// TODO take &Message
async fn send(&self, message: Message) -> Result<Self::Ok, Self::Error> {
let raw = message.formatted();
let envelope = message.envelope();
self.send_raw(&envelope, &raw).await
}
async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error>;
}
/// tokio 0.2.x based Transport method for emails
#[cfg(feature = "tokio02")] #[cfg(feature = "tokio02")]
#[cfg_attr(docsrs, doc(cfg(feature = "tokio02")))] pub use crate::transport::Tokio02Transport;
#[async_trait] #[doc(hidden)]
pub trait Tokio02Transport {
/// Response produced by the Transport
type Ok;
/// Error produced by the Transport
type Error;
/// Sends the email
#[cfg(feature = "builder")]
#[cfg_attr(docsrs, doc(cfg(feature = "builder")))]
// TODO take &Message
async fn send(&self, message: Message) -> Result<Self::Ok, Self::Error> {
let raw = message.formatted();
let envelope = message.envelope();
self.send_raw(&envelope, &raw).await
}
async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error>;
}
/// tokio 1.x based Transport method for emails
#[cfg(feature = "tokio1")] #[cfg(feature = "tokio1")]
#[cfg_attr(docsrs, doc(cfg(feature = "tokio1")))] pub use crate::transport::Tokio1Transport;
#[async_trait]
pub trait Tokio1Transport {
/// Response produced by the Transport
type Ok;
/// Error produced by the Transport
type Error;
/// Sends the email
#[cfg(feature = "builder")]
#[cfg_attr(docsrs, doc(cfg(feature = "builder")))]
// TODO take &Message
async fn send(&self, message: Message) -> Result<Self::Ok, Self::Error> {
let raw = message.formatted();
let envelope = message.envelope();
self.send_raw(&envelope, &raw).await
}
async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error>;
}
#[cfg(test)] #[cfg(test)]
#[cfg(feature = "builder")] #[cfg(feature = "builder")]

View File

@@ -1,5 +1,7 @@
use std::io::{self, Write}; use std::{
use std::ops::Deref; io::{self, Write},
ops::Deref,
};
use crate::message::header::ContentTransferEncoding; use crate::message::header::ContentTransferEncoding;

View File

@@ -7,7 +7,10 @@ use std::{
str::{from_utf8, FromStr}, str::{from_utf8, FromStr},
}; };
header! { (ContentId, "Content-ID") => [String] } header! {
/// `Content-Id` header, defined in [RFC2045](https://tools.ietf.org/html/rfc2045#section-7)
(ContentId, "Content-ID") => [String]
}
/// `Content-Transfer-Encoding` of the body /// `Content-Transfer-Encoding` of the body
/// ///

View File

@@ -1,8 +1,4 @@
/*! //! Headers widely used in email messages
## Headers widely used in email messages
*/
mod content; mod content;
mod mailbox; mod mailbox;

View File

@@ -5,6 +5,7 @@ use hyperx::{
use std::{fmt::Result as FmtResult, str::from_utf8}; use std::{fmt::Result as FmtResult, str::from_utf8};
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
/// Message format version, defined in [RFC2045](https://tools.ietf.org/html/rfc2045#section-4)
pub struct MimeVersion { pub struct MimeVersion {
pub major: u8, pub major: u8,
pub minor: u8, pub minor: u8,

View File

@@ -6,8 +6,9 @@ use hyperx::{
use std::{fmt::Result as FmtResult, str::from_utf8}; use std::{fmt::Result as FmtResult, str::from_utf8};
macro_rules! text_header { macro_rules! text_header {
( $type_name: ident, $header_name: expr ) => { ($(#[$attr:meta])* Header($type_name: ident, $header_name: expr )) => {
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
$(#[$attr])*
pub struct $type_name(pub String); pub struct $type_name(pub String);
impl Header for $type_name { impl Header for $type_name {
@@ -33,13 +34,41 @@ macro_rules! text_header {
}; };
} }
text_header!(Subject, "Subject"); text_header!(
text_header!(Comments, "Comments"); /// `Subject` of the message, defined in [RFC5322](https://tools.ietf.org/html/rfc5322#section-3.6.5)
text_header!(Keywords, "Keywords"); Header(Subject, "Subject")
text_header!(InReplyTo, "In-Reply-To"); );
text_header!(References, "References"); text_header!(
text_header!(MessageId, "Message-Id"); /// `Comments` of the message, defined in [RFC5322](https://tools.ietf.org/html/rfc5322#section-3.6.5)
text_header!(UserAgent, "User-Agent"); Header(Comments, "Comments")
);
text_header!(
/// `Keywords` header. Should contain a comma-separated list of one or more
/// words or quoted-strings, defined in [RFC5322](https://tools.ietf.org/html/rfc5322#section-3.6.5)
Header(Keywords, "Keywords")
);
text_header!(
/// `In-Reply-To` header. Contains one or more
/// unique message identifiers,
/// defined in [RFC5322](https://tools.ietf.org/html/rfc5322#section-3.6.4)
Header(InReplyTo, "In-Reply-To")
);
text_header!(
/// `References` header. Contains one or more
/// unique message identifiers,
/// defined in [RFC5322](https://tools.ietf.org/html/rfc5322#section-3.6.4)
Header(References, "References")
);
text_header!(
/// `Message-Id` header. Contains a unique message identifier,
/// defined in [RFC5322](https://tools.ietf.org/html/rfc5322#section-3.6.4)
Header(MessageId, "Message-Id")
);
text_header!(
/// `User-Agent` header. Contains information about the client,
/// defined in [draft-melnikov-email-user-agent-00](https://tools.ietf.org/html/draft-melnikov-email-user-agent-00#section-3)
Header(UserAgent, "User-Agent")
);
fn parse_text(raw: &[u8]) -> HyperResult<String> { fn parse_text(raw: &[u8]) -> HyperResult<String> {
if let Ok(src) = from_utf8(raw) { if let Ok(src) = from_utf8(raw) {

View File

@@ -54,7 +54,7 @@ impl Mailbox {
/// # Examples /// # Examples
/// ///
/// ``` /// ```
/// use lettre::{Address, message::Mailbox}; /// use lettre::{message::Mailbox, Address};
/// ///
/// # use std::error::Error; /// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> { /// # fn main() -> Result<(), Box<dyn Error>> {
@@ -163,7 +163,10 @@ impl Mailboxes {
/// # Examples /// # Examples
/// ///
/// ``` /// ```
/// use lettre::{Address, message::{Mailbox, Mailboxes}}; /// use lettre::{
/// message::{Mailbox, Mailboxes},
/// Address,
/// };
/// ///
/// # use std::error::Error; /// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> { /// # fn main() -> Result<(), Box<dyn Error>> {
@@ -182,7 +185,10 @@ impl Mailboxes {
/// # Examples /// # Examples
/// ///
/// ``` /// ```
/// use lettre::{Address, message::{Mailbox, Mailboxes}}; /// use lettre::{
/// message::{Mailbox, Mailboxes},
/// Address,
/// };
/// ///
/// # use std::error::Error; /// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> { /// # fn main() -> Result<(), Box<dyn Error>> {
@@ -201,7 +207,10 @@ impl Mailboxes {
/// # Examples /// # Examples
/// ///
/// ``` /// ```
/// use lettre::{Address, message::{Mailbox, Mailboxes}}; /// use lettre::{
/// message::{Mailbox, Mailboxes},
/// Address,
/// };
/// ///
/// # use std::error::Error; /// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> { /// # fn main() -> Result<(), Box<dyn Error>> {
@@ -225,7 +234,10 @@ impl Mailboxes {
/// # Examples /// # Examples
/// ///
/// ``` /// ```
/// use lettre::{Address, message::{Mailbox, Mailboxes}}; /// use lettre::{
/// message::{Mailbox, Mailboxes},
/// Address,
/// };
/// ///
/// # use std::error::Error; /// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> { /// # fn main() -> Result<(), Box<dyn Error>> {

View File

@@ -6,15 +6,12 @@ use mime::Mime;
use rand::Rng; use rand::Rng;
/// MIME part variants /// MIME part variants
///
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Part { pub enum Part {
/// Single part with content /// Single part with content
///
Single(SinglePart), Single(SinglePart),
/// Multiple parts of content /// Multiple parts of content
///
Multi(MultiPart), Multi(MultiPart),
} }
@@ -37,11 +34,9 @@ impl Part {
} }
/// Parts of multipart body /// Parts of multipart body
///
pub type Parts = Vec<Part>; pub type Parts = Vec<Part>;
/// Creates builder for single part /// Creates builder for single part
///
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SinglePartBuilder { pub struct SinglePartBuilder {
headers: Headers, headers: Headers,
@@ -92,17 +87,16 @@ impl Default for SinglePartBuilder {
/// # Example /// # Example
/// ///
/// ``` /// ```
/// use lettre::message::{SinglePart, header}; /// use lettre::message::{header, SinglePart};
/// ///
/// # use std::error::Error; /// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> { /// # fn main() -> Result<(), Box<dyn Error>> {
/// let part = SinglePart::builder() /// let part = SinglePart::builder()
/// .header(header::ContentType("text/plain; charset=utf8".parse()?)) /// .header(header::ContentType("text/plain; charset=utf8".parse()?))
/// .body(String::from("Текст письма в уникоде")); /// .body(String::from("Текст письма в уникоде"));
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
///
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SinglePart { pub struct SinglePart {
headers: Headers, headers: Headers,
@@ -116,30 +110,6 @@ impl SinglePart {
SinglePartBuilder::new() SinglePartBuilder::new()
} }
#[doc(hidden)]
#[deprecated = "Replaced by SinglePart::builder(), which chooses the best Content-Transfer-Encoding based on the provided body"]
pub fn seven_bit() -> SinglePartBuilder {
Self::builder().header(ContentTransferEncoding::SevenBit)
}
#[doc(hidden)]
#[deprecated = "Replaced by SinglePart::builder(), which chooses the best Content-Transfer-Encoding based on the provided body"]
pub fn quoted_printable() -> SinglePartBuilder {
Self::builder().header(ContentTransferEncoding::QuotedPrintable)
}
#[doc(hidden)]
#[deprecated = "Replaced by SinglePart::builder(), which chooses the best Content-Transfer-Encoding based on the provided body"]
pub fn base64() -> SinglePartBuilder {
Self::builder().header(ContentTransferEncoding::Base64)
}
#[doc(hidden)]
#[deprecated = "Replaced by SinglePart::builder(), which chooses the best Content-Transfer-Encoding based on the provided body"]
pub fn eight_bit() -> SinglePartBuilder {
Self::builder().header(ContentTransferEncoding::EightBit)
}
/// Get the headers from singlepart /// Get the headers from singlepart
#[inline] #[inline]
pub fn headers(&self) -> &Headers { pub fn headers(&self) -> &Headers {
@@ -170,7 +140,6 @@ impl EmailFormat for SinglePart {
} }
/// The kind of multipart /// The kind of multipart
///
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum MultiPartKind { pub enum MultiPartKind {
/// Mixed kind to combine unrelated content parts /// Mixed kind to combine unrelated content parts
@@ -257,7 +226,6 @@ impl From<MultiPartKind> for Mime {
} }
/// Multipart builder /// Multipart builder
///
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct MultiPartBuilder { pub struct MultiPartBuilder {
headers: Headers, headers: Headers,
@@ -323,7 +291,6 @@ impl Default for MultiPartBuilder {
} }
/// Multipart variant with parts /// Multipart variant with parts
///
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct MultiPart { pub struct MultiPart {
headers: Headers, headers: Headers,

View File

@@ -114,7 +114,6 @@
//! //!
//! <p><b>Hello</b>, <i>world</i>! <img src="cid:123"></p> //! <p><b>Hello</b>, <i>world</i>! <img src="cid:123"></p>
//! --0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1-- //! --0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1--
//!
//! ``` //! ```
//! </details> //! </details>
//! //!
@@ -125,8 +124,8 @@
//! //!
//! ```rust //! ```rust
//! # use std::error::Error; //! # use std::error::Error;
//! use lettre::message::{header, Body, Message, MultiPart, Part, SinglePart};
//! use std::fs; //! use std::fs;
//! use lettre::message::{Body, header, Message, MultiPart, Part, SinglePart};
//! //!
//! # fn main() -> Result<(), Box<dyn Error>> { //! # fn main() -> Result<(), Box<dyn Error>> {
//! let image = fs::read("docs/lettre.png")?; //! let image = fs::read("docs/lettre.png")?;
@@ -236,7 +235,6 @@
//! //!
//! fn main() { println!("Hello, World!") } //! fn main() { println!("Hello, World!") }
//! --0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1-- //! --0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1--
//!
//! ``` //! ```
//! </details> //! </details>
@@ -252,8 +250,7 @@ mod mailbox;
mod mimebody; mod mimebody;
mod utf8_b; mod utf8_b;
use std::convert::TryFrom; use std::{convert::TryFrom, time::SystemTime};
use std::time::SystemTime;
use uuid::Uuid; use uuid::Uuid;
@@ -265,7 +262,8 @@ use crate::{
const DEFAULT_MESSAGE_ID_DOMAIN: &str = "localhost"; const DEFAULT_MESSAGE_ID_DOMAIN: &str = "localhost";
pub trait EmailFormat { /// Something that can be formatted as an email message
trait EmailFormat {
// Use a writer? // Use a writer?
fn format(&self, out: &mut Vec<u8>); fn format(&self, out: &mut Vec<u8>);
} }
@@ -334,7 +332,7 @@ impl MessageBuilder {
/// Set `Sender` header. Should be used when providing several `From` mailboxes. /// Set `Sender` header. Should be used when providing several `From` mailboxes.
/// ///
/// https://tools.ietf.org/html/rfc5322#section-3.6.2 /// Defined in [RFC5322](https://tools.ietf.org/html/rfc5322#section-3.6.2).
/// ///
/// Shortcut for `self.header(header::Sender(mbox))`. /// Shortcut for `self.header(header::Sender(mbox))`.
pub fn sender(self, mbox: Mailbox) -> Self { pub fn sender(self, mbox: Mailbox) -> Self {
@@ -343,7 +341,7 @@ impl MessageBuilder {
/// Set or add mailbox to `From` header /// Set or add mailbox to `From` header
/// ///
/// https://tools.ietf.org/html/rfc5322#section-3.6.2 /// Defined in [RFC5322](https://tools.ietf.org/html/rfc5322#section-3.6.2).
/// ///
/// Shortcut for `self.mailbox(header::From(mbox))`. /// Shortcut for `self.mailbox(header::From(mbox))`.
pub fn from(self, mbox: Mailbox) -> Self { pub fn from(self, mbox: Mailbox) -> Self {
@@ -352,7 +350,7 @@ impl MessageBuilder {
/// Set or add mailbox to `ReplyTo` header /// Set or add mailbox to `ReplyTo` header
/// ///
/// https://tools.ietf.org/html/rfc5322#section-3.6.2 /// Defined in [RFC5322](https://tools.ietf.org/html/rfc5322#section-3.6.2).
/// ///
/// Shortcut for `self.mailbox(header::ReplyTo(mbox))`. /// Shortcut for `self.mailbox(header::ReplyTo(mbox))`.
pub fn reply_to(self, mbox: Mailbox) -> Self { pub fn reply_to(self, mbox: Mailbox) -> Self {

View File

@@ -9,8 +9,8 @@
//! //!
//! # #[cfg(all(feature = "file-transport", feature = "builder"))] //! # #[cfg(all(feature = "file-transport", feature = "builder"))]
//! # fn main() -> Result<(), Box<dyn Error>> { //! # fn main() -> Result<(), Box<dyn Error>> {
//! use lettre::{FileTransport, Message, Transport};
//! use std::env::temp_dir; //! use std::env::temp_dir;
//! use lettre::{Transport, Message, FileTransport};
//! //!
//! // Write to the local temp directory //! // Write to the local temp directory
//! let sender = FileTransport::new(temp_dir()); //! let sender = FileTransport::new(temp_dir());
@@ -41,8 +41,8 @@
//! //!
//! # #[cfg(all(feature = "file-transport-envelope", feature = "builder"))] //! # #[cfg(all(feature = "file-transport-envelope", feature = "builder"))]
//! # fn main() -> Result<(), Box<dyn Error>> { //! # fn main() -> Result<(), Box<dyn Error>> {
//! use lettre::{FileTransport, Message, Transport};
//! use std::env::temp_dir; //! use std::env::temp_dir;
//! use lettre::{Transport, Message, FileTransport};
//! //!
//! // Write to the local temp directory //! // Write to the local temp directory
//! let sender = FileTransport::with_envelope(temp_dir()); //! let sender = FileTransport::with_envelope(temp_dir());
@@ -70,10 +70,10 @@
//! # #[cfg(all(feature = "tokio1", feature = "file-transport", feature = "builder"))] //! # #[cfg(all(feature = "tokio1", feature = "file-transport", feature = "builder"))]
//! # async fn run() -> Result<(), Box<dyn Error>> { //! # async fn run() -> Result<(), Box<dyn Error>> {
//! use std::env::temp_dir; //! use std::env::temp_dir;
//! use lettre::{Tokio1Transport, Message, FileTransport}; //! use lettre::{AsyncTransport, Tokio1Executor, Message, AsyncFileTransport};
//! //!
//! // Write to the local temp directory //! // Write to the local temp directory
//! let sender = FileTransport::new(temp_dir()); //! let sender = AsyncFileTransport::<Tokio1Executor>::new(temp_dir());
//! let email = Message::builder() //! let email = Message::builder()
//! .from("NoBody <nobody@domain.tld>".parse()?) //! .from("NoBody <nobody@domain.tld>".parse()?)
//! .reply_to("Yuin <yuin@domain.tld>".parse()?) //! .reply_to("Yuin <yuin@domain.tld>".parse()?)
@@ -95,10 +95,10 @@
//! # #[cfg(all(feature = "async-std1", feature = "file-transport", feature = "builder"))] //! # #[cfg(all(feature = "async-std1", feature = "file-transport", feature = "builder"))]
//! # async fn run() -> Result<(), Box<dyn Error>> { //! # async fn run() -> Result<(), Box<dyn Error>> {
//! use std::env::temp_dir; //! use std::env::temp_dir;
//! use lettre::{AsyncStd1Transport, Message, FileTransport}; //! use lettre::{AsyncTransport, AsyncStd1Executor, Message, AsyncFileTransport};
//! //!
//! // Write to the local temp directory //! // Write to the local temp directory
//! let sender = FileTransport::new(temp_dir()); //! let sender = AsyncFileTransport::<AsyncStd1Executor>::new(temp_dir());
//! let email = Message::builder() //! let email = Message::builder()
//! .from("NoBody <nobody@domain.tld>".parse()?) //! .from("NoBody <nobody@domain.tld>".parse()?)
//! .reply_to("Yuin <yuin@domain.tld>".parse()?) //! .reply_to("Yuin <yuin@domain.tld>".parse()?)
@@ -133,16 +133,13 @@
//! ``` //! ```
pub use self::error::Error; pub use self::error::Error;
use crate::address::Envelope; use crate::{address::Envelope, Transport};
#[cfg(feature = "async-std1")] #[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio1"))]
use crate::AsyncStd1Transport; use crate::{AsyncTransport, Executor};
#[cfg(feature = "tokio02")]
use crate::Tokio02Transport;
#[cfg(feature = "tokio1")]
use crate::Tokio1Transport;
use crate::Transport;
#[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio1"))] #[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio1"))]
use async_trait::async_trait; use async_trait::async_trait;
#[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio1"))]
use std::marker::PhantomData;
use std::{ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
str, str,
@@ -162,6 +159,14 @@ pub struct FileTransport {
save_envelope: bool, save_envelope: bool,
} }
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio1"))]
pub struct AsyncFileTransport<E: Executor> {
inner: FileTransport,
marker_: PhantomData<E>,
}
impl FileTransport { impl FileTransport {
/// Creates a new transport to the given directory /// Creates a new transport to the given directory
/// ///
@@ -187,11 +192,71 @@ impl FileTransport {
} }
} }
/// Read a message that was written using the file transport.
///
/// Reads the envelope and the raw message content.
#[cfg(feature = "file-transport-envelope")]
pub fn read(&self, email_id: &str) -> Result<(Envelope, Vec<u8>), Error> {
use std::fs;
let eml_file = self.path.join(format!("{}.eml", email_id));
let eml = fs::read(eml_file)?;
let json_file = self.path.join(format!("{}.json", email_id));
let json = fs::read(&json_file)?;
let envelope = serde_json::from_slice(&json)?;
Ok((envelope, eml))
}
fn path(&self, email_id: &Uuid, extension: &str) -> PathBuf { fn path(&self, email_id: &Uuid, extension: &str) -> PathBuf {
self.path.join(format!("{}.{}", email_id, extension)) self.path.join(format!("{}.{}", email_id, extension))
} }
} }
#[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio1"))]
impl<E> AsyncFileTransport<E>
where
E: Executor,
{
/// Creates a new transport to the given directory
///
/// Writes the email content in eml format.
pub fn new<P: AsRef<Path>>(path: P) -> Self {
Self {
inner: FileTransport::new(path),
marker_: PhantomData,
}
}
/// Creates a new transport to the given directory
///
/// Writes the email content in eml format and the envelope
/// in json format.
#[cfg(feature = "file-transport-envelope")]
pub fn with_envelope<P: AsRef<Path>>(path: P) -> Self {
Self {
inner: FileTransport::with_envelope(path),
marker_: PhantomData,
}
}
/// Read a message that was written using the file transport.
///
/// Reads the envelope and the raw message content.
#[cfg(feature = "file-transport-envelope")]
pub async fn read(&self, email_id: &str) -> Result<(Envelope, Vec<u8>), Error> {
let eml_file = self.inner.path.join(format!("{}.eml", email_id));
let eml = E::fs_read(&eml_file).await?;
let json_file = self.inner.path.join(format!("{}.json", email_id));
let json = E::fs_read(&json_file).await?;
let envelope = serde_json::from_slice(&json)?;
Ok((envelope, eml))
}
}
impl Transport for FileTransport { impl Transport for FileTransport {
type Ok = Id; type Ok = Id;
type Error = Error; type Error = Error;
@@ -218,80 +283,27 @@ impl Transport for FileTransport {
} }
} }
#[cfg(feature = "async-std1")] #[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio1"))]
#[async_trait] #[async_trait]
impl AsyncStd1Transport for FileTransport { impl<E> AsyncTransport for AsyncFileTransport<E>
where
E: Executor,
{
type Ok = Id; type Ok = Id;
type Error = Error; type Error = Error;
async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> { async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> {
use async_std::fs;
let email_id = Uuid::new_v4(); let email_id = Uuid::new_v4();
let file = self.path(&email_id, "eml"); let file = self.inner.path(&email_id, "eml");
fs::write(file, email).await?; E::fs_write(&file, email).await?;
#[cfg(feature = "file-transport-envelope")] #[cfg(feature = "file-transport-envelope")]
{ {
if self.save_envelope { if self.inner.save_envelope {
let file = self.path(&email_id, "json"); let file = self.inner.path(&email_id, "json");
fs::write(file, serde_json::to_string(&envelope)?).await?; let buf = serde_json::to_vec(&envelope)?;
} E::fs_write(&file, &buf).await?;
}
// use envelope anyway
let _ = envelope;
Ok(email_id.to_string())
}
}
#[cfg(feature = "tokio02")]
#[async_trait]
impl Tokio02Transport for FileTransport {
type Ok = Id;
type Error = Error;
async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> {
use tokio02_crate::fs;
let email_id = Uuid::new_v4();
let file = self.path(&email_id, "eml");
fs::write(file, email).await?;
#[cfg(feature = "file-transport-envelope")]
{
if self.save_envelope {
let file = self.path(&email_id, "json");
fs::write(file, serde_json::to_string(&envelope)?).await?;
}
}
// use envelope anyway
let _ = envelope;
Ok(email_id.to_string())
}
}
#[cfg(feature = "tokio1")]
#[async_trait]
impl Tokio1Transport for FileTransport {
type Ok = Id;
type Error = Error;
async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> {
use tokio1_crate::fs;
let email_id = Uuid::new_v4();
let file = self.path(&email_id, "eml");
fs::write(file, email).await?;
#[cfg(feature = "file-transport-envelope")]
{
if self.save_envelope {
let file = self.path(&email_id, "json");
fs::write(file, serde_json::to_string(&envelope)?).await?;
} }
} }
// use envelope anyway // use envelope anyway

View File

@@ -16,6 +16,26 @@
//! * The `StubTransport` is useful for debugging, and only prints the content of the email in the //! * The `StubTransport` is useful for debugging, and only prints the content of the email in the
//! logs. //! logs.
#[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio1"))]
use async_trait::async_trait;
#[doc(hidden)]
#[deprecated(note = "use lettre::AsyncStd1Transport")]
#[cfg(feature = "async-std1")]
pub use self::AsyncTransport as AsyncStd1Transport;
#[doc(hidden)]
#[deprecated(note = "use lettre::Tokio1Transport")]
#[cfg(feature = "tokio1")]
pub use self::AsyncTransport as Tokio1Transport;
#[doc(hidden)]
#[deprecated(note = "use lettre::Tokio02Transport")]
#[cfg(feature = "tokio02")]
pub use self::AsyncTransport as Tokio02Transport;
use crate::Envelope;
#[cfg(feature = "builder")]
use crate::Message;
#[cfg(feature = "file-transport")] #[cfg(feature = "file-transport")]
#[cfg_attr(docsrs, doc(cfg(feature = "file-transport")))] #[cfg_attr(docsrs, doc(cfg(feature = "file-transport")))]
pub mod file; pub mod file;
@@ -26,3 +46,47 @@ pub mod sendmail;
#[cfg_attr(docsrs, doc(cfg(feature = "smtp-transport")))] #[cfg_attr(docsrs, doc(cfg(feature = "smtp-transport")))]
pub mod smtp; pub mod smtp;
pub mod stub; pub mod stub;
/// Blocking Transport method for emails
pub trait Transport {
/// Response produced by the Transport
type Ok;
/// Error produced by the Transport
type Error;
/// Sends the email
#[cfg(feature = "builder")]
#[cfg_attr(docsrs, doc(cfg(feature = "builder")))]
fn send(&self, message: &Message) -> Result<Self::Ok, Self::Error> {
let raw = message.formatted();
self.send_raw(message.envelope(), &raw)
}
fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error>;
}
/// Async Transport method for emails
#[cfg(any(feature = "tokio02", feature = "tokio1", feature = "async-std1"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "tokio02", feature = "tokio1", feature = "async-std1")))
)]
#[async_trait]
pub trait AsyncTransport {
/// Response produced by the Transport
type Ok;
/// Error produced by the Transport
type Error;
/// Sends the email
#[cfg(feature = "builder")]
#[cfg_attr(docsrs, doc(cfg(feature = "builder")))]
// TODO take &Message
async fn send(&self, message: Message) -> Result<Self::Ok, Self::Error> {
let raw = message.formatted();
let envelope = message.envelope();
self.send_raw(&envelope, &raw).await
}
async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error>;
}

View File

@@ -33,7 +33,7 @@
//! //!
//! # #[cfg(all(feature = "tokio02", feature = "sendmail-transport", feature = "builder"))] //! # #[cfg(all(feature = "tokio02", feature = "sendmail-transport", feature = "builder"))]
//! # async fn run() -> Result<(), Box<dyn Error>> { //! # async fn run() -> Result<(), Box<dyn Error>> {
//! use lettre::{Message, Tokio02Transport, SendmailTransport}; //! use lettre::{Message, AsyncTransport, Tokio02Executor, AsyncSendmailTransport, SendmailTransport};
//! //!
//! let email = Message::builder() //! let email = Message::builder()
//! .from("NoBody <nobody@domain.tld>".parse()?) //! .from("NoBody <nobody@domain.tld>".parse()?)
@@ -42,7 +42,7 @@
//! .subject("Happy new year") //! .subject("Happy new year")
//! .body(String::from("Be happy!"))?; //! .body(String::from("Be happy!"))?;
//! //!
//! let sender = SendmailTransport::new(); //! let sender = AsyncSendmailTransport::<Tokio02Executor>::new();
//! let result = sender.send(email).await; //! let result = sender.send(email).await;
//! assert!(result.is_ok()); //! assert!(result.is_ok());
//! # Ok(()) //! # Ok(())
@@ -56,7 +56,7 @@
//! //!
//! # #[cfg(all(feature = "tokio1", feature = "sendmail-transport", feature = "builder"))] //! # #[cfg(all(feature = "tokio1", feature = "sendmail-transport", feature = "builder"))]
//! # async fn run() -> Result<(), Box<dyn Error>> { //! # async fn run() -> Result<(), Box<dyn Error>> {
//! use lettre::{Message, Tokio1Transport, SendmailTransport}; //! use lettre::{Message, AsyncTransport, Tokio1Executor, AsyncSendmailTransport, SendmailTransport};
//! //!
//! let email = Message::builder() //! let email = Message::builder()
//! .from("NoBody <nobody@domain.tld>".parse()?) //! .from("NoBody <nobody@domain.tld>".parse()?)
@@ -65,7 +65,7 @@
//! .subject("Happy new year") //! .subject("Happy new year")
//! .body(String::from("Be happy!"))?; //! .body(String::from("Be happy!"))?;
//! //!
//! let sender = SendmailTransport::new(); //! let sender = AsyncSendmailTransport::<Tokio1Executor>::new();
//! let result = sender.send(email).await; //! let result = sender.send(email).await;
//! assert!(result.is_ok()); //! assert!(result.is_ok());
//! # Ok(()) //! # Ok(())
@@ -79,7 +79,7 @@
//! //!
//! # #[cfg(all(feature = "async-std1", feature = "sendmail-transport", feature = "builder"))] //! # #[cfg(all(feature = "async-std1", feature = "sendmail-transport", feature = "builder"))]
//! # async fn run() -> Result<(), Box<dyn Error>> { //! # async fn run() -> Result<(), Box<dyn Error>> {
//! use lettre::{Message, AsyncStd1Transport, SendmailTransport}; //! use lettre::{Message, AsyncTransport, AsyncStd1Executor, AsyncSendmailTransport};
//! //!
//! let email = Message::builder() //! let email = Message::builder()
//! .from("NoBody <nobody@domain.tld>".parse()?) //! .from("NoBody <nobody@domain.tld>".parse()?)
@@ -88,7 +88,7 @@
//! .subject("Happy new year") //! .subject("Happy new year")
//! .body(String::from("Be happy!"))?; //! .body(String::from("Be happy!"))?;
//! //!
//! let sender = SendmailTransport::new(); //! let sender = AsyncSendmailTransport::<AsyncStd1Executor>::new();
//! let result = sender.send(email).await; //! let result = sender.send(email).await;
//! assert!(result.is_ok()); //! assert!(result.is_ok());
//! # Ok(()) //! # Ok(())
@@ -96,16 +96,19 @@
//! ``` //! ```
pub use self::error::Error; pub use self::error::Error;
use crate::address::Envelope;
#[cfg(feature = "async-std1")] #[cfg(feature = "async-std1")]
use crate::AsyncStd1Transport; use crate::AsyncStd1Executor;
#[cfg(feature = "tokio02")] #[cfg(feature = "tokio02")]
use crate::Tokio02Transport; use crate::Tokio02Executor;
#[cfg(feature = "tokio1")] #[cfg(feature = "tokio1")]
use crate::Tokio1Transport; use crate::Tokio1Executor;
use crate::Transport; use crate::{address::Envelope, Transport};
#[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio1"))]
use crate::{AsyncTransport, Executor};
#[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio1"))] #[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio1"))]
use async_trait::async_trait; use async_trait::async_trait;
#[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio1"))]
use std::marker::PhantomData;
use std::{ use std::{
ffi::OsString, ffi::OsString,
io::prelude::*, io::prelude::*,
@@ -123,6 +126,14 @@ pub struct SendmailTransport {
command: OsString, command: OsString,
} }
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio1"))]
pub struct AsyncSendmailTransport<E: Executor> {
inner: SendmailTransport,
marker_: PhantomData<E>,
}
impl SendmailTransport { impl SendmailTransport {
/// Creates a new transport with the default `/usr/sbin/sendmail` command /// Creates a new transport with the default `/usr/sbin/sendmail` command
pub fn new() -> SendmailTransport { pub fn new() -> SendmailTransport {
@@ -151,12 +162,34 @@ impl SendmailTransport {
.stderr(Stdio::piped()); .stderr(Stdio::piped());
c c
} }
}
#[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio1"))]
impl<E> AsyncSendmailTransport<E>
where
E: Executor,
{
/// Creates a new transport with the default `/usr/sbin/sendmail` command
pub fn new() -> Self {
Self {
inner: SendmailTransport::new(),
marker_: PhantomData,
}
}
/// Creates a new transport to the given sendmail command
pub fn new_with_command<S: Into<OsString>>(command: S) -> Self {
Self {
inner: SendmailTransport::new_with_command(command),
marker_: PhantomData,
}
}
#[cfg(feature = "tokio02")] #[cfg(feature = "tokio02")]
fn tokio02_command(&self, envelope: &Envelope) -> tokio02_crate::process::Command { fn tokio02_command(&self, envelope: &Envelope) -> tokio02_crate::process::Command {
use tokio02_crate::process::Command; use tokio02_crate::process::Command;
let mut c = Command::new(&self.command); let mut c = Command::new(&self.inner.command);
c.kill_on_drop(true); c.kill_on_drop(true);
c.arg("-i"); c.arg("-i");
if let Some(from) = envelope.from() { if let Some(from) = envelope.from() {
@@ -174,7 +207,7 @@ impl SendmailTransport {
fn tokio1_command(&self, envelope: &Envelope) -> tokio1_crate::process::Command { fn tokio1_command(&self, envelope: &Envelope) -> tokio1_crate::process::Command {
use tokio1_crate::process::Command; use tokio1_crate::process::Command;
let mut c = Command::new(&self.command); let mut c = Command::new(&self.inner.command);
c.kill_on_drop(true); c.kill_on_drop(true);
c.arg("-i"); c.arg("-i");
if let Some(from) = envelope.from() { if let Some(from) = envelope.from() {
@@ -192,7 +225,7 @@ impl SendmailTransport {
fn async_std_command(&self, envelope: &Envelope) -> async_std::process::Command { fn async_std_command(&self, envelope: &Envelope) -> async_std::process::Command {
use async_std::process::Command; use async_std::process::Command;
let mut c = Command::new(&self.command); let mut c = Command::new(&self.inner.command);
// TODO: figure out why enabling this kills it earlier // TODO: figure out why enabling this kills it earlier
// c.kill_on_drop(true); // c.kill_on_drop(true);
c.arg("-i"); c.arg("-i");
@@ -214,6 +247,16 @@ impl Default for SendmailTransport {
} }
} }
#[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio1"))]
impl<E> Default for AsyncSendmailTransport<E>
where
E: Executor,
{
fn default() -> Self {
Self::new()
}
}
impl Transport for SendmailTransport { impl Transport for SendmailTransport {
type Ok = (); type Ok = ();
type Error = Error; type Error = Error;
@@ -235,7 +278,7 @@ impl Transport for SendmailTransport {
#[cfg(feature = "async-std1")] #[cfg(feature = "async-std1")]
#[async_trait] #[async_trait]
impl AsyncStd1Transport for SendmailTransport { impl AsyncTransport for AsyncSendmailTransport<AsyncStd1Executor> {
type Ok = (); type Ok = ();
type Error = Error; type Error = Error;
@@ -260,7 +303,7 @@ impl AsyncStd1Transport for SendmailTransport {
#[cfg(feature = "tokio02")] #[cfg(feature = "tokio02")]
#[async_trait] #[async_trait]
impl Tokio02Transport for SendmailTransport { impl AsyncTransport for AsyncSendmailTransport<Tokio02Executor> {
type Ok = (); type Ok = ();
type Error = Error; type Error = Error;
@@ -285,7 +328,7 @@ impl Tokio02Transport for SendmailTransport {
#[cfg(feature = "tokio1")] #[cfg(feature = "tokio1")]
#[async_trait] #[async_trait]
impl Tokio1Transport for SendmailTransport { impl AsyncTransport for AsyncSendmailTransport<Tokio1Executor> {
type Ok = (); type Ok = ();
type Error = Error; type Error = Error;

View File

@@ -1,28 +1,29 @@
use std::marker::PhantomData;
use async_trait::async_trait; use async_trait::async_trait;
#[cfg(any(feature = "tokio02", feature = "tokio1", feature = "async-std1"))]
use super::Tls;
use super::{ use super::{
client::AsyncSmtpConnection, ClientId, Credentials, Error, Mechanism, Response, SmtpInfo, client::AsyncSmtpConnection, ClientId, Credentials, Error, Mechanism, Response, SmtpInfo,
}; };
#[cfg(feature = "async-std1")] #[cfg(feature = "async-std1")]
use crate::AsyncStd1Transport; use crate::AsyncStd1Executor;
use crate::Envelope; #[cfg(any(feature = "tokio02", feature = "tokio1", feature = "async-std1"))]
use crate::AsyncTransport;
#[cfg(feature = "tokio02")] #[cfg(feature = "tokio02")]
use crate::Tokio02Transport; use crate::Tokio02Executor;
#[cfg(feature = "tokio1")] #[cfg(feature = "tokio1")]
use crate::Tokio1Transport; use crate::Tokio1Executor;
use crate::{Envelope, Executor};
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
#[derive(Clone)] pub struct AsyncSmtpTransport<E> {
pub struct AsyncSmtpTransport<C> {
// TODO: pool // TODO: pool
inner: AsyncSmtpClient<C>, inner: AsyncSmtpClient<E>,
} }
#[cfg(feature = "tokio02")] #[cfg(feature = "tokio02")]
#[async_trait] #[async_trait]
impl Tokio02Transport for AsyncSmtpTransport<Tokio02Connector> { impl AsyncTransport for AsyncSmtpTransport<Tokio02Executor> {
type Ok = Response; type Ok = Response;
type Error = Error; type Error = Error;
@@ -40,7 +41,7 @@ impl Tokio02Transport for AsyncSmtpTransport<Tokio02Connector> {
#[cfg(feature = "tokio1")] #[cfg(feature = "tokio1")]
#[async_trait] #[async_trait]
impl Tokio1Transport for AsyncSmtpTransport<Tokio1Connector> { impl AsyncTransport for AsyncSmtpTransport<Tokio1Executor> {
type Ok = Response; type Ok = Response;
type Error = Error; type Error = Error;
@@ -58,7 +59,7 @@ impl Tokio1Transport for AsyncSmtpTransport<Tokio1Connector> {
#[cfg(feature = "async-std1")] #[cfg(feature = "async-std1")]
#[async_trait] #[async_trait]
impl AsyncStd1Transport for AsyncSmtpTransport<AsyncStd1Connector> { impl AsyncTransport for AsyncSmtpTransport<AsyncStd1Executor> {
type Ok = Response; type Ok = Response;
type Error = Error; type Error = Error;
@@ -74,9 +75,9 @@ impl AsyncStd1Transport for AsyncSmtpTransport<AsyncStd1Connector> {
} }
} }
impl<C> AsyncSmtpTransport<C> impl<E> AsyncSmtpTransport<E>
where where
C: AsyncSmtpConnector, E: Executor,
{ {
/// Simple and secure transport, using TLS connections to communicate with the SMTP server /// Simple and secure transport, using TLS connections to communicate with the SMTP server
/// ///
@@ -93,7 +94,7 @@ where
feature = "async-std1-rustls-tls" feature = "async-std1-rustls-tls"
))] ))]
pub fn relay(relay: &str) -> Result<AsyncSmtpTransportBuilder, Error> { pub fn relay(relay: &str) -> Result<AsyncSmtpTransportBuilder, Error> {
use super::{TlsParameters, SUBMISSIONS_PORT}; use super::{Tls, TlsParameters, SUBMISSIONS_PORT};
let tls_parameters = TlsParameters::new(relay.into())?; let tls_parameters = TlsParameters::new(relay.into())?;
@@ -122,7 +123,7 @@ where
feature = "async-std1-rustls-tls" feature = "async-std1-rustls-tls"
))] ))]
pub fn starttls_relay(relay: &str) -> Result<AsyncSmtpTransportBuilder, Error> { pub fn starttls_relay(relay: &str) -> Result<AsyncSmtpTransportBuilder, Error> {
use super::{TlsParameters, SUBMISSION_PORT}; use super::{Tls, TlsParameters, SUBMISSION_PORT};
let tls_parameters = TlsParameters::new(relay.into())?; let tls_parameters = TlsParameters::new(relay.into())?;
@@ -134,7 +135,7 @@ where
/// Creates a new local SMTP client to port 25 /// Creates a new local SMTP client to port 25
/// ///
/// Shortcut for local unencrypted relay (typical local email daemon that will handle relaying) /// Shortcut for local unencrypted relay (typical local email daemon that will handle relaying)
pub fn unencrypted_localhost() -> AsyncSmtpTransport<C> { pub fn unencrypted_localhost() -> AsyncSmtpTransport<E> {
Self::builder_dangerous("localhost").build() Self::builder_dangerous("localhost").build()
} }
@@ -158,6 +159,17 @@ where
} }
} }
impl<E> Clone for AsyncSmtpTransport<E>
where
E: Executor,
{
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}
/// Contains client configuration. /// Contains client configuration.
/// Instances of this struct can be created using functions of [`AsyncSmtpTransport`]. /// Instances of this struct can be created using functions of [`AsyncSmtpTransport`].
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
@@ -201,41 +213,39 @@ impl AsyncSmtpTransportBuilder {
feature = "async-std1-native-tls", feature = "async-std1-native-tls",
feature = "async-std1-rustls-tls" feature = "async-std1-rustls-tls"
))] ))]
pub fn tls(mut self, tls: Tls) -> Self { pub fn tls(mut self, tls: super::Tls) -> Self {
self.info.tls = tls; self.info.tls = tls;
self self
} }
/// Build the transport (with default pool if enabled) /// Build the transport (with default pool if enabled)
pub fn build<C>(self) -> AsyncSmtpTransport<C> pub fn build<E>(self) -> AsyncSmtpTransport<E>
where where
C: AsyncSmtpConnector, E: Executor,
{ {
let connector = Default::default();
let client = AsyncSmtpClient { let client = AsyncSmtpClient {
connector,
info: self.info, info: self.info,
marker_: PhantomData,
}; };
AsyncSmtpTransport { inner: client } AsyncSmtpTransport { inner: client }
} }
} }
/// Build client /// Build client
#[derive(Clone)]
pub struct AsyncSmtpClient<C> { pub struct AsyncSmtpClient<C> {
connector: C,
info: SmtpInfo, info: SmtpInfo,
marker_: PhantomData<C>,
} }
impl<C> AsyncSmtpClient<C> impl<E> AsyncSmtpClient<E>
where where
C: AsyncSmtpConnector, E: Executor,
{ {
/// Creates a new connection directly usable to send emails /// Creates a new connection directly usable to send emails
/// ///
/// Handles encryption and authentication /// Handles encryption and authentication
pub async fn connection(&self) -> Result<AsyncSmtpConnection, Error> { pub async fn connection(&self) -> Result<AsyncSmtpConnection, Error> {
let mut conn = C::connect( let mut conn = E::connect(
&self.info.server, &self.info.server,
self.info.port, self.info.port,
&self.info.hello_name, &self.info.hello_name,
@@ -250,152 +260,33 @@ where
} }
} }
#[async_trait] impl<E> AsyncSmtpClient<E>
pub trait AsyncSmtpConnector: Default + private::Sealed { where
async fn connect( E: Executor,
hostname: &str, {
port: u16, fn clone(&self) -> Self {
hello_name: &ClientId, Self {
tls: &Tls, info: self.info.clone(),
) -> Result<AsyncSmtpConnection, Error>; marker_: PhantomData,
}
}
} }
#[derive(Debug, Copy, Clone, Default)] #[doc(hidden)]
#[deprecated(note = "use lettre::Executor instead")]
pub use crate::Executor as AsyncSmtpConnector;
#[doc(hidden)]
#[deprecated(note = "use lettre::Tokio02Executor instead")]
#[cfg(feature = "tokio02")] #[cfg(feature = "tokio02")]
#[cfg_attr(docsrs, doc(cfg(feature = "tokio02")))] pub type Tokio02Connector = crate::Tokio02Executor;
pub struct Tokio02Connector;
#[async_trait] #[doc(hidden)]
#[cfg(feature = "tokio02")] #[deprecated(note = "use lettre::Tokio1Executor instead")]
impl AsyncSmtpConnector for Tokio02Connector {
async fn connect(
hostname: &str,
port: u16,
hello_name: &ClientId,
tls: &Tls,
) -> Result<AsyncSmtpConnection, Error> {
#[allow(clippy::match_single_binding)]
let tls_parameters = match tls {
#[cfg(any(feature = "tokio02-native-tls", feature = "tokio02-rustls-tls"))]
Tls::Wrapper(ref tls_parameters) => Some(tls_parameters.clone()),
_ => None,
};
#[allow(unused_mut)]
let mut conn =
AsyncSmtpConnection::connect_tokio02(hostname, port, hello_name, tls_parameters)
.await?;
#[cfg(any(feature = "tokio02-native-tls", feature = "tokio02-rustls-tls"))]
match tls {
Tls::Opportunistic(ref tls_parameters) => {
if conn.can_starttls() {
conn.starttls(tls_parameters.clone(), hello_name).await?;
}
}
Tls::Required(ref tls_parameters) => {
conn.starttls(tls_parameters.clone(), hello_name).await?;
}
_ => (),
}
Ok(conn)
}
}
#[derive(Debug, Copy, Clone, Default)]
#[cfg(feature = "tokio1")] #[cfg(feature = "tokio1")]
#[cfg_attr(docsrs, doc(cfg(feature = "tokio1")))] pub type Tokio1Connector = crate::Tokio1Executor;
pub struct Tokio1Connector;
#[async_trait] #[doc(hidden)]
#[cfg(feature = "tokio1")] #[deprecated(note = "use lettre::AsyncStd1Executor instead")]
impl AsyncSmtpConnector for Tokio1Connector {
async fn connect(
hostname: &str,
port: u16,
hello_name: &ClientId,
tls: &Tls,
) -> Result<AsyncSmtpConnection, Error> {
#[allow(clippy::match_single_binding)]
let tls_parameters = match tls {
#[cfg(any(feature = "tokio1-native-tls", feature = "tokio1-rustls-tls"))]
Tls::Wrapper(ref tls_parameters) => Some(tls_parameters.clone()),
_ => None,
};
#[allow(unused_mut)]
let mut conn =
AsyncSmtpConnection::connect_tokio1(hostname, port, hello_name, tls_parameters).await?;
#[cfg(any(feature = "tokio1-native-tls", feature = "tokio1-rustls-tls"))]
match tls {
Tls::Opportunistic(ref tls_parameters) => {
if conn.can_starttls() {
conn.starttls(tls_parameters.clone(), hello_name).await?;
}
}
Tls::Required(ref tls_parameters) => {
conn.starttls(tls_parameters.clone(), hello_name).await?;
}
_ => (),
}
Ok(conn)
}
}
#[derive(Debug, Copy, Clone, Default)]
#[cfg(feature = "async-std1")] #[cfg(feature = "async-std1")]
#[cfg_attr(docsrs, doc(cfg(feature = "async-std1")))] pub type AsyncStd1Connector = crate::AsyncStd1Executor;
pub struct AsyncStd1Connector;
#[async_trait]
#[cfg(feature = "async-std1")]
impl AsyncSmtpConnector for AsyncStd1Connector {
async fn connect(
hostname: &str,
port: u16,
hello_name: &ClientId,
tls: &Tls,
) -> Result<AsyncSmtpConnection, Error> {
#[allow(clippy::match_single_binding)]
let tls_parameters = match tls {
#[cfg(any(feature = "async-std1-native-tls", feature = "async-std1-rustls-tls"))]
Tls::Wrapper(ref tls_parameters) => Some(tls_parameters.clone()),
_ => None,
};
#[allow(unused_mut)]
let mut conn =
AsyncSmtpConnection::connect_asyncstd1(hostname, port, hello_name, tls_parameters)
.await?;
#[cfg(any(feature = "async-std1-native-tls", feature = "async-std1-rustls-tls"))]
match tls {
Tls::Opportunistic(ref tls_parameters) => {
if conn.can_starttls() {
conn.starttls(tls_parameters.clone(), hello_name).await?;
}
}
Tls::Required(ref tls_parameters) => {
conn.starttls(tls_parameters.clone(), hello_name).await?;
}
_ => (),
}
Ok(conn)
}
}
mod private {
use super::*;
pub trait Sealed {}
#[cfg(feature = "tokio02")]
impl Sealed for Tokio02Connector {}
#[cfg(feature = "tokio1")]
impl Sealed for Tokio1Connector {}
#[cfg(feature = "async-std1")]
impl Sealed for AsyncStd1Connector {}
}

View File

@@ -39,15 +39,16 @@ where
#[derive(PartialEq, Eq, Copy, Clone, Hash, Debug)] #[derive(PartialEq, Eq, Copy, Clone, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Mechanism { pub enum Mechanism {
/// PLAIN authentication mechanism /// PLAIN authentication mechanism, defined in
/// RFC 4616: https://tools.ietf.org/html/rfc4616 /// [RFC 4616](https://tools.ietf.org/html/rfc4616)
Plain, Plain,
/// LOGIN authentication mechanism /// LOGIN authentication mechanism
/// Obsolete but needed for some providers (like office365) /// Obsolete but needed for some providers (like office365)
/// https://www.ietf.org/archive/id/draft-murchison-sasl-login-00.txt ///
/// Defined in [draft-murchison-sasl-login-00](https://www.ietf.org/archive/id/draft-murchison-sasl-login-00.txt).
Login, Login,
/// Non-standard XOAUTH2 mechanism /// Non-standard XOAUTH2 mechanism, defined in
/// https://developers.google.com/gmail/imap/xoauth2-protocol /// [xoauth2-protocol](https://developers.google.com/gmail/imap/xoauth2-protocol)
Xoauth2, Xoauth2,
} }

View File

@@ -112,9 +112,32 @@ impl AsyncSmtpConnection {
// Mail // Mail
let mut mail_options = vec![]; let mut mail_options = vec![];
if self.server_info().supports_feature(Extension::EightBitMime) { // Internationalization handling
//
// * 8BITMIME: https://tools.ietf.org/html/rfc6152
// * SMTPUTF8: https://tools.ietf.org/html/rfc653
// Check for non-ascii addresses and use the SMTPUTF8 option if any.
if envelope.has_non_ascii_addresses() {
if !self.server_info().supports_feature(Extension::SmtpUtfEight) {
// don't try to send non-ascii addresses (per RFC)
return Err(Error::Client(
"Envelope contains non-ascii chars but server does not support SMTPUTF8",
));
}
mail_options.push(MailParameter::SmtpUtfEight);
}
// Check for non-ascii content in message
if !email.is_ascii() {
if !self.server_info().supports_feature(Extension::EightBitMime) {
return Err(Error::Client(
"Message contains non-ascii chars but server does not support 8BITMIME",
));
}
mail_options.push(MailParameter::Body(MailBodyParameter::EightBitMime)); mail_options.push(MailParameter::Body(MailBodyParameter::EightBitMime));
} }
try_smtp!( try_smtp!(
self.command(Mail::new(envelope.from().cloned(), mail_options)) self.command(Mail::new(envelope.from().cloned(), mail_options))
.await, .await,

View File

@@ -10,8 +10,10 @@ use std::{
task::{Context, Poll}, task::{Context, Poll},
}; };
use futures_io::{AsyncRead as FuturesAsyncRead, AsyncWrite as FuturesAsyncWrite}; use futures_io::{
use futures_io::{Error as IoError, ErrorKind, Result as IoResult}; AsyncRead as FuturesAsyncRead, AsyncWrite as FuturesAsyncWrite, Error as IoError, ErrorKind,
Result as IoResult,
};
#[cfg(feature = "tokio02")] #[cfg(feature = "tokio02")]
use tokio02_crate::io::{AsyncRead as _, AsyncWrite as _}; use tokio02_crate::io::{AsyncRead as _, AsyncWrite as _};
#[cfg(feature = "tokio1")] #[cfg(feature = "tokio1")]
@@ -370,8 +372,7 @@ impl AsyncNetworkStream {
#[cfg(feature = "async-std1-rustls-tls")] #[cfg(feature = "async-std1-rustls-tls")]
return { return {
use async_rustls::webpki::DNSNameRef; use async_rustls::{webpki::DNSNameRef, TlsConnector};
use async_rustls::TlsConnector;
let domain = DNSNameRef::try_from_ascii_str(&domain)?; let domain = DNSNameRef::try_from_ascii_str(&domain)?;

View File

@@ -6,13 +6,15 @@ use std::{
}; };
use super::{ClientCodec, NetworkStream, TlsParameters}; use super::{ClientCodec, NetworkStream, TlsParameters};
use crate::address::Envelope; use crate::{
use crate::transport::smtp::{ address::Envelope,
authentication::{Credentials, Mechanism}, transport::smtp::{
commands::*, authentication::{Credentials, Mechanism},
error::Error, commands::*,
extension::{ClientId, Extension, MailBodyParameter, MailParameter, ServerInfo}, error::Error,
response::{parse_response, Response}, extension::{ClientId, Extension, MailBodyParameter, MailParameter, ServerInfo},
response::{parse_response, Response},
},
}; };
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
@@ -80,9 +82,32 @@ impl SmtpConnection {
// Mail // Mail
let mut mail_options = vec![]; let mut mail_options = vec![];
if self.server_info().supports_feature(Extension::EightBitMime) { // Internationalization handling
//
// * 8BITMIME: https://tools.ietf.org/html/rfc6152
// * SMTPUTF8: https://tools.ietf.org/html/rfc653
// Check for non-ascii addresses and use the SMTPUTF8 option if any.
if envelope.has_non_ascii_addresses() {
if !self.server_info().supports_feature(Extension::SmtpUtfEight) {
// don't try to send non-ascii addresses (per RFC)
return Err(Error::Client(
"Envelope contains non-ascii chars but server does not support SMTPUTF8",
));
}
mail_options.push(MailParameter::SmtpUtfEight);
}
// Check for non-ascii content in message
if !email.is_ascii() {
if !self.server_info().supports_feature(Extension::EightBitMime) {
return Err(Error::Client(
"Message contains non-ascii chars but server does not support 8BITMIME",
));
}
mail_options.push(MailParameter::Body(MailBodyParameter::EightBitMime)); mail_options.push(MailParameter::Body(MailBodyParameter::EightBitMime));
} }
try_smtp!( try_smtp!(
self.command(Mail::new(envelope.from().cloned(), mail_options)), self.command(Mail::new(envelope.from().cloned(), mail_options)),
self self

View File

@@ -52,7 +52,7 @@ mod tls;
/// The codec used for transparency /// The codec used for transparency
#[derive(Default, Clone, Copy, Debug)] #[derive(Default, Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ClientCodec { struct ClientCodec {
escape_count: u8, escape_count: u8,
} }

View File

@@ -65,7 +65,7 @@ impl TlsParametersBuilder {
/// Add a custom root certificate /// Add a custom root certificate
/// ///
/// Can be used to safely connect to a server using a self signed certificate, for example. /// Can be used to safely connect to a server using a self signed certificate, for example.
pub fn add_root_certificate(&mut self, cert: Certificate) -> &mut Self { pub fn add_root_certificate(mut self, cert: Certificate) -> Self {
self.root_certs.push(cert); self.root_certs.push(cert);
self self
} }
@@ -85,10 +85,7 @@ impl TlsParametersBuilder {
/// Hostname verification can only be disabled with the `native-tls` TLS backend. /// Hostname verification can only be disabled with the `native-tls` TLS backend.
#[cfg(feature = "native-tls")] #[cfg(feature = "native-tls")]
#[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))] #[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
pub fn dangerous_accept_invalid_hostnames( pub fn dangerous_accept_invalid_hostnames(mut self, accept_invalid_hostnames: bool) -> Self {
&mut self,
accept_invalid_hostnames: bool,
) -> &mut Self {
self.accept_invalid_hostnames = accept_invalid_hostnames; self.accept_invalid_hostnames = accept_invalid_hostnames;
self self
} }
@@ -109,7 +106,7 @@ impl TlsParametersBuilder {
/// ///
/// This method should only be used as a last resort, as it introduces /// This method should only be used as a last resort, as it introduces
/// significant vulnerabilities to man-in-the-middle attacks. /// significant vulnerabilities to man-in-the-middle attacks.
pub fn dangerous_accept_invalid_certs(&mut self, accept_invalid_certs: bool) -> &mut Self { pub fn dangerous_accept_invalid_certs(mut self, accept_invalid_certs: bool) -> Self {
self.accept_invalid_certs = accept_invalid_certs; self.accept_invalid_certs = accept_invalid_certs;
self self
} }

View File

@@ -71,15 +71,15 @@ impl ClientId {
pub enum Extension { pub enum Extension {
/// 8BITMIME keyword /// 8BITMIME keyword
/// ///
/// RFC 6152: https://tools.ietf.org/html/rfc6152 /// Defined in [RFC 6152](https://tools.ietf.org/html/rfc6152)
EightBitMime, EightBitMime,
/// SMTPUTF8 keyword /// SMTPUTF8 keyword
/// ///
/// RFC 6531: https://tools.ietf.org/html/rfc6531 /// Defined in [RFC 6531](https://tools.ietf.org/html/rfc6531)
SmtpUtfEight, SmtpUtfEight,
/// STARTTLS keyword /// STARTTLS keyword
/// ///
/// RFC 2487: https://tools.ietf.org/html/rfc2487 /// Defined in [RFC 2487](https://tools.ietf.org/html/rfc2487)
StartTls, StartTls,
/// AUTH mechanism /// AUTH mechanism
Authentication(Mechanism), Authentication(Mechanism),

View File

@@ -43,8 +43,7 @@
//! .body(String::from("Be happy!"))?; //! .body(String::from("Be happy!"))?;
//! //!
//! // Create TLS transport on port 465 //! // Create TLS transport on port 465
//! let sender = SmtpTransport::relay("smtp.example.com") //! let sender = SmtpTransport::relay("smtp.example.com")?
//! .expect("relay valid")
//! .build(); //! .build();
//! // Send the email via remote relay //! // Send the email via remote relay
//! let result = sender.send(&email); //! let result = sender.send(&email);
@@ -52,114 +51,71 @@
//! # Ok(()) //! # Ok(())
//! # } //! # }
//! ``` //! ```
//! #### Complete example
//! //!
//! ```todo //! #### Authentication
//! # #[cfg(feature = "smtp-transport")]
//! # {
//! use lettre::transport::smtp::authentication::{Credentials, Mechanism};
//! use lettre::{Email, Envelope, Transport, SmtpClient};
//! use lettre::transport::smtp::extension::ClientId;
//! //!
//! let email_1 = Email::new( //! Example with authentication and connection pool:
//! Envelope::new(
//! Some(EmailAddress::new("user@localhost".to_string())?),
//! vec![EmailAddress::new("root@localhost".to_string())?],
//! )?,
//! "id1".to_string(),
//! "Hello world".to_string().into_bytes(),
//! );
//! //!
//! let email_2 = Email::new( //! ```rust,no_run
//! Envelope::new( //! # #[cfg(all(feature = "builder", any(feature = "native-tls", feature = "rustls-tls")))]
//! Some(EmailAddress::new("user@localhost".to_string())?), //! # fn test() -> Result<(), Box<dyn std::error::Error>> {
//! vec![EmailAddress::new("root@localhost".to_string())?], //! use lettre::{Message, Transport, SmtpTransport, transport::smtp::{PoolConfig, authentication::{Credentials, Mechanism}}};
//! )?,
//! "id2".to_string(),
//! "Hello world a second time".to_string().into_bytes(),
//! );
//! //!
//! // Connect to a remote server on a custom port //! let email = Message::builder()
//! let mut mailer = SmtpClient::new_simple("server.tld")? //! .from("NoBody <nobody@domain.tld>".parse()?)
//! // Set the name sent during EHLO/HELO, default is `localhost` //! .reply_to("Yuin <yuin@domain.tld>".parse()?)
//! .hello_name(ClientId::Domain("my.hostname.tld".to_string())) //! .to("Hei <hei@domain.tld>".parse()?)
//! // Add credentials for authentication //! .subject("Happy new year")
//! .credentials(Credentials::new("username".to_string(), "password".to_string())) //! .body(String::from("Be happy!"))?;
//! // Enable SMTPUTF8 if the server supports it
//! .smtp_utf8(true)
//! // Configure expected authentication mechanism
//! .authentication_mechanism(Mechanism::Plain)
//! // Enable connection reuse
//! .connection_reuse(ConnectionReuseParameters::ReuseUnlimited).transport();
//! //!
//! let result_1 = mailer.send(&email_1); //! // Create TLS transport on port 587 with STARTTLS
//! assert!(result_1.is_ok()); //! let sender = SmtpTransport::starttls_relay("smtp.example.com")?
//! // Add credentials for authentication
//! .credentials(Credentials::new("username".to_string(), "password".to_string()))
//! // Configure expected authentication mechanism
//! .authentication(vec![Mechanism::Plain])
//! // Connection pool settings
//! .pool_config( PoolConfig::new().max_size(20))
//! .build();
//! //!
//! // The second email will use the same connection //! // Send the email via remote relay
//! let result_2 = mailer.send(&email_2); //! let result = sender.send(&email);
//! assert!(result_2.is_ok()); //! assert!(result.is_ok());
//! //! # Ok(())
//! // Explicitly close the SMTP transaction as we enabled connection reuse
//! mailer.close();
//! # } //! # }
//! ``` //! ```
//! //!
//! You can specify custom TLS settings: //! You can specify custom TLS settings:
//! //!
//! ```todo //! ```rust,no_run
//! # #[cfg(feature = "native-tls")] //! # #[cfg(all(feature = "builder", any(feature = "native-tls", feature = "rustls-tls")))]
//! # { //! # fn test() -> Result<(), Box<dyn std::error::Error>> {
//! use lettre::{ //! use lettre::{Message, Transport, SmtpTransport, transport::smtp::client::{TlsParameters, Tls}};
//! ClientSecurity, ClientTlsParameters, EmailAddress, Envelope,
//! Email, SmtpClient, Transport,
//! };
//! use lettre::transport::smtp::authentication::{Credentials, Mechanism};
//! use lettre::transport::smtp::ConnectionReuseParameters;
//! use native_tls::{Protocol, TlsConnector};
//! //!
//! let email = Email::new( //! let email = Message::builder()
//! Envelope::new( //! .from("NoBody <nobody@domain.tld>".parse()?)
//! Some(EmailAddress::new("user@localhost".to_string())?), //! .reply_to("Yuin <yuin@domain.tld>".parse()?)
//! vec![EmailAddress::new("root@localhost".to_string())?], //! .to("Hei <hei@domain.tld>".parse()?)
//! )?, //! .subject("Happy new year")
//! "message_id".to_string(), //! .body(String::from("Be happy!"))?;
//! "Hello world".to_string().into_bytes(),
//! );
//! //!
//! let mut tls_builder = TlsConnector::builder(); //! // Custom TLS configuration
//! tls_builder.min_protocol_version(Some(Protocol::Tlsv10)); //! let tls = TlsParameters::builder("smtp.example.com".to_string())
//! let tls_parameters = //! .dangerous_accept_invalid_certs(true).build()?;
//! ClientTlsParameters::new(
//! "smtp.example.com".to_string(),
//! tls_builder.build()?
//! );
//! //!
//! let mut mailer = SmtpClient::new( //! // Create TLS transport on port 465
//! ("smtp.example.com", 465), ClientSecurity::Wrapper(tls_parameters) //! let sender = SmtpTransport::relay("smtp.example.com")?
//! )? //! // Custom TLS configuration
//! .authentication_mechanism(Mechanism::Login) //! .tls(Tls::Required(tls))
//! .credentials(Credentials::new( //! .build();
//! "example_username".to_string(), "example_password".to_string()
//! ))
//! .connection_reuse(ConnectionReuseParameters::ReuseUnlimited)
//! .transport();
//! //!
//! let result = mailer.send(&email); //! // Send the email via remote relay
//! //! let result = sender.send(&email);
//! assert!(result.is_ok()); //! assert!(result.is_ok());
//! //! # Ok(())
//! mailer.close();
//! # } //! # }
//! ``` //! ```
//!
#[cfg(feature = "async-std1")]
pub use self::async_transport::AsyncStd1Connector;
#[cfg(feature = "tokio02")]
pub use self::async_transport::Tokio02Connector;
#[cfg(feature = "tokio1")]
pub use self::async_transport::Tokio1Connector;
#[cfg(any(feature = "tokio02", feature = "tokio1", feature = "async-std1"))] #[cfg(any(feature = "tokio02", feature = "tokio1", feature = "async-std1"))]
pub use self::async_transport::{ pub use self::async_transport::{
AsyncSmtpConnector, AsyncSmtpTransport, AsyncSmtpTransportBuilder, AsyncSmtpConnector, AsyncSmtpTransport, AsyncSmtpTransportBuilder,
@@ -183,6 +139,19 @@ use crate::transport::smtp::{
use client::Tls; use client::Tls;
use std::time::Duration; use std::time::Duration;
#[doc(hidden)]
#[allow(deprecated)]
#[cfg(feature = "async-std1")]
pub use self::async_transport::AsyncStd1Connector;
#[doc(hidden)]
#[allow(deprecated)]
#[cfg(feature = "tokio02")]
pub use self::async_transport::Tokio02Connector;
#[doc(hidden)]
#[allow(deprecated)]
#[cfg(feature = "tokio1")]
pub use self::async_transport::Tokio1Connector;
#[cfg(any(feature = "tokio02", feature = "tokio1", feature = "async-std1"))] #[cfg(any(feature = "tokio02", feature = "tokio1", feature = "async-std1"))]
mod async_transport; mod async_transport;
pub mod authentication; pub mod authentication;
@@ -206,7 +175,7 @@ pub const SMTP_PORT: u16 = 25;
pub const SUBMISSION_PORT: u16 = 587; pub const SUBMISSION_PORT: u16 = 587;
/// Default submission over TLS port /// Default submission over TLS port
/// ///
/// https://tools.ietf.org/html/rfc8314 /// Defined in [RFC8314](https://tools.ietf.org/html/rfc8314)
pub const SUBMISSIONS_PORT: u16 = 465; pub const SUBMISSIONS_PORT: u16 = 465;
/// Default timeout /// Default timeout

View File

@@ -16,6 +16,11 @@ pub struct PoolConfig {
} }
impl PoolConfig { impl PoolConfig {
/// Create a new pool configuration with default values
pub fn new() -> Self {
Self::default()
}
/// Minimum number of idle connections /// Minimum number of idle connections
/// ///
/// Defaults to `0` /// Defaults to `0`
@@ -28,7 +33,7 @@ impl PoolConfig {
/// ///
/// Defaults to `10` /// Defaults to `10`
pub fn max_size(mut self, max_size: u32) -> Self { pub fn max_size(mut self, max_size: u32) -> Self {
self.min_idle = max_size; self.max_size = max_size;
self self
} }

View File

@@ -8,11 +8,11 @@ use super::PoolConfig;
use super::{ClientId, Credentials, Error, Mechanism, Response, SmtpConnection, SmtpInfo}; use super::{ClientId, Credentials, Error, Mechanism, Response, SmtpConnection, SmtpInfo};
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))] #[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
use super::{Tls, TlsParameters, SUBMISSIONS_PORT, SUBMISSION_PORT}; use super::{Tls, TlsParameters, SUBMISSIONS_PORT, SUBMISSION_PORT};
use crate::address::Envelope; use crate::{address::Envelope, Transport};
use crate::Transport;
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
#[derive(Clone)] #[derive(Clone)]
/// Transport using the SMTP protocol
pub struct SmtpTransport { pub struct SmtpTransport {
#[cfg(feature = "r2d2")] #[cfg(feature = "r2d2")]
inner: Pool<SmtpClient>, inner: Pool<SmtpClient>,

View File

@@ -9,8 +9,7 @@
//! ```rust //! ```rust
//! # #[cfg(feature = "builder")] //! # #[cfg(feature = "builder")]
//! # { //! # {
//! use lettre::{Message, Transport}; //! use lettre::{transport::stub::StubTransport, Message, Transport};
//! use lettre::transport::stub::StubTransport;
//! //!
//! # use std::error::Error; //! # use std::error::Error;
//! # fn main() -> Result<(), Box<dyn Error>> { //! # fn main() -> Result<(), Box<dyn Error>> {
@@ -29,13 +28,10 @@
//! # } //! # }
//! ``` //! ```
use crate::address::Envelope; #[cfg(any(feature = "tokio02", feature = "tokio1", feature = "async-std1"))]
#[cfg(feature = "async-std1")] use crate::AsyncTransport;
use crate::AsyncStd1Transport; use crate::{address::Envelope, Transport};
#[cfg(feature = "tokio02")] #[cfg(any(feature = "tokio02", feature = "tokio1", feature = "async-std1"))]
use crate::Tokio02Transport;
use crate::Transport;
#[cfg(any(feature = "async-std1", feature = "tokio02"))]
use async_trait::async_trait; use async_trait::async_trait;
use std::{error::Error as StdError, fmt}; use std::{error::Error as StdError, fmt};
@@ -84,20 +80,9 @@ impl Transport for StubTransport {
} }
} }
#[cfg(feature = "async-std1")] #[cfg(any(feature = "tokio02", feature = "tokio1", feature = "async-std1"))]
#[async_trait] #[async_trait]
impl AsyncStd1Transport for StubTransport { impl AsyncTransport for StubTransport {
type Ok = ();
type Error = Error;
async fn send_raw(&self, _envelope: &Envelope, _email: &[u8]) -> Result<Self::Ok, Self::Error> {
self.response
}
}
#[cfg(feature = "tokio02")]
#[async_trait]
impl Tokio02Transport for StubTransport {
type Ok = (); type Ok = ();
type Error = Error; type Error = Error;

View File

@@ -81,21 +81,27 @@ mod test {
"Be happy!" "Be happy!"
) )
); );
remove_file(eml_file).unwrap();
assert_eq!( assert_eq!(
json, json,
"{\"forward_path\":[\"hei@domain.tld\"],\"reverse_path\":\"nobody@domain.tld\"}" "{\"forward_path\":[\"hei@domain.tld\"],\"reverse_path\":\"nobody@domain.tld\"}"
); );
let (e, m) = sender.read(&id).unwrap();
assert_eq!(&e, email.envelope());
assert_eq!(m, email.formatted());
remove_file(eml_file).unwrap();
remove_file(json_file).unwrap(); remove_file(json_file).unwrap();
} }
#[cfg(feature = "async-std1")] #[cfg(feature = "async-std1")]
#[async_std::test] #[async_std::test]
async fn file_transport_asyncstd1() { async fn file_transport_asyncstd1() {
use lettre::AsyncStd1Transport; use lettre::{AsyncFileTransport, AsyncStd1Executor, AsyncTransport};
let sender = FileTransport::new(temp_dir()); let sender = AsyncFileTransport::<AsyncStd1Executor>::new(temp_dir());
let email = Message::builder() let email = Message::builder()
.from("NoBody <nobody@domain.tld>".parse().unwrap()) .from("NoBody <nobody@domain.tld>".parse().unwrap())
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap()) .reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
@@ -130,9 +136,9 @@ mod test {
#[cfg(feature = "tokio02")] #[cfg(feature = "tokio02")]
#[tokio::test] #[tokio::test]
async fn file_transport_tokio02() { async fn file_transport_tokio02() {
use lettre::Tokio02Transport; use lettre::{AsyncFileTransport, AsyncTransport, Tokio02Executor};
let sender = FileTransport::new(temp_dir()); let sender = AsyncFileTransport::<Tokio02Executor>::new(temp_dir());
let email = Message::builder() let email = Message::builder()
.from("NoBody <nobody@domain.tld>".parse().unwrap()) .from("NoBody <nobody@domain.tld>".parse().unwrap())
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap()) .reply_to("Yuin <yuin@domain.tld>".parse().unwrap())

View File

@@ -26,9 +26,9 @@ mod test {
#[cfg(feature = "async-std1")] #[cfg(feature = "async-std1")]
#[async_std::test] #[async_std::test]
async fn sendmail_transport_asyncstd1() { async fn sendmail_transport_asyncstd1() {
use lettre::AsyncStd1Transport; use lettre::{AsyncSendmailTransport, AsyncStd1Executor, AsyncTransport};
let sender = SendmailTransport::new(); let sender = AsyncSendmailTransport::<AsyncStd1Executor>::new();
let email = Message::builder() let email = Message::builder()
.from("NoBody <nobody@domain.tld>".parse().unwrap()) .from("NoBody <nobody@domain.tld>".parse().unwrap())
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap()) .reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
@@ -46,9 +46,9 @@ mod test {
#[cfg(feature = "tokio02")] #[cfg(feature = "tokio02")]
#[tokio::test] #[tokio::test]
async fn sendmail_transport_tokio02() { async fn sendmail_transport_tokio02() {
use lettre::Tokio02Transport; use lettre::{AsyncSendmailTransport, Tokio02Executor, Tokio02Transport};
let sender = SendmailTransport::new(); let sender = AsyncSendmailTransport::<Tokio02Executor>::new();
let email = Message::builder() let email = Message::builder()
.from("NoBody <nobody@domain.tld>".parse().unwrap()) .from("NoBody <nobody@domain.tld>".parse().unwrap())
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap()) .reply_to("Yuin <yuin@domain.tld>".parse().unwrap())

View File

@@ -1,7 +1,6 @@
#[cfg(all(test, feature = "smtp-transport", feature = "r2d2"))] #[cfg(all(test, feature = "smtp-transport", feature = "r2d2"))]
mod test { mod test {
use lettre::address::Envelope; use lettre::{address::Envelope, SmtpTransport, Transport};
use lettre::{SmtpTransport, Transport};
use std::{sync::mpsc, thread}; use std::{sync::mpsc, thread};