diff --git a/src/address/envelope.rs b/src/address/envelope.rs
new file mode 100644
index 0000000..3ea3341
--- /dev/null
+++ b/src/address/envelope.rs
@@ -0,0 +1,134 @@
+#[cfg(feature = "builder")]
+use std::convert::TryFrom;
+
+use super::Address;
+#[cfg(feature = "builder")]
+use crate::message::header::{self, Headers};
+#[cfg(feature = "builder")]
+use crate::message::{Mailbox, Mailboxes};
+use crate::Error;
+
+/// Simple email envelope representation
+///
+/// We only accept mailboxes, and do not support source routes (as per RFC).
+#[derive(PartialEq, Eq, Clone, Debug)]
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+pub struct Envelope {
+ /// The envelope recipients' addresses
+ ///
+ /// This can not be empty.
+ forward_path: Vec
,
+ /// The envelope sender address
+ reverse_path: Option,
+}
+
+impl Envelope {
+ /// Creates a new envelope, which may fail if `to` is empty.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::str::FromStr;
+ /// # use lettre::Address;
+ /// # use lettre::address::Envelope;
+ ///
+ /// let sender = Address::from_str("from@email.com").unwrap();
+ /// let recipients = vec![Address::from_str("to@email.com").unwrap()];
+ ///
+ /// let envelope = Envelope::new(Some(sender), recipients);
+ /// ```
+ ///
+ /// # Errors
+ ///
+ /// If `to` has no elements in it.
+ pub fn new(from: Option, to: Vec) -> Result {
+ if to.is_empty() {
+ return Err(Error::MissingTo);
+ }
+ Ok(Envelope {
+ forward_path: to,
+ reverse_path: from,
+ })
+ }
+
+ /// Gets the destination addresses of the envelope.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::str::FromStr;
+ /// # use lettre::Address;
+ /// # use lettre::address::Envelope;
+ ///
+ /// let sender = Address::from_str("from@email.com").unwrap();
+ /// let recipients = vec![Address::from_str("to@email.com").unwrap()];
+ ///
+ /// let envelope = Envelope::new(Some(sender), recipients.clone()).unwrap();
+ /// assert_eq!(envelope.to(), recipients.as_slice());
+ /// ```
+ pub fn to(&self) -> &[Address] {
+ self.forward_path.as_slice()
+ }
+
+ /// Gets the sender of the envelope.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use std::str::FromStr;
+ /// # use lettre::Address;
+ /// # use lettre::address::Envelope;
+ ///
+ /// let sender = Address::from_str("from@email.com").unwrap();
+ /// let recipients = vec![Address::from_str("to@email.com").unwrap()];
+ ///
+ /// let envelope = Envelope::new(Some(sender), recipients.clone()).unwrap();
+ /// assert!(envelope.from().is_some());
+ ///
+ /// let senderless = Envelope::new(None, recipients.clone()).unwrap();
+ /// assert!(senderless.from().is_none());
+ /// ```
+ pub fn from(&self) -> Option<&Address> {
+ self.reverse_path.as_ref()
+ }
+}
+
+#[cfg(feature = "builder")]
+impl TryFrom<&Headers> for Envelope {
+ type Error = Error;
+
+ fn try_from(headers: &Headers) -> Result {
+ let from = match headers.get::() {
+ // If there is a Sender, use it
+ Some(header::Sender(a)) => Some(a.email.clone()),
+ // ... else try From
+ None => match headers.get::() {
+ Some(header::From(a)) => {
+ let from: Vec = a.clone().into();
+ if from.len() > 1 {
+ return Err(Error::TooManyFrom);
+ }
+ Some(from[0].email.clone())
+ }
+ None => None,
+ },
+ };
+
+ fn add_addresses_from_mailboxes(
+ addresses: &mut Vec,
+ mailboxes: Option<&Mailboxes>,
+ ) {
+ if let Some(mailboxes) = mailboxes {
+ for mailbox in mailboxes.iter() {
+ addresses.push(mailbox.email.clone());
+ }
+ }
+ }
+ let mut to = vec![];
+ add_addresses_from_mailboxes(&mut to, headers.get::().map(|h| &h.0));
+ add_addresses_from_mailboxes(&mut to, headers.get::().map(|h| &h.0));
+ add_addresses_from_mailboxes(&mut to, headers.get::().map(|h| &h.0));
+
+ Self::new(from, to)
+ }
+}
diff --git a/src/address/mod.rs b/src/address/mod.rs
new file mode 100644
index 0000000..c53a75a
--- /dev/null
+++ b/src/address/mod.rs
@@ -0,0 +1,8 @@
+#[cfg(feature = "serde")]
+mod serde;
+
+mod envelope;
+mod types;
+
+pub use self::envelope::Envelope;
+pub use self::types::{Address, AddressError};
diff --git a/src/address/serde.rs b/src/address/serde.rs
new file mode 100644
index 0000000..342e332
--- /dev/null
+++ b/src/address/serde.rs
@@ -0,0 +1,112 @@
+use std::fmt::{Formatter, Result as FmtResult};
+
+use serde::{
+ de::{Deserializer, Error as DeError, MapAccess, Visitor},
+ ser::Serializer,
+ Deserialize, Serialize,
+};
+
+use super::Address;
+
+impl Serialize for Address {
+ fn serialize(&self, serializer: S) -> Result
+ where
+ S: Serializer,
+ {
+ serializer.serialize_str(self.as_ref())
+ }
+}
+
+impl<'de> Deserialize<'de> for Address {
+ fn deserialize(deserializer: D) -> Result
+ where
+ D: Deserializer<'de>,
+ {
+ enum Field {
+ User,
+ Domain,
+ };
+
+ const FIELDS: &[&str] = &["user", "domain"];
+
+ impl<'de> Deserialize<'de> for Field {
+ fn deserialize(deserializer: D) -> Result
+ where
+ D: Deserializer<'de>,
+ {
+ struct FieldVisitor;
+
+ impl<'de> Visitor<'de> for FieldVisitor {
+ type Value = Field;
+
+ fn expecting(&self, formatter: &mut Formatter<'_>) -> FmtResult {
+ formatter.write_str("'user' or 'domain'")
+ }
+
+ fn visit_str(self, value: &str) -> Result
+ where
+ E: DeError,
+ {
+ match value {
+ "user" => Ok(Field::User),
+ "domain" => Ok(Field::Domain),
+ _ => Err(DeError::unknown_field(value, FIELDS)),
+ }
+ }
+ }
+
+ deserializer.deserialize_identifier(FieldVisitor)
+ }
+ }
+
+ struct AddressVisitor;
+
+ impl<'de> Visitor<'de> for AddressVisitor {
+ type Value = Address;
+
+ fn expecting(&self, formatter: &mut Formatter<'_>) -> FmtResult {
+ formatter.write_str("email address string or object")
+ }
+
+ fn visit_str(self, s: &str) -> Result
+ where
+ E: DeError,
+ {
+ s.parse().map_err(DeError::custom)
+ }
+
+ fn visit_map(self, mut map: V) -> Result
+ where
+ V: MapAccess<'de>,
+ {
+ let mut user = None;
+ let mut domain = None;
+ while let Some(key) = map.next_key()? {
+ match key {
+ Field::User => {
+ if user.is_some() {
+ return Err(DeError::duplicate_field("user"));
+ }
+ let val = map.next_value()?;
+ Address::check_user(val).map_err(DeError::custom)?;
+ user = Some(val);
+ }
+ Field::Domain => {
+ if domain.is_some() {
+ return Err(DeError::duplicate_field("domain"));
+ }
+ let val = map.next_value()?;
+ Address::check_domain(val).map_err(DeError::custom)?;
+ domain = Some(val);
+ }
+ }
+ }
+ let user: &str = user.ok_or_else(|| DeError::missing_field("user"))?;
+ let domain: &str = domain.ok_or_else(|| DeError::missing_field("domain"))?;
+ Ok(Address::new(user, domain).unwrap())
+ }
+ }
+
+ deserializer.deserialize_any(AddressVisitor)
+ }
+}
diff --git a/src/address.rs b/src/address/types.rs
similarity index 59%
rename from src/address.rs
rename to src/address/types.rs
index f4242c4..81afb96 100644
--- a/src/address.rs
+++ b/src/address/types.rs
@@ -122,7 +122,7 @@ impl Address {
&self.serialized[self.at_start + 1..]
}
- fn check_user(user: &str) -> Result<(), AddressError> {
+ pub(super) fn check_user(user: &str) -> Result<(), AddressError> {
if USER_RE.is_match(user) {
Ok(())
} else {
@@ -130,7 +130,7 @@ impl Address {
}
}
- fn check_domain(domain: &str) -> Result<(), AddressError> {
+ pub(super) fn check_domain(domain: &str) -> Result<(), AddressError> {
Address::check_domain_ascii(domain).or_else(|_| {
domain_to_ascii(domain)
.map_err(|_| AddressError::InvalidDomain)
@@ -213,120 +213,6 @@ impl Display for AddressError {
}
}
-#[cfg(feature = "serde")]
-mod serde {
- use crate::address::Address;
- use serde::{
- de::{Deserializer, Error as DeError, MapAccess, Visitor},
- ser::Serializer,
- Deserialize, Serialize,
- };
- use std::fmt::{Formatter, Result as FmtResult};
-
- impl Serialize for Address {
- fn serialize(&self, serializer: S) -> Result
- where
- S: Serializer,
- {
- serializer.serialize_str(self.as_ref())
- }
- }
-
- impl<'de> Deserialize<'de> for Address {
- fn deserialize(deserializer: D) -> Result
- where
- D: Deserializer<'de>,
- {
- enum Field {
- User,
- Domain,
- };
-
- const FIELDS: &[&str] = &["user", "domain"];
-
- impl<'de> Deserialize<'de> for Field {
- fn deserialize(deserializer: D) -> Result
- where
- D: Deserializer<'de>,
- {
- struct FieldVisitor;
-
- impl<'de> Visitor<'de> for FieldVisitor {
- type Value = Field;
-
- fn expecting(&self, formatter: &mut Formatter<'_>) -> FmtResult {
- formatter.write_str("'user' or 'domain'")
- }
-
- fn visit_str(self, value: &str) -> Result
- where
- E: DeError,
- {
- match value {
- "user" => Ok(Field::User),
- "domain" => Ok(Field::Domain),
- _ => Err(DeError::unknown_field(value, FIELDS)),
- }
- }
- }
-
- deserializer.deserialize_identifier(FieldVisitor)
- }
- }
-
- struct AddressVisitor;
-
- impl<'de> Visitor<'de> for AddressVisitor {
- type Value = Address;
-
- fn expecting(&self, formatter: &mut Formatter<'_>) -> FmtResult {
- formatter.write_str("email address string or object")
- }
-
- fn visit_str(self, s: &str) -> Result
- where
- E: DeError,
- {
- s.parse().map_err(DeError::custom)
- }
-
- fn visit_map(self, mut map: V) -> Result
- where
- V: MapAccess<'de>,
- {
- let mut user = None;
- let mut domain = None;
- while let Some(key) = map.next_key()? {
- match key {
- Field::User => {
- if user.is_some() {
- return Err(DeError::duplicate_field("user"));
- }
- let val = map.next_value()?;
- Address::check_user(val).map_err(DeError::custom)?;
- user = Some(val);
- }
- Field::Domain => {
- if domain.is_some() {
- return Err(DeError::duplicate_field("domain"));
- }
- let val = map.next_value()?;
- Address::check_domain(val).map_err(DeError::custom)?;
- domain = Some(val);
- }
- }
- }
- let user: &str = user.ok_or_else(|| DeError::missing_field("user"))?;
- let domain: &str = domain.ok_or_else(|| DeError::missing_field("domain"))?;
- Ok(Address::new(user, domain).unwrap())
- }
- }
-
- deserializer.deserialize_any(AddressVisitor)
- }
- }
-}
-
#[cfg(test)]
mod tests {
use super::*;
diff --git a/src/lib.rs b/src/lib.rs
index 92fa0f6..f74de22 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -52,6 +52,7 @@ pub mod transport;
#[macro_use]
extern crate hyperx;
+use crate::address::Envelope;
use crate::error::Error;
#[cfg(feature = "builder")]
pub use crate::message::{
@@ -78,130 +79,6 @@ pub use crate::transport::smtp::Tokio03Connector;
pub use crate::{address::Address, transport::stub::StubTransport};
#[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio03"))]
use async_trait::async_trait;
-#[cfg(feature = "builder")]
-use std::convert::TryFrom;
-
-/// Simple email envelope representation
-///
-/// We only accept mailboxes, and do not support source routes (as per RFC).
-#[derive(PartialEq, Eq, Clone, Debug)]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
-pub struct Envelope {
- /// The envelope recipients' addresses
- ///
- /// This can not be empty.
- forward_path: Vec,
- /// The envelope sender address
- reverse_path: Option,
-}
-
-impl Envelope {
- /// Creates a new envelope, which may fail if `to` is empty.
- ///
- /// # Examples
- ///
- /// ```
- /// use std::str::FromStr;
- /// # use lettre::{Address, Envelope};
- ///
- /// let sender = Address::from_str("from@email.com").unwrap();
- /// let recipients = vec![Address::from_str("to@email.com").unwrap()];
- ///
- /// let envelope = Envelope::new(Some(sender), recipients);
- /// ```
- ///
- /// # Errors
- ///
- /// If `to` has no elements in it.
- pub fn new(from: Option, to: Vec) -> Result {
- if to.is_empty() {
- return Err(Error::MissingTo);
- }
- Ok(Envelope {
- forward_path: to,
- reverse_path: from,
- })
- }
-
- /// Gets the destination addresses of the envelope.
- ///
- /// # Examples
- ///
- /// ```
- /// use std::str::FromStr;
- /// # use lettre::{Address, Envelope};
- ///
- /// let sender = Address::from_str("from@email.com").unwrap();
- /// let recipients = vec![Address::from_str("to@email.com").unwrap()];
- ///
- /// let envelope = Envelope::new(Some(sender), recipients.clone()).unwrap();
- /// assert_eq!(envelope.to(), recipients.as_slice());
- /// ```
- pub fn to(&self) -> &[Address] {
- self.forward_path.as_slice()
- }
-
- /// Gets the sender of the envelope.
- ///
- /// # Examples
- ///
- /// ```
- /// use std::str::FromStr;
- /// # use lettre::{Address, Envelope};
- ///
- /// let sender = Address::from_str("from@email.com").unwrap();
- /// let recipients = vec![Address::from_str("to@email.com").unwrap()];
- ///
- /// let envelope = Envelope::new(Some(sender), recipients.clone()).unwrap();
- /// assert!(envelope.from().is_some());
- ///
- /// let senderless = Envelope::new(None, recipients.clone()).unwrap();
- /// assert!(senderless.from().is_none());
- /// ```
- pub fn from(&self) -> Option<&Address> {
- self.reverse_path.as_ref()
- }
-}
-
-#[cfg(feature = "builder")]
-impl TryFrom<&Headers> for Envelope {
- type Error = Error;
-
- fn try_from(headers: &Headers) -> Result {
- let from = match headers.get::() {
- // If there is a Sender, use it
- Some(header::Sender(a)) => Some(a.email.clone()),
- // ... else try From
- None => match headers.get::() {
- Some(header::From(a)) => {
- let from: Vec = a.clone().into();
- if from.len() > 1 {
- return Err(Error::TooManyFrom);
- }
- Some(from[0].email.clone())
- }
- None => None,
- },
- };
-
- fn add_addresses_from_mailboxes(
- addresses: &mut Vec,
- mailboxes: Option<&Mailboxes>,
- ) {
- if let Some(mailboxes) = mailboxes {
- for mailbox in mailboxes.iter() {
- addresses.push(mailbox.email.clone());
- }
- }
- }
- let mut to = vec![];
- add_addresses_from_mailboxes(&mut to, headers.get::().map(|h| &h.0));
- add_addresses_from_mailboxes(&mut to, headers.get::().map(|h| &h.0));
- add_addresses_from_mailboxes(&mut to, headers.get::().map(|h| &h.0));
-
- Self::new(from, to)
- }
-}
/// Blocking Transport method for emails
pub trait Transport {
@@ -295,6 +172,7 @@ mod test {
use super::*;
use crate::message::{header, Mailbox, Mailboxes};
use hyperx::header::Headers;
+ use std::convert::TryFrom;
#[test]
fn envelope_from_headers() {
diff --git a/src/message/mod.rs b/src/message/mod.rs
index 8101468..13ba5e4 100644
--- a/src/message/mod.rs
+++ b/src/message/mod.rs
@@ -186,8 +186,9 @@ mod mimebody;
mod utf8_b;
use crate::{
+ address::Envelope,
message::header::{EmailDate, Header, Headers, MailboxesHeader},
- Envelope, Error as EmailError,
+ Error as EmailError,
};
use std::{convert::TryFrom, time::SystemTime};
use uuid::Uuid;
diff --git a/src/transport/file/mod.rs b/src/transport/file/mod.rs
index 40cfcc1..8218ebf 100644
--- a/src/transport/file/mod.rs
+++ b/src/transport/file/mod.rs
@@ -6,7 +6,7 @@
//!
//! ```rust
//! use std::env::temp_dir;
-//! use lettre::{Transport, Envelope, Message, FileTransport};
+//! use lettre::{Transport, Message, FileTransport};
//!
//! // Write to the local temp directory
//! let sender = FileTransport::new(temp_dir());
@@ -28,7 +28,7 @@
//! # #[cfg(feature = "tokio02")]
//! # async fn run() {
//! use std::env::temp_dir;
-//! use lettre::{Tokio02Transport, Envelope, Message, FileTransport};
+//! use lettre::{Tokio02Transport, Message, FileTransport};
//!
//! // Write to the local temp directory
//! let sender = FileTransport::new(temp_dir());
@@ -51,7 +51,7 @@
//! # #[cfg(feature = "async-std1")]
//! # async fn run() {
//! use std::env::temp_dir;
-//! use lettre::{AsyncStd1Transport, Envelope, Message, FileTransport};
+//! use lettre::{AsyncStd1Transport, Message, FileTransport};
//!
//! // Write to the local temp directory
//! let sender = FileTransport::new(temp_dir());
@@ -86,13 +86,14 @@
//! ```
pub use self::error::Error;
+use crate::address::Envelope;
#[cfg(feature = "async-std1")]
use crate::AsyncStd1Transport;
#[cfg(feature = "tokio02")]
use crate::Tokio02Transport;
#[cfg(feature = "tokio03")]
use crate::Tokio03Transport;
-use crate::{Envelope, Transport};
+use crate::Transport;
#[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio03"))]
use async_trait::async_trait;
use std::{
diff --git a/src/transport/sendmail/mod.rs b/src/transport/sendmail/mod.rs
index 94838d5..855a8a8 100644
--- a/src/transport/sendmail/mod.rs
+++ b/src/transport/sendmail/mod.rs
@@ -3,7 +3,7 @@
//! ## Sync example
//!
//! ```rust
-//! use lettre::{Message, Envelope, Transport, SendmailTransport};
+//! use lettre::{Message, Transport, SendmailTransport};
//!
//! let email = Message::builder()
//! .from("NoBody ".parse().unwrap())
@@ -23,7 +23,7 @@
//! ```rust
//! # #[cfg(feature = "tokio02")]
//! # async fn run() {
-//! use lettre::{Message, Envelope, Tokio02Transport, SendmailTransport};
+//! use lettre::{Message, Tokio02Transport, SendmailTransport};
//!
//! let email = Message::builder()
//! .from("NoBody ".parse().unwrap())
@@ -44,7 +44,7 @@
//!```rust
//! # #[cfg(feature = "async-std1")]
//! # async fn run() {
-//! use lettre::{Message, Envelope, AsyncStd1Transport, SendmailTransport};
+//! use lettre::{Message, AsyncStd1Transport, SendmailTransport};
//!
//! let email = Message::builder()
//! .from("NoBody ".parse().unwrap())
@@ -61,13 +61,14 @@
//! ```
pub use self::error::Error;
+use crate::address::Envelope;
#[cfg(feature = "async-std1")]
use crate::AsyncStd1Transport;
#[cfg(feature = "tokio02")]
use crate::Tokio02Transport;
#[cfg(feature = "tokio03")]
use crate::Tokio03Transport;
-use crate::{Envelope, Transport};
+use crate::Transport;
#[cfg(any(feature = "async-std1", feature = "tokio02", feature = "tokio03"))]
use async_trait::async_trait;
use std::{
diff --git a/src/transport/smtp/client/connection.rs b/src/transport/smtp/client/connection.rs
index 12bd63a..10b2b8d 100644
--- a/src/transport/smtp/client/connection.rs
+++ b/src/transport/smtp/client/connection.rs
@@ -6,15 +6,13 @@ use std::{
};
use super::{ClientCodec, NetworkStream, TlsParameters};
-use crate::{
- transport::smtp::{
- authentication::{Credentials, Mechanism},
- commands::*,
- error::Error,
- extension::{ClientId, Extension, MailBodyParameter, MailParameter, ServerInfo},
- response::{parse_response, Response},
- },
- Envelope,
+use crate::address::Envelope;
+use crate::transport::smtp::{
+ authentication::{Credentials, Mechanism},
+ commands::*,
+ error::Error,
+ extension::{ClientId, Extension, MailBodyParameter, MailParameter, ServerInfo},
+ response::{parse_response, Response},
};
#[cfg(feature = "tracing")]
diff --git a/src/transport/smtp/transport.rs b/src/transport/smtp/transport.rs
index 1e1ee05..7c56ce4 100644
--- a/src/transport/smtp/transport.rs
+++ b/src/transport/smtp/transport.rs
@@ -8,7 +8,8 @@ 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::{Envelope, Transport};
+use crate::address::Envelope;
+use crate::Transport;
#[allow(missing_debug_implementations)]
#[derive(Clone)]
diff --git a/src/transport/stub/mod.rs b/src/transport/stub/mod.rs
index 24bbf4e..92359ef 100644
--- a/src/transport/stub/mod.rs
+++ b/src/transport/stub/mod.rs
@@ -7,7 +7,7 @@
//! testing purposes.
//!
//! ```rust
-//! use lettre::{Message, Envelope, Transport, StubTransport};
+//! use lettre::{Message, Transport, StubTransport};
//!
//! let email = Message::builder()
//! .from("NoBody ".parse().unwrap())
@@ -22,11 +22,12 @@
//! assert!(result.is_ok());
//! ```
+use crate::address::Envelope;
#[cfg(feature = "async-std1")]
use crate::AsyncStd1Transport;
#[cfg(feature = "tokio02")]
use crate::Tokio02Transport;
-use crate::{Envelope, Transport};
+use crate::Transport;
#[cfg(any(feature = "async-std1", feature = "tokio02"))]
use async_trait::async_trait;
use std::{error::Error as StdError, fmt};
diff --git a/tests/transport_smtp_pool.rs b/tests/transport_smtp_pool.rs
index b12b758..18154b5 100644
--- a/tests/transport_smtp_pool.rs
+++ b/tests/transport_smtp_pool.rs
@@ -1,6 +1,8 @@
#[cfg(all(test, feature = "smtp-transport", feature = "r2d2"))]
mod test {
- use lettre::{Envelope, SmtpTransport, Transport};
+ use lettre::address::Envelope;
+ use lettre::{SmtpTransport, Transport};
+
use std::{sync::mpsc, thread};
fn envelope() -> Envelope {