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]
name = "lettre"
# 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"
readme = "README.md"
homepage = "https://lettre.rs"
@@ -23,7 +23,7 @@ tracing = { version = "0.1.16", default-features = false, features = ["std"], op
# builder
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"] }
rand = { version = "0.8", optional = true }
quoted_printable = { version = "0.4", optional = true }
@@ -80,7 +80,7 @@ harness = false
name = "transport_smtp"
[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"]
# transports

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -103,6 +103,14 @@ impl Envelope {
pub fn from(&self) -> Option<&Address> {
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")]

View File

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

View File

@@ -173,6 +173,11 @@ impl Address {
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 {
@@ -211,6 +216,7 @@ impl AsRef<OsStr> for Address {
}
#[derive(Debug, PartialEq, Clone, Copy)]
/// Errors in email addresses parsing
pub enum AddressError {
MissingParts,
Unbalanced,

View File

@@ -1,3 +1,5 @@
//! Error type for email messages
use std::{
error::Error as StdError,
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
//! * **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_logo_url = "https://avatars0.githubusercontent.com/u/15113230?v=4")]
#![forbid(unsafe_code)]
@@ -46,6 +46,8 @@
pub mod address;
pub mod error;
#[cfg(all(any(feature = "tokio02", feature = "tokio1", feature = "async-std1")))]
mod executor;
#[cfg(feature = "builder")]
#[cfg_attr(docsrs, doc(cfg(feature = "builder")))]
pub mod message;
@@ -55,13 +57,31 @@ pub mod transport;
#[macro_use]
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;
use crate::address::Envelope;
use crate::error::Error;
#[cfg(feature = "builder")]
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")]
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")]
pub use crate::transport::sendmail::SendmailTransport;
#[cfg(all(
@@ -69,103 +89,32 @@ pub use crate::transport::sendmail::SendmailTransport;
any(feature = "tokio02", feature = "tokio1")
))]
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"))]
pub use crate::transport::smtp::AsyncStd1Connector;
#[cfg(feature = "smtp-transport")]
pub use crate::transport::smtp::SmtpTransport;
#[doc(hidden)]
#[allow(deprecated)]
#[cfg(all(feature = "smtp-transport", feature = "tokio02"))]
pub use crate::transport::smtp::Tokio02Connector;
#[doc(hidden)]
#[allow(deprecated)]
#[cfg(all(feature = "smtp-transport", feature = "tokio1"))]
pub use crate::transport::smtp::Tokio1Connector;
#[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio1"))]
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
#[doc(hidden)]
#[cfg(feature = "async-std1")]
#[cfg_attr(docsrs, doc(cfg(feature = "async-std1")))]
#[async_trait]
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
pub use crate::transport::AsyncStd1Transport;
#[doc(hidden)]
#[cfg(feature = "tokio02")]
#[cfg_attr(docsrs, doc(cfg(feature = "tokio02")))]
#[async_trait]
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
pub use crate::transport::Tokio02Transport;
#[doc(hidden)]
#[cfg(feature = "tokio1")]
#[cfg_attr(docsrs, doc(cfg(feature = "tokio1")))]
#[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>;
}
pub use crate::transport::Tokio1Transport;
#[cfg(test)]
#[cfg(feature = "builder")]

View File

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

View File

@@ -7,7 +7,10 @@ use std::{
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
///

View File

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

View File

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

View File

@@ -6,8 +6,9 @@ use hyperx::{
use std::{fmt::Result as FmtResult, str::from_utf8};
macro_rules! text_header {
( $type_name: ident, $header_name: expr ) => {
($(#[$attr:meta])* Header($type_name: ident, $header_name: expr )) => {
#[derive(Debug, Clone, PartialEq)]
$(#[$attr])*
pub struct $type_name(pub String);
impl Header for $type_name {
@@ -33,13 +34,41 @@ macro_rules! text_header {
};
}
text_header!(Subject, "Subject");
text_header!(Comments, "Comments");
text_header!(Keywords, "Keywords");
text_header!(InReplyTo, "In-Reply-To");
text_header!(References, "References");
text_header!(MessageId, "Message-Id");
text_header!(UserAgent, "User-Agent");
text_header!(
/// `Subject` of the message, defined in [RFC5322](https://tools.ietf.org/html/rfc5322#section-3.6.5)
Header(Subject, "Subject")
);
text_header!(
/// `Comments` of the message, defined in [RFC5322](https://tools.ietf.org/html/rfc5322#section-3.6.5)
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> {
if let Ok(src) = from_utf8(raw) {

View File

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

View File

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

View File

@@ -114,7 +114,6 @@
//!
//! <p><b>Hello</b>, <i>world</i>! <img src="cid:123"></p>
//! --0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1--
//!
//! ```
//! </details>
//!
@@ -125,8 +124,8 @@
//!
//! ```rust
//! # use std::error::Error;
//! use lettre::message::{header, Body, Message, MultiPart, Part, SinglePart};
//! use std::fs;
//! use lettre::message::{Body, header, Message, MultiPart, Part, SinglePart};
//!
//! # fn main() -> Result<(), Box<dyn Error>> {
//! let image = fs::read("docs/lettre.png")?;
@@ -236,7 +235,6 @@
//!
//! fn main() { println!("Hello, World!") }
//! --0oVZ2r6AoLAhLlb0gPNSKy6BEqdS2IfwxrcbUuo1--
//!
//! ```
//! </details>
@@ -252,8 +250,7 @@ mod mailbox;
mod mimebody;
mod utf8_b;
use std::convert::TryFrom;
use std::time::SystemTime;
use std::{convert::TryFrom, time::SystemTime};
use uuid::Uuid;
@@ -265,7 +262,8 @@ use crate::{
const DEFAULT_MESSAGE_ID_DOMAIN: &str = "localhost";
pub trait EmailFormat {
/// Something that can be formatted as an email message
trait EmailFormat {
// Use a writer?
fn format(&self, out: &mut Vec<u8>);
}
@@ -334,7 +332,7 @@ impl MessageBuilder {
/// 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))`.
pub fn sender(self, mbox: Mailbox) -> Self {
@@ -343,7 +341,7 @@ impl MessageBuilder {
/// 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))`.
pub fn from(self, mbox: Mailbox) -> Self {
@@ -352,7 +350,7 @@ impl MessageBuilder {
/// 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))`.
pub fn reply_to(self, mbox: Mailbox) -> Self {

View File

@@ -9,8 +9,8 @@
//!
//! # #[cfg(all(feature = "file-transport", feature = "builder"))]
//! # fn main() -> Result<(), Box<dyn Error>> {
//! use lettre::{FileTransport, Message, Transport};
//! use std::env::temp_dir;
//! use lettre::{Transport, Message, FileTransport};
//!
//! // Write to the local temp directory
//! let sender = FileTransport::new(temp_dir());
@@ -41,8 +41,8 @@
//!
//! # #[cfg(all(feature = "file-transport-envelope", feature = "builder"))]
//! # fn main() -> Result<(), Box<dyn Error>> {
//! use lettre::{FileTransport, Message, Transport};
//! use std::env::temp_dir;
//! use lettre::{Transport, Message, FileTransport};
//!
//! // Write to the local temp directory
//! let sender = FileTransport::with_envelope(temp_dir());
@@ -70,10 +70,10 @@
//! # #[cfg(all(feature = "tokio1", feature = "file-transport", feature = "builder"))]
//! # async fn run() -> Result<(), Box<dyn Error>> {
//! use std::env::temp_dir;
//! use lettre::{Tokio1Transport, Message, FileTransport};
//! use lettre::{AsyncTransport, Tokio1Executor, Message, AsyncFileTransport};
//!
//! // Write to the local temp directory
//! let sender = FileTransport::new(temp_dir());
//! let sender = AsyncFileTransport::<Tokio1Executor>::new(temp_dir());
//! let email = Message::builder()
//! .from("NoBody <nobody@domain.tld>".parse()?)
//! .reply_to("Yuin <yuin@domain.tld>".parse()?)
@@ -95,10 +95,10 @@
//! # #[cfg(all(feature = "async-std1", feature = "file-transport", feature = "builder"))]
//! # async fn run() -> Result<(), Box<dyn Error>> {
//! use std::env::temp_dir;
//! use lettre::{AsyncStd1Transport, Message, FileTransport};
//! use lettre::{AsyncTransport, AsyncStd1Executor, Message, AsyncFileTransport};
//!
//! // Write to the local temp directory
//! let sender = FileTransport::new(temp_dir());
//! let sender = AsyncFileTransport::<AsyncStd1Executor>::new(temp_dir());
//! let email = Message::builder()
//! .from("NoBody <nobody@domain.tld>".parse()?)
//! .reply_to("Yuin <yuin@domain.tld>".parse()?)
@@ -133,16 +133,13 @@
//! ```
pub use self::error::Error;
use crate::address::Envelope;
#[cfg(feature = "async-std1")]
use crate::AsyncStd1Transport;
#[cfg(feature = "tokio02")]
use crate::Tokio02Transport;
#[cfg(feature = "tokio1")]
use crate::Tokio1Transport;
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"))]
use async_trait::async_trait;
#[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio1"))]
use std::marker::PhantomData;
use std::{
path::{Path, PathBuf},
str,
@@ -162,6 +159,14 @@ pub struct FileTransport {
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 {
/// 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 {
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 {
type Ok = Id;
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]
impl AsyncStd1Transport for FileTransport {
impl<E> AsyncTransport for AsyncFileTransport<E>
where
E: Executor,
{
type Ok = Id;
type Error = 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 file = self.path(&email_id, "eml");
fs::write(file, email).await?;
let file = self.inner.path(&email_id, "eml");
E::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 = "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?;
if self.inner.save_envelope {
let file = self.inner.path(&email_id, "json");
let buf = serde_json::to_vec(&envelope)?;
E::fs_write(&file, &buf).await?;
}
}
// 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
//! 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_attr(docsrs, doc(cfg(feature = "file-transport")))]
pub mod file;
@@ -26,3 +46,47 @@ pub mod sendmail;
#[cfg_attr(docsrs, doc(cfg(feature = "smtp-transport")))]
pub mod smtp;
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"))]
//! # async fn run() -> Result<(), Box<dyn Error>> {
//! use lettre::{Message, Tokio02Transport, SendmailTransport};
//! use lettre::{Message, AsyncTransport, Tokio02Executor, AsyncSendmailTransport, SendmailTransport};
//!
//! let email = Message::builder()
//! .from("NoBody <nobody@domain.tld>".parse()?)
@@ -42,7 +42,7 @@
//! .subject("Happy new year")
//! .body(String::from("Be happy!"))?;
//!
//! let sender = SendmailTransport::new();
//! let sender = AsyncSendmailTransport::<Tokio02Executor>::new();
//! let result = sender.send(email).await;
//! assert!(result.is_ok());
//! # Ok(())
@@ -56,7 +56,7 @@
//!
//! # #[cfg(all(feature = "tokio1", feature = "sendmail-transport", feature = "builder"))]
//! # async fn run() -> Result<(), Box<dyn Error>> {
//! use lettre::{Message, Tokio1Transport, SendmailTransport};
//! use lettre::{Message, AsyncTransport, Tokio1Executor, AsyncSendmailTransport, SendmailTransport};
//!
//! let email = Message::builder()
//! .from("NoBody <nobody@domain.tld>".parse()?)
@@ -65,7 +65,7 @@
//! .subject("Happy new year")
//! .body(String::from("Be happy!"))?;
//!
//! let sender = SendmailTransport::new();
//! let sender = AsyncSendmailTransport::<Tokio1Executor>::new();
//! let result = sender.send(email).await;
//! assert!(result.is_ok());
//! # Ok(())
@@ -79,7 +79,7 @@
//!
//! # #[cfg(all(feature = "async-std1", feature = "sendmail-transport", feature = "builder"))]
//! # async fn run() -> Result<(), Box<dyn Error>> {
//! use lettre::{Message, AsyncStd1Transport, SendmailTransport};
//! use lettre::{Message, AsyncTransport, AsyncStd1Executor, AsyncSendmailTransport};
//!
//! let email = Message::builder()
//! .from("NoBody <nobody@domain.tld>".parse()?)
@@ -88,7 +88,7 @@
//! .subject("Happy new year")
//! .body(String::from("Be happy!"))?;
//!
//! let sender = SendmailTransport::new();
//! let sender = AsyncSendmailTransport::<AsyncStd1Executor>::new();
//! let result = sender.send(email).await;
//! assert!(result.is_ok());
//! # Ok(())
@@ -96,16 +96,19 @@
//! ```
pub use self::error::Error;
use crate::address::Envelope;
#[cfg(feature = "async-std1")]
use crate::AsyncStd1Transport;
use crate::AsyncStd1Executor;
#[cfg(feature = "tokio02")]
use crate::Tokio02Transport;
use crate::Tokio02Executor;
#[cfg(feature = "tokio1")]
use crate::Tokio1Transport;
use crate::Transport;
use crate::Tokio1Executor;
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"))]
use async_trait::async_trait;
#[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio1"))]
use std::marker::PhantomData;
use std::{
ffi::OsString,
io::prelude::*,
@@ -123,6 +126,14 @@ pub struct SendmailTransport {
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 {
/// Creates a new transport with the default `/usr/sbin/sendmail` command
pub fn new() -> SendmailTransport {
@@ -151,12 +162,34 @@ impl SendmailTransport {
.stderr(Stdio::piped());
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")]
fn tokio02_command(&self, envelope: &Envelope) -> 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.arg("-i");
if let Some(from) = envelope.from() {
@@ -174,7 +207,7 @@ impl SendmailTransport {
fn tokio1_command(&self, envelope: &Envelope) -> 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.arg("-i");
if let Some(from) = envelope.from() {
@@ -192,7 +225,7 @@ impl SendmailTransport {
fn async_std_command(&self, envelope: &Envelope) -> 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
// c.kill_on_drop(true);
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 {
type Ok = ();
type Error = Error;
@@ -235,7 +278,7 @@ impl Transport for SendmailTransport {
#[cfg(feature = "async-std1")]
#[async_trait]
impl AsyncStd1Transport for SendmailTransport {
impl AsyncTransport for AsyncSendmailTransport<AsyncStd1Executor> {
type Ok = ();
type Error = Error;
@@ -260,7 +303,7 @@ impl AsyncStd1Transport for SendmailTransport {
#[cfg(feature = "tokio02")]
#[async_trait]
impl Tokio02Transport for SendmailTransport {
impl AsyncTransport for AsyncSendmailTransport<Tokio02Executor> {
type Ok = ();
type Error = Error;
@@ -285,7 +328,7 @@ impl Tokio02Transport for SendmailTransport {
#[cfg(feature = "tokio1")]
#[async_trait]
impl Tokio1Transport for SendmailTransport {
impl AsyncTransport for AsyncSendmailTransport<Tokio1Executor> {
type Ok = ();
type Error = Error;

View File

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

View File

@@ -39,15 +39,16 @@ where
#[derive(PartialEq, Eq, Copy, Clone, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Mechanism {
/// PLAIN authentication mechanism
/// RFC 4616: https://tools.ietf.org/html/rfc4616
/// PLAIN authentication mechanism, defined in
/// [RFC 4616](https://tools.ietf.org/html/rfc4616)
Plain,
/// LOGIN authentication mechanism
/// 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,
/// Non-standard XOAUTH2 mechanism
/// https://developers.google.com/gmail/imap/xoauth2-protocol
/// Non-standard XOAUTH2 mechanism, defined in
/// [xoauth2-protocol](https://developers.google.com/gmail/imap/xoauth2-protocol)
Xoauth2,
}

View File

@@ -112,9 +112,32 @@ impl AsyncSmtpConnection {
// Mail
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));
}
try_smtp!(
self.command(Mail::new(envelope.from().cloned(), mail_options))
.await,

View File

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

View File

@@ -6,13 +6,15 @@ use std::{
};
use super::{ClientCodec, NetworkStream, TlsParameters};
use crate::address::Envelope;
use crate::transport::smtp::{
authentication::{Credentials, Mechanism},
commands::*,
error::Error,
extension::{ClientId, Extension, MailBodyParameter, MailParameter, ServerInfo},
response::{parse_response, Response},
use crate::{
address::Envelope,
transport::smtp::{
authentication::{Credentials, Mechanism},
commands::*,
error::Error,
extension::{ClientId, Extension, MailBodyParameter, MailParameter, ServerInfo},
response::{parse_response, Response},
},
};
#[cfg(feature = "tracing")]
@@ -80,9 +82,32 @@ impl SmtpConnection {
// Mail
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));
}
try_smtp!(
self.command(Mail::new(envelope.from().cloned(), mail_options)),
self

View File

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

View File

@@ -65,7 +65,7 @@ impl TlsParametersBuilder {
/// Add a custom root certificate
///
/// 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
}
@@ -85,10 +85,7 @@ impl TlsParametersBuilder {
/// Hostname verification can only be disabled with the `native-tls` TLS backend.
#[cfg(feature = "native-tls")]
#[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
pub fn dangerous_accept_invalid_hostnames(
&mut self,
accept_invalid_hostnames: bool,
) -> &mut Self {
pub fn dangerous_accept_invalid_hostnames(mut self, accept_invalid_hostnames: bool) -> Self {
self.accept_invalid_hostnames = accept_invalid_hostnames;
self
}
@@ -109,7 +106,7 @@ impl TlsParametersBuilder {
///
/// This method should only be used as a last resort, as it introduces
/// 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
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,8 +9,7 @@
//! ```rust
//! # #[cfg(feature = "builder")]
//! # {
//! use lettre::{Message, Transport};
//! use lettre::transport::stub::StubTransport;
//! use lettre::{transport::stub::StubTransport, Message, Transport};
//!
//! # use std::error::Error;
//! # fn main() -> Result<(), Box<dyn Error>> {
@@ -29,13 +28,10 @@
//! # }
//! ```
use crate::address::Envelope;
#[cfg(feature = "async-std1")]
use crate::AsyncStd1Transport;
#[cfg(feature = "tokio02")]
use crate::Tokio02Transport;
use crate::Transport;
#[cfg(any(feature = "async-std1", feature = "tokio02"))]
#[cfg(any(feature = "tokio02", feature = "tokio1", feature = "async-std1"))]
use crate::AsyncTransport;
use crate::{address::Envelope, Transport};
#[cfg(any(feature = "tokio02", feature = "tokio1", feature = "async-std1"))]
use async_trait::async_trait;
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]
impl AsyncStd1Transport 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 {
impl AsyncTransport for StubTransport {
type Ok = ();
type Error = Error;

View File

@@ -81,21 +81,27 @@ mod test {
"Be happy!"
)
);
remove_file(eml_file).unwrap();
assert_eq!(
json,
"{\"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();
}
#[cfg(feature = "async-std1")]
#[async_std::test]
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()
.from("NoBody <nobody@domain.tld>".parse().unwrap())
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
@@ -130,9 +136,9 @@ mod test {
#[cfg(feature = "tokio02")]
#[tokio::test]
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()
.from("NoBody <nobody@domain.tld>".parse().unwrap())
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())

View File

@@ -26,9 +26,9 @@ mod test {
#[cfg(feature = "async-std1")]
#[async_std::test]
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()
.from("NoBody <nobody@domain.tld>".parse().unwrap())
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
@@ -46,9 +46,9 @@ mod test {
#[cfg(feature = "tokio02")]
#[tokio::test]
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()
.from("NoBody <nobody@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"))]
mod test {
use lettre::address::Envelope;
use lettre::{SmtpTransport, Transport};
use lettre::{address::Envelope, SmtpTransport, Transport};
use std::{sync::mpsc, thread};