diff --git a/lettre_email/examples/smtp.rs b/lettre_email/examples/smtp.rs index 5448315..2247e92 100644 --- a/lettre_email/examples/smtp.rs +++ b/lettre_email/examples/smtp.rs @@ -2,7 +2,7 @@ extern crate lettre; extern crate lettre_email; use lettre::{EmailTransport, SmtpTransport}; -use lettre_email::email::EmailBuilder; +use lettre_email::EmailBuilder; fn main() { let email = EmailBuilder::new() diff --git a/lettre_email/src/email.rs b/lettre_email/src/email.rs deleted file mode 100644 index a872e7d..0000000 --- a/lettre_email/src/email.rs +++ /dev/null @@ -1,969 +0,0 @@ -//! Simple email representation - -use email_format::{Address, Header, Mailbox, MimeMessage, MimeMultipartType}; -use error::Error; -use lettre::{EmailAddress, SendableEmail}; -use mime; -use mime::Mime; -use std::fmt; -use std::fmt::{Display, Formatter}; -use time::{Tm, now}; -use uuid::Uuid; - -/// Converts an address or an address with an alias to a `Header` -pub trait IntoHeader { - /// Converts to a `Header` struct - fn into_header(self) -> Header; -} - -impl IntoHeader for Header { - fn into_header(self) -> Header { - self - } -} - -impl, T: Into> IntoHeader for (S, T) { - fn into_header(self) -> Header { - let (name, value) = self; - Header::new(name.into(), value.into()) - } -} - -/// Converts an adress or an address with an alias to a `Mailbox` -pub trait IntoMailbox { - /// Converts to a `Mailbox` struct - fn into_mailbox(self) -> Mailbox; -} - -impl IntoMailbox for Mailbox { - fn into_mailbox(self) -> Mailbox { - self - } -} - -impl<'a> IntoMailbox for &'a str { - fn into_mailbox(self) -> Mailbox { - Mailbox::new(self.into()) - } -} - -impl IntoMailbox for String { - fn into_mailbox(self) -> Mailbox { - Mailbox::new(self) - } -} - -impl, T: Into> IntoMailbox for (S, T) { - fn into_mailbox(self) -> Mailbox { - let (address, alias) = self; - Mailbox::new_with_name(alias.into(), address.into()) - } -} - -/// Can be transformed to a sendable email -pub trait IntoEmail { - /// Builds an email - fn into_email(self) -> Result; -} - -impl IntoEmail for SimpleEmail { - fn into_email(self) -> Result { - let mut builder = EmailBuilder::new(); - - if self.from.is_some() { - builder.add_from(self.from.unwrap()); - } - - for to_address in self.to { - builder.add_to(to_address.into_mailbox()); - } - - for cc_address in self.cc { - builder.add_cc(cc_address.into_mailbox()); - } - - if self.reply_to.is_some() { - builder.add_reply_to(self.reply_to.unwrap().into_mailbox()); - } - - if self.subject.is_some() { - builder.set_subject(self.subject.unwrap()); - } - - // No date for now - - match (self.text, self.html) { - (Some(text), Some(html)) => builder.set_alternative(html, text), - (Some(text), None) => builder.set_text(text), - (None, Some(html)) => builder.set_html(html), - (None, None) => (), - } - - for header in self.headers { - builder.add_header(header.into_header()); - } - - builder.build() - } -} - - -/// Simple representation of an email, useful for some transports -#[derive(PartialEq, Eq, Clone, Debug, Default)] -pub struct SimpleEmail { - from: Option, - to: Vec, - cc: Vec, - bcc: Vec, - reply_to: Option, - subject: Option, - date: Option, - html: Option, - text: Option, - // attachments: Vec, - headers: Vec
, -} - -impl SimpleEmail { - /// Adds a generic header - pub fn header(mut self, header: A) -> SimpleEmail { - self.add_header(header); - self - } - - /// Adds a generic header - pub fn add_header(&mut self, header: A) { - self.headers.push(header.into_header()); - } - - /// Adds a `From` header and stores the sender address - pub fn from(mut self, address: A) -> SimpleEmail { - self.add_from(address); - self - } - - /// Adds a `From` header and stores the sender address - pub fn add_from(&mut self, address: A) { - self.from = Some(address.into_mailbox()); - } - - /// Adds a `To` header and stores the recipient address - pub fn to(mut self, address: A) -> SimpleEmail { - self.add_to(address); - self - } - - /// Adds a `To` header and stores the recipient address - pub fn add_to(&mut self, address: A) { - self.to.push(address.into_mailbox()); - } - - /// Adds a `Cc` header and stores the recipient address - pub fn cc(mut self, address: A) -> SimpleEmail { - self.add_cc(address); - self - } - - /// Adds a `Cc` header and stores the recipient address - pub fn add_cc(&mut self, address: A) { - self.cc.push(address.into_mailbox()); - } - - /// Adds a `Bcc` header and stores the recipient address - pub fn bcc(mut self, address: A) -> SimpleEmail { - self.add_bcc(address); - self - } - - /// Adds a `Bcc` header and stores the recipient address - pub fn add_bcc(&mut self, address: A) { - self.bcc.push(address.into_mailbox()); - } - - /// Adds a `Reply-To` header - pub fn reply_to(mut self, address: A) -> SimpleEmail { - self.add_reply_to(address); - self - } - - /// Adds a `Reply-To` header - pub fn add_reply_to(&mut self, address: A) { - self.reply_to = Some(address.into_mailbox()); - } - - /// Adds a `Subject` header - pub fn subject>(mut self, subject: S) -> SimpleEmail { - self.set_subject(subject); - self - } - - /// Adds a `Subject` header - pub fn set_subject>(&mut self, subject: S) { - self.subject = Some(subject.into()); - } - - /// Adds a `Date` header with the given date - pub fn date(mut self, date: Tm) -> SimpleEmail { - self.set_date(date); - self - } - - /// Adds a `Date` header with the given date - pub fn set_date(&mut self, date: Tm) { - self.date = Some(date); - } - - /// Sets the email body to plain text content - pub fn text>(mut self, body: S) -> SimpleEmail { - self.set_text(body); - self - } - - /// Sets the email body to plain text content - pub fn set_text>(&mut self, body: S) { - self.text = Some(body.into()); - } - - /// Sets the email body to HTML content - pub fn html>(mut self, body: S) -> SimpleEmail { - self.set_html(body); - self - } - - /// Sets the email body to HTML content - pub fn set_html>(&mut self, body: S) { - self.html = Some(body.into()); - } -} - -/// Builds a `MimeMessage` structure -#[derive(PartialEq, Eq, Clone, Debug)] -pub struct PartBuilder { - /// Message - message: MimeMessage, -} - -impl Default for PartBuilder { - fn default() -> Self { - Self::new() - } -} - - -/// Builds an `Email` structure -#[derive(PartialEq, Eq, Clone, Debug, Default)] -pub struct EmailBuilder { - /// Message - message: PartBuilder, - /// The recipients' addresses for the mail header - to_header: Vec
, - /// The sender addresses for the mail header - from_header: Vec
, - /// The Cc addresses for the mail header - cc_header: Vec
, - /// The Bcc addresses for the mail header - bcc_header: Vec
, - /// The Reply-To addresses for the mail header - reply_to_header: Vec
, - /// The sender address for the mail header - sender_header: Option, - /// The envelope - envelope: Option, - /// Date issued - date_issued: bool, -} - -/// Simple email enveloppe representation -#[derive(PartialEq, Eq, Clone, Debug, Default)] -pub struct Envelope { - /// The envelope recipients' addresses - pub to: Vec, - /// The envelope sender address - pub from: String, -} - -impl Envelope { - /// Constructs an envelope with no receivers and an empty sender - pub fn new() -> Self { - Envelope { - to: vec![], - from: String::new(), - } - } - /// Adds a receiver - pub fn to>(mut self, address: S) -> Self { - self.add_to(address); - self - } - /// Adds a receiver - pub fn add_to>(&mut self, address: S) { - self.to.push(address.into()); - } - /// Sets the sender - pub fn from>(mut self, address: S) -> Self { - self.set_from(address); - self - } - /// Sets the sender - pub fn set_from>(&mut self, address: S) { - self.from = address.into(); - } -} - -/// Simple email representation -#[derive(PartialEq, Eq, Clone, Debug)] -pub struct Email { - /// Message - message: MimeMessage, - /// Envelope - envelope: Envelope, - /// Message-ID - message_id: Uuid, -} - -impl Display for Email { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}", self.message.as_string()) - } -} - -impl PartBuilder { - /// Creates a new empty part - pub fn new() -> PartBuilder { - PartBuilder { - message: MimeMessage::new_blank_message(), - } - } - - /// Adds a generic header - pub fn header(mut self, header: A) -> PartBuilder { - self.add_header(header); - self - } - - /// Adds a generic header - pub fn add_header(&mut self, header: A) { - self.message.headers.insert(header.into_header()); - } - - /// Sets the body - pub fn body>(mut self, body: S) -> PartBuilder { - self.set_body(body); - self - } - - /// Sets the body - pub fn set_body>(&mut self, body: S) { - self.message.body = body.into(); - } - - /// Defines a `MimeMultipartType` value - pub fn message_type(mut self, mime_type: MimeMultipartType) -> PartBuilder { - self.set_message_type(mime_type); - self - } - - /// Defines a `MimeMultipartType` value - pub fn set_message_type(&mut self, mime_type: MimeMultipartType) { - self.message.message_type = Some(mime_type); - } - - /// Adds a `ContentType` header with the given MIME type - pub fn content_type(mut self, content_type: Mime) -> PartBuilder { - self.set_content_type(content_type); - self - } - - /// Adds a `ContentType` header with the given MIME type - pub fn set_content_type(&mut self, content_type: Mime) { - self.add_header(("Content-Type", format!("{}", content_type).as_ref())); - } - - /// Adds a child part - pub fn child(mut self, child: MimeMessage) -> PartBuilder { - self.add_child(child); - self - } - - /// Adds a child part - pub fn add_child(&mut self, child: MimeMessage) { - self.message.children.push(child); - } - - /// Gets builded `MimeMessage` - pub fn build(mut self) -> MimeMessage { - self.message.update_headers(); - self.message - } -} - -impl EmailBuilder { - /// Creates a new empty email - pub fn new() -> EmailBuilder { - EmailBuilder { - message: PartBuilder::new(), - to_header: vec![], - from_header: vec![], - cc_header: vec![], - bcc_header: vec![], - reply_to_header: vec![], - sender_header: None, - envelope: None, - date_issued: false, - } - } - - /// Sets the email body - pub fn body>(mut self, body: S) -> EmailBuilder { - self.message.set_body(body); - self - } - - /// Sets the email body - pub fn set_body>(&mut self, body: S) { - self.message.set_body(body); - } - - /// Add a generic header - pub fn header(mut self, header: A) -> EmailBuilder { - self.message.add_header(header); - self - } - - /// Add a generic header - pub fn add_header(&mut self, header: A) { - self.message.add_header(header); - } - - /// Adds a `From` header and stores the sender address - pub fn from(mut self, address: A) -> EmailBuilder { - self.add_from(address); - self - } - - /// Adds a `From` header and stores the sender address - pub fn add_from(&mut self, address: A) { - let mailbox = address.into_mailbox(); - self.from_header.push(Address::Mailbox(mailbox)); - } - - /// Adds a `To` header and stores the recipient address - pub fn to(mut self, address: A) -> EmailBuilder { - self.add_to(address); - self - } - - /// Adds a `To` header and stores the recipient address - pub fn add_to(&mut self, address: A) { - let mailbox = address.into_mailbox(); - self.to_header.push(Address::Mailbox(mailbox)); - } - - /// Adds a `Cc` header and stores the recipient address - pub fn cc(mut self, address: A) -> EmailBuilder { - self.add_cc(address); - self - } - - /// Adds a `Cc` header and stores the recipient address - pub fn add_cc(&mut self, address: A) { - let mailbox = address.into_mailbox(); - self.cc_header.push(Address::Mailbox(mailbox)); - } - - /// Adds a `Bcc` header and stores the recipient address - pub fn bcc(mut self, address: A) -> EmailBuilder { - self.add_bcc(address); - self - } - - /// Adds a `Bcc` header and stores the recipient address - pub fn add_bcc(&mut self, address: A) { - let mailbox = address.into_mailbox(); - self.bcc_header.push(Address::Mailbox(mailbox)); - } - - /// Adds a `Reply-To` header - pub fn reply_to(mut self, address: A) -> EmailBuilder { - self.add_reply_to(address); - self - } - - /// Adds a `Reply-To` header - pub fn add_reply_to(&mut self, address: A) { - let mailbox = address.into_mailbox(); - self.reply_to_header.push(Address::Mailbox(mailbox)); - } - - /// Adds a `Sender` header - pub fn sender(mut self, address: A) -> EmailBuilder { - self.set_sender(address); - self - } - - /// Adds a `Sender` header - pub fn set_sender(&mut self, address: A) { - let mailbox = address.into_mailbox(); - self.sender_header = Some(mailbox); - } - - /// Adds a `Subject` header - pub fn subject>(mut self, subject: S) -> EmailBuilder { - self.set_subject(subject); - self - } - - /// Adds a `Subject` header - pub fn set_subject>(&mut self, subject: S) { - self.message - .add_header(("Subject".to_string(), subject.into())); - } - - /// Adds a `Date` header with the given date - pub fn date(mut self, date: &Tm) -> EmailBuilder { - self.set_date(date); - self - } - - /// Adds a `Date` header with the given date - pub fn set_date(&mut self, date: &Tm) { - self.message - .add_header(("Date", Tm::rfc822z(date).to_string())); - self.date_issued = true; - } - - /// Set the message type - pub fn message_type(mut self, message_type: MimeMultipartType) -> EmailBuilder { - self.set_message_type(message_type); - self - } - - /// Set the message type - pub fn set_message_type(&mut self, message_type: MimeMultipartType) { - self.message.set_message_type(message_type); - } - - /// Adds a child - pub fn child(mut self, child: MimeMessage) -> EmailBuilder { - self.add_child(child); - self - } - - /// Adds a child - pub fn add_child(&mut self, child: MimeMessage) { - self.message.add_child(child); - } - - /// Sets the email body to plain text content - pub fn text>(mut self, body: S) -> EmailBuilder { - self.set_text(body); - self - } - - /// Sets the email body to plain text content - pub fn set_text>(&mut self, body: S) { - self.message.set_body(body); - self.message.add_header(( - "Content-Type", - format!("{}", mime::TEXT_PLAIN_UTF_8).as_ref(), - )); - } - - /// Sets the email body to HTML content - pub fn html>(mut self, body: S) -> EmailBuilder { - self.set_html(body); - self - } - - /// Sets the email body to HTML content - pub fn set_html>(&mut self, body: S) { - self.message.set_body(body); - self.message - .add_header(("Content-Type", format!("{}", mime::TEXT_HTML).as_ref())); - } - - /// Sets the email content - pub fn alternative, T: Into>( - mut self, - body_html: S, - body_text: T, - ) -> EmailBuilder { - self.set_alternative(body_html, body_text); - self - } - - /// Sets the email content - pub fn set_alternative, T: Into>( - &mut self, - body_html: S, - body_text: T, - ) { - let mut alternate = PartBuilder::new(); - alternate.set_message_type(MimeMultipartType::Alternative); - - let text = PartBuilder::new() - .body(body_text) - .header(( - "Content-Type", - format!("{}", mime::TEXT_PLAIN_UTF_8).as_ref(), - )) - .build(); - - let html = PartBuilder::new() - .body(body_html) - .header(("Content-Type", format!("{}", mime::TEXT_HTML).as_ref())) - .build(); - - alternate.add_child(text); - alternate.add_child(html); - - self.set_message_type(MimeMultipartType::Mixed); - self.add_child(alternate.build()); - } - - /// Sets the envelope for manual destination control - /// If this function is not called, the envelope will be calculated - /// from the "to" and "cc" addresses you set. - pub fn envelope(mut self, envelope: Envelope) -> EmailBuilder { - self.set_envelope(envelope); - self - } - - /// Sets the envelope for manual destination control - /// If this function is not called, the envelope will be calculated - /// from the "to" and "cc" addresses you set. - pub fn set_envelope(&mut self, envelope: Envelope) { - self.envelope = Some(envelope); - } - - /// Builds the Email - pub fn build(mut self) -> Result { - // If there are multiple addresses in "From", the "Sender" is required. - if self.from_header.len() >= 2 && self.sender_header.is_none() { - // So, we must find something to put as Sender. - for possible_sender in &self.from_header { - // Only a mailbox can be used as sender, not Address::Group. - if let Address::Mailbox(ref mbx) = *possible_sender { - self.sender_header = Some(mbx.clone()); - break; - } - } - // Address::Group is not yet supported, so the line below will never panic. - // If groups are supported one day, add another Error for this case - // and return it here, if sender_header is still None at this point. - assert!(self.sender_header.is_some()); - } - // Add the sender header, if any. - if let Some(ref v) = self.sender_header { - self.message.add_header(("Sender", v.to_string().as_ref())); - } - // Calculate the envelope - let envelope = match self.envelope { - Some(e) => e, - None => { - // we need to generate the envelope - let mut e = Envelope::new(); - // add all receivers in to_header and cc_header - for receiver in self.to_header - .iter() - .chain(self.cc_header.iter()) - .chain(self.bcc_header.iter()) - { - match *receiver { - Address::Mailbox(ref m) => e.add_to(m.address.clone()), - Address::Group(_, ref ms) => { - for m in ms.iter() { - e.add_to(m.address.clone()); - } - } - } - } - if e.to.is_empty() { - return Err(Error::MissingTo); - } - e.set_from(match self.sender_header { - Some(x) => x.address.clone(), // if we have a sender_header, use it - None => { - // use a from header - debug_assert!(self.from_header.len()<=1); // else we'd have sender_header - match self.from_header.first() { - Some(a) => match *a { - // if we have a from header - Address::Mailbox(ref mailbox) => mailbox.address.clone(), // use it - Address::Group(_,ref mailbox_list) => match mailbox_list.first() { - // if it's an author group, use the first author - Some(mailbox) => mailbox.address.clone(), - // for an empty author group (the rarest of the rare cases) - None => return Err(Error::MissingFrom), // empty envelope sender - }, - }, - // if we don't have a from header - None => return Err(Error::MissingFrom), // empty envelope sender - } - } - }); - e - } - }; - // Add the collected addresses as mailbox-list all at once. - // The unwraps are fine because the conversions for Vec
never errs. - if !self.to_header.is_empty() { - self.message - .add_header(Header::new_with_value("To".into(), self.to_header).unwrap()); - } - if !self.from_header.is_empty() { - self.message.add_header( - Header::new_with_value("From".into(), self.from_header).unwrap(), - ); - } else { - return Err(Error::MissingFrom); - } - if !self.cc_header.is_empty() { - self.message - .add_header(Header::new_with_value("Cc".into(), self.cc_header).unwrap()); - } - if !self.reply_to_header.is_empty() { - self.message.add_header( - Header::new_with_value("Reply-To".into(), self.reply_to_header).unwrap(), - ); - } - - if !self.date_issued { - self.message - .add_header(("Date", Tm::rfc822z(&now()).to_string().as_ref())); - } - - self.message.add_header(("MIME-Version", "1.0")); - - let message_id = Uuid::new_v4(); - - if let Ok(header) = Header::new_with_value( - "Message-ID".to_string(), - format!("<{}.lettre@localhost>", message_id), - ) { - self.message.add_header(header) - } - - Ok(Email { - message: self.message.build(), - envelope: envelope, - message_id: message_id, - }) - } -} - -impl SendableEmail for Email { - fn to(&self) -> Vec { - self.envelope - .to - .iter() - .map(|x| EmailAddress::new(x.clone())) - .collect() - } - - fn from(&self) -> EmailAddress { - EmailAddress::new(self.envelope.from.clone()) - } - - fn message_id(&self) -> String { - format!("{}", self.message_id) - } - - fn message(self) -> String { - self.to_string() - } -} - -/// Email sendable by any type of client, giving access to all fields -pub trait ExtractableEmail { - /// From address - fn from_address(&self) -> Option; - /// To addresses - fn to_addresses(&self) -> Vec; - /// Cc addresses - fn cc_addresses(&self) -> Vec; - /// Bcc addresses - fn bcc_addresses(&self) -> Vec; - /// Replay-To addresses - fn reply_to_address(&self) -> String; - /// Subject - fn subject(&self) -> String; - /// Message ID - fn message_id(&self) -> String; - /// Other Headers - fn headers(&self) -> Vec; - /// html content - fn html(self) -> String; - /// text content - fn text(self) -> String; -} - - -#[cfg(test)] -mod test { - - use super::{Email, EmailBuilder, Envelope, IntoEmail, SimpleEmail}; - use email_format::{Header, MimeMessage}; - use lettre::{EmailAddress, SendableEmail}; - use time::now; - - use uuid::Uuid; - - #[test] - fn test_simple_email_builder() { - let email_builder = SimpleEmail::default(); - let date_now = now(); - - let email = email_builder - .to("user@localhost") - .from("user@localhost") - .cc(("cc@localhost", "Alias")) - .reply_to("reply@localhost") - .text("Hello World!") - .date(date_now.clone()) - .subject("Hello") - .header(("X-test", "value")) - .into_email() - .unwrap(); - - assert_eq!( - format!("{}", email), - format!( - "Subject: Hello\r\nContent-Type: text/plain; \ - charset=utf-8\r\nX-test: value\r\nTo: \r\nFrom: \ - \r\nCc: \"Alias\" \r\nReply-To: \ - \r\nDate: {}\r\nMIME-Version: 1.0\r\nMessage-ID: \ - <{}.lettre@localhost>\r\n\r\nHello World!\r\n", - date_now.rfc822z(), - email.message_id() - ) - ); - } - - #[test] - fn test_email_display() { - let current_message = Uuid::new_v4(); - - let mut email = Email { - message: MimeMessage::new_blank_message(), - envelope: Envelope { - to: vec![], - from: "".to_string(), - }, - message_id: current_message, - }; - - email.message.headers.insert( - Header::new_with_value( - "Message-ID".to_string(), - format!("<{}@rust-smtp>", current_message), - ).unwrap(), - ); - - email.message.headers.insert( - Header::new_with_value("To".to_string(), "to@example.com".to_string()).unwrap(), - ); - - email.message.body = "body".to_string(); - - assert_eq!( - format!("{}", email), - format!( - "Message-ID: <{}@rust-smtp>\r\nTo: to@example.com\r\n\r\nbody\r\n", - current_message - ) - ); - assert_eq!(current_message.to_string(), email.message_id()); - } - - #[test] - fn test_multiple_from() { - let email_builder = EmailBuilder::new(); - let date_now = now(); - let email = email_builder - .to("anna@example.com") - .from("dieter@example.com") - .from("joachim@example.com") - .date(&date_now) - .subject("Invitation") - .body("We invite you!") - .build() - .unwrap(); - assert_eq!( - format!("{}", email), - format!( - "Date: {}\r\nSubject: Invitation\r\nSender: \ - \r\nTo: \r\nFrom: \ - , \r\nMIME-Version: \ - 1.0\r\nMessage-ID: <{}.lettre@localhost>\r\n\r\nWe invite you!\r\n", - date_now.rfc822z(), - email.message_id() - ) - ); - } - - #[test] - fn test_email_builder() { - let email_builder = EmailBuilder::new(); - let date_now = now(); - - let email = email_builder - .to("user@localhost") - .from("user@localhost") - .cc(("cc@localhost", "Alias")) - .reply_to("reply@localhost") - .sender("sender@localhost") - .body("Hello World!") - .date(&date_now) - .subject("Hello") - .header(("X-test", "value")) - .build() - .unwrap(); - - assert_eq!( - format!("{}", email), - format!( - "Date: {}\r\nSubject: Hello\r\nX-test: value\r\nSender: \ - \r\nTo: \r\nFrom: \ - \r\nCc: \"Alias\" \r\nReply-To: \ - \r\nMIME-Version: 1.0\r\nMessage-ID: \ - <{}.lettre@localhost>\r\n\r\nHello World!\r\n", - date_now.rfc822z(), - email.message_id() - ) - ); - } - - #[test] - fn test_email_sendable() { - let email_builder = EmailBuilder::new(); - let date_now = now(); - - let email = email_builder - .to("user@localhost") - .from("user@localhost") - .cc(("cc@localhost", "Alias")) - .bcc("bcc@localhost") - .reply_to("reply@localhost") - .sender("sender@localhost") - .body("Hello World!") - .date(&date_now) - .subject("Hello") - .header(("X-test", "value")) - .build() - .unwrap(); - - assert_eq!(email.from().to_string(), "sender@localhost".to_string()); - assert_eq!( - email.to(), - vec![ - EmailAddress::new("user@localhost".to_string()), - EmailAddress::new("cc@localhost".to_string()), - EmailAddress::new("bcc@localhost".to_string()), - ] - ); - let content = format!("{}", email); - assert_eq!(email.message(), content); - } - -} diff --git a/lettre_email/src/lib.rs b/lettre_email/src/lib.rs index db0f2dd..4d5d19c 100644 --- a/lettre_email/src/lib.rs +++ b/lettre_email/src/lib.rs @@ -6,7 +6,7 @@ //! An email is built using an `EmailBuilder`. The simplest email could be: //! //! ```rust -//! use lettre_email::email::EmailBuilder; +//! use lettre_email::EmailBuilder; //! //! // Create an email //! let email = EmailBuilder::new() @@ -32,7 +32,7 @@ //! Below is a more complete example, not using method chaining: //! //! ```rust -//! use lettre_email::email::EmailBuilder; +//! use lettre_email::EmailBuilder; //! //! let mut builder = EmailBuilder::new(); //! builder.add_to(("user@example.org", "Alias name")); @@ -61,4 +61,970 @@ extern crate email as email_format; extern crate lettre; pub mod error; -pub mod email; + +pub use email_format::{Address, Header, Mailbox, MimeMessage, MimeMultipartType}; +use error::Error; +use lettre::{EmailAddress, SendableEmail}; +use mime::Mime; +use std::fmt; +use std::fmt::{Display, Formatter}; +use time::{Tm, now}; +use uuid::Uuid; + +/// Converts an address or an address with an alias to a `Header` +pub trait IntoHeader { + /// Converts to a `Header` struct + fn into_header(self) -> Header; +} + +impl IntoHeader for Header { + fn into_header(self) -> Header { + self + } +} + +impl, T: Into> IntoHeader for (S, T) { + fn into_header(self) -> Header { + let (name, value) = self; + Header::new(name.into(), value.into()) + } +} + +/// Converts an adress or an address with an alias to a `Mailbox` +pub trait IntoMailbox { + /// Converts to a `Mailbox` struct + fn into_mailbox(self) -> Mailbox; +} + +impl IntoMailbox for Mailbox { + fn into_mailbox(self) -> Mailbox { + self + } +} + +impl<'a> IntoMailbox for &'a str { + fn into_mailbox(self) -> Mailbox { + Mailbox::new(self.into()) + } +} + +impl IntoMailbox for String { + fn into_mailbox(self) -> Mailbox { + Mailbox::new(self) + } +} + +impl, T: Into> IntoMailbox for (S, T) { + fn into_mailbox(self) -> Mailbox { + let (address, alias) = self; + Mailbox::new_with_name(alias.into(), address.into()) + } +} + +/// Can be transformed to a sendable email +pub trait IntoEmail { + /// Builds an email + fn into_email(self) -> Result; +} + +impl IntoEmail for SimpleEmail { + fn into_email(self) -> Result { + let mut builder = EmailBuilder::new(); + + if self.from.is_some() { + builder.add_from(self.from.unwrap()); + } + + for to_address in self.to { + builder.add_to(to_address.into_mailbox()); + } + + for cc_address in self.cc { + builder.add_cc(cc_address.into_mailbox()); + } + + if self.reply_to.is_some() { + builder.add_reply_to(self.reply_to.unwrap().into_mailbox()); + } + + if self.subject.is_some() { + builder.set_subject(self.subject.unwrap()); + } + + // No date for now + + match (self.text, self.html) { + (Some(text), Some(html)) => builder.set_alternative(html, text), + (Some(text), None) => builder.set_text(text), + (None, Some(html)) => builder.set_html(html), + (None, None) => (), + } + + for header in self.headers { + builder.add_header(header.into_header()); + } + + builder.build() + } +} + + +/// Simple representation of an email, useful for some transports +#[derive(PartialEq, Eq, Clone, Debug, Default)] +pub struct SimpleEmail { + from: Option, + to: Vec, + cc: Vec, + bcc: Vec, + reply_to: Option, + subject: Option, + date: Option, + html: Option, + text: Option, + // attachments: Vec, + headers: Vec
, +} + +impl SimpleEmail { + /// Adds a generic header + pub fn header(mut self, header: A) -> SimpleEmail { + self.add_header(header); + self + } + + /// Adds a generic header + pub fn add_header(&mut self, header: A) { + self.headers.push(header.into_header()); + } + + /// Adds a `From` header and stores the sender address + pub fn from(mut self, address: A) -> SimpleEmail { + self.add_from(address); + self + } + + /// Adds a `From` header and stores the sender address + pub fn add_from(&mut self, address: A) { + self.from = Some(address.into_mailbox()); + } + + /// Adds a `To` header and stores the recipient address + pub fn to(mut self, address: A) -> SimpleEmail { + self.add_to(address); + self + } + + /// Adds a `To` header and stores the recipient address + pub fn add_to(&mut self, address: A) { + self.to.push(address.into_mailbox()); + } + + /// Adds a `Cc` header and stores the recipient address + pub fn cc(mut self, address: A) -> SimpleEmail { + self.add_cc(address); + self + } + + /// Adds a `Cc` header and stores the recipient address + pub fn add_cc(&mut self, address: A) { + self.cc.push(address.into_mailbox()); + } + + /// Adds a `Bcc` header and stores the recipient address + pub fn bcc(mut self, address: A) -> SimpleEmail { + self.add_bcc(address); + self + } + + /// Adds a `Bcc` header and stores the recipient address + pub fn add_bcc(&mut self, address: A) { + self.bcc.push(address.into_mailbox()); + } + + /// Adds a `Reply-To` header + pub fn reply_to(mut self, address: A) -> SimpleEmail { + self.add_reply_to(address); + self + } + + /// Adds a `Reply-To` header + pub fn add_reply_to(&mut self, address: A) { + self.reply_to = Some(address.into_mailbox()); + } + + /// Adds a `Subject` header + pub fn subject>(mut self, subject: S) -> SimpleEmail { + self.set_subject(subject); + self + } + + /// Adds a `Subject` header + pub fn set_subject>(&mut self, subject: S) { + self.subject = Some(subject.into()); + } + + /// Adds a `Date` header with the given date + pub fn date(mut self, date: Tm) -> SimpleEmail { + self.set_date(date); + self + } + + /// Adds a `Date` header with the given date + pub fn set_date(&mut self, date: Tm) { + self.date = Some(date); + } + + /// Sets the email body to plain text content + pub fn text>(mut self, body: S) -> SimpleEmail { + self.set_text(body); + self + } + + /// Sets the email body to plain text content + pub fn set_text>(&mut self, body: S) { + self.text = Some(body.into()); + } + + /// Sets the email body to HTML content + pub fn html>(mut self, body: S) -> SimpleEmail { + self.set_html(body); + self + } + + /// Sets the email body to HTML content + pub fn set_html>(&mut self, body: S) { + self.html = Some(body.into()); + } +} + +/// Builds a `MimeMessage` structure +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct PartBuilder { + /// Message + message: MimeMessage, +} + +impl Default for PartBuilder { + fn default() -> Self { + Self::new() + } +} + + +/// Builds an `Email` structure +#[derive(PartialEq, Eq, Clone, Debug, Default)] +pub struct EmailBuilder { + /// Message + message: PartBuilder, + /// The recipients' addresses for the mail header + to_header: Vec
, + /// The sender addresses for the mail header + from_header: Vec
, + /// The Cc addresses for the mail header + cc_header: Vec
, + /// The Bcc addresses for the mail header + bcc_header: Vec
, + /// The Reply-To addresses for the mail header + reply_to_header: Vec
, + /// The sender address for the mail header + sender_header: Option, + /// The envelope + envelope: Option, + /// Date issued + date_issued: bool, +} + +/// Simple email enveloppe representation +#[derive(PartialEq, Eq, Clone, Debug, Default)] +pub struct Envelope { + /// The envelope recipients' addresses + pub to: Vec, + /// The envelope sender address + pub from: String, +} + +impl Envelope { + /// Constructs an envelope with no receivers and an empty sender + pub fn new() -> Self { + Envelope { + to: vec![], + from: String::new(), + } + } + /// Adds a receiver + pub fn to>(mut self, address: S) -> Self { + self.add_to(address); + self + } + /// Adds a receiver + pub fn add_to>(&mut self, address: S) { + self.to.push(address.into()); + } + /// Sets the sender + pub fn from>(mut self, address: S) -> Self { + self.set_from(address); + self + } + /// Sets the sender + pub fn set_from>(&mut self, address: S) { + self.from = address.into(); + } +} + +/// Simple email representation +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct Email { + /// Message + message: MimeMessage, + /// Envelope + envelope: Envelope, + /// Message-ID + message_id: Uuid, +} + +impl Display for Email { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}", self.message.as_string()) + } +} + +impl PartBuilder { + /// Creates a new empty part + pub fn new() -> PartBuilder { + PartBuilder { + message: MimeMessage::new_blank_message(), + } + } + + /// Adds a generic header + pub fn header(mut self, header: A) -> PartBuilder { + self.add_header(header); + self + } + + /// Adds a generic header + pub fn add_header(&mut self, header: A) { + self.message.headers.insert(header.into_header()); + } + + /// Sets the body + pub fn body>(mut self, body: S) -> PartBuilder { + self.set_body(body); + self + } + + /// Sets the body + pub fn set_body>(&mut self, body: S) { + self.message.body = body.into(); + } + + /// Defines a `MimeMultipartType` value + pub fn message_type(mut self, mime_type: MimeMultipartType) -> PartBuilder { + self.set_message_type(mime_type); + self + } + + /// Defines a `MimeMultipartType` value + pub fn set_message_type(&mut self, mime_type: MimeMultipartType) { + self.message.message_type = Some(mime_type); + } + + /// Adds a `ContentType` header with the given MIME type + pub fn content_type(mut self, content_type: Mime) -> PartBuilder { + self.set_content_type(content_type); + self + } + + /// Adds a `ContentType` header with the given MIME type + pub fn set_content_type(&mut self, content_type: Mime) { + self.add_header(("Content-Type", format!("{}", content_type).as_ref())); + } + + /// Adds a child part + pub fn child(mut self, child: MimeMessage) -> PartBuilder { + self.add_child(child); + self + } + + /// Adds a child part + pub fn add_child(&mut self, child: MimeMessage) { + self.message.children.push(child); + } + + /// Gets builded `MimeMessage` + pub fn build(mut self) -> MimeMessage { + self.message.update_headers(); + self.message + } +} + +impl EmailBuilder { + /// Creates a new empty email + pub fn new() -> EmailBuilder { + EmailBuilder { + message: PartBuilder::new(), + to_header: vec![], + from_header: vec![], + cc_header: vec![], + bcc_header: vec![], + reply_to_header: vec![], + sender_header: None, + envelope: None, + date_issued: false, + } + } + + /// Sets the email body + pub fn body>(mut self, body: S) -> EmailBuilder { + self.message.set_body(body); + self + } + + /// Sets the email body + pub fn set_body>(&mut self, body: S) { + self.message.set_body(body); + } + + /// Add a generic header + pub fn header(mut self, header: A) -> EmailBuilder { + self.message.add_header(header); + self + } + + /// Add a generic header + pub fn add_header(&mut self, header: A) { + self.message.add_header(header); + } + + /// Adds a `From` header and stores the sender address + pub fn from(mut self, address: A) -> EmailBuilder { + self.add_from(address); + self + } + + /// Adds a `From` header and stores the sender address + pub fn add_from(&mut self, address: A) { + let mailbox = address.into_mailbox(); + self.from_header.push(Address::Mailbox(mailbox)); + } + + /// Adds a `To` header and stores the recipient address + pub fn to(mut self, address: A) -> EmailBuilder { + self.add_to(address); + self + } + + /// Adds a `To` header and stores the recipient address + pub fn add_to(&mut self, address: A) { + let mailbox = address.into_mailbox(); + self.to_header.push(Address::Mailbox(mailbox)); + } + + /// Adds a `Cc` header and stores the recipient address + pub fn cc(mut self, address: A) -> EmailBuilder { + self.add_cc(address); + self + } + + /// Adds a `Cc` header and stores the recipient address + pub fn add_cc(&mut self, address: A) { + let mailbox = address.into_mailbox(); + self.cc_header.push(Address::Mailbox(mailbox)); + } + + /// Adds a `Bcc` header and stores the recipient address + pub fn bcc(mut self, address: A) -> EmailBuilder { + self.add_bcc(address); + self + } + + /// Adds a `Bcc` header and stores the recipient address + pub fn add_bcc(&mut self, address: A) { + let mailbox = address.into_mailbox(); + self.bcc_header.push(Address::Mailbox(mailbox)); + } + + /// Adds a `Reply-To` header + pub fn reply_to(mut self, address: A) -> EmailBuilder { + self.add_reply_to(address); + self + } + + /// Adds a `Reply-To` header + pub fn add_reply_to(&mut self, address: A) { + let mailbox = address.into_mailbox(); + self.reply_to_header.push(Address::Mailbox(mailbox)); + } + + /// Adds a `Sender` header + pub fn sender(mut self, address: A) -> EmailBuilder { + self.set_sender(address); + self + } + + /// Adds a `Sender` header + pub fn set_sender(&mut self, address: A) { + let mailbox = address.into_mailbox(); + self.sender_header = Some(mailbox); + } + + /// Adds a `Subject` header + pub fn subject>(mut self, subject: S) -> EmailBuilder { + self.set_subject(subject); + self + } + + /// Adds a `Subject` header + pub fn set_subject>(&mut self, subject: S) { + self.message + .add_header(("Subject".to_string(), subject.into())); + } + + /// Adds a `Date` header with the given date + pub fn date(mut self, date: &Tm) -> EmailBuilder { + self.set_date(date); + self + } + + /// Adds a `Date` header with the given date + pub fn set_date(&mut self, date: &Tm) { + self.message + .add_header(("Date", Tm::rfc822z(date).to_string())); + self.date_issued = true; + } + + /// Set the message type + pub fn message_type(mut self, message_type: MimeMultipartType) -> EmailBuilder { + self.set_message_type(message_type); + self + } + + /// Set the message type + pub fn set_message_type(&mut self, message_type: MimeMultipartType) { + self.message.set_message_type(message_type); + } + + /// Adds a child + pub fn child(mut self, child: MimeMessage) -> EmailBuilder { + self.add_child(child); + self + } + + /// Adds a child + pub fn add_child(&mut self, child: MimeMessage) { + self.message.add_child(child); + } + + /// Sets the email body to plain text content + pub fn text>(mut self, body: S) -> EmailBuilder { + self.set_text(body); + self + } + + /// Sets the email body to plain text content + pub fn set_text>(&mut self, body: S) { + self.message.set_body(body); + self.message.add_header(( + "Content-Type", + format!("{}", mime::TEXT_PLAIN_UTF_8).as_ref(), + )); + } + + /// Sets the email body to HTML content + pub fn html>(mut self, body: S) -> EmailBuilder { + self.set_html(body); + self + } + + /// Sets the email body to HTML content + pub fn set_html>(&mut self, body: S) { + self.message.set_body(body); + self.message + .add_header(("Content-Type", format!("{}", mime::TEXT_HTML).as_ref())); + } + + /// Sets the email content + pub fn alternative, T: Into>( + mut self, + body_html: S, + body_text: T, + ) -> EmailBuilder { + self.set_alternative(body_html, body_text); + self + } + + /// Sets the email content + pub fn set_alternative, T: Into>( + &mut self, + body_html: S, + body_text: T, + ) { + let mut alternate = PartBuilder::new(); + alternate.set_message_type(MimeMultipartType::Alternative); + + let text = PartBuilder::new() + .body(body_text) + .header(( + "Content-Type", + format!("{}", mime::TEXT_PLAIN_UTF_8).as_ref(), + )) + .build(); + + let html = PartBuilder::new() + .body(body_html) + .header(("Content-Type", format!("{}", mime::TEXT_HTML).as_ref())) + .build(); + + alternate.add_child(text); + alternate.add_child(html); + + self.set_message_type(MimeMultipartType::Mixed); + self.add_child(alternate.build()); + } + + /// Sets the envelope for manual destination control + /// If this function is not called, the envelope will be calculated + /// from the "to" and "cc" addresses you set. + pub fn envelope(mut self, envelope: Envelope) -> EmailBuilder { + self.set_envelope(envelope); + self + } + + /// Sets the envelope for manual destination control + /// If this function is not called, the envelope will be calculated + /// from the "to" and "cc" addresses you set. + pub fn set_envelope(&mut self, envelope: Envelope) { + self.envelope = Some(envelope); + } + + /// Builds the Email + pub fn build(mut self) -> Result { + // If there are multiple addresses in "From", the "Sender" is required. + if self.from_header.len() >= 2 && self.sender_header.is_none() { + // So, we must find something to put as Sender. + for possible_sender in &self.from_header { + // Only a mailbox can be used as sender, not Address::Group. + if let Address::Mailbox(ref mbx) = *possible_sender { + self.sender_header = Some(mbx.clone()); + break; + } + } + // Address::Group is not yet supported, so the line below will never panic. + // If groups are supported one day, add another Error for this case + // and return it here, if sender_header is still None at this point. + assert!(self.sender_header.is_some()); + } + // Add the sender header, if any. + if let Some(ref v) = self.sender_header { + self.message.add_header(("Sender", v.to_string().as_ref())); + } + // Calculate the envelope + let envelope = match self.envelope { + Some(e) => e, + None => { + // we need to generate the envelope + let mut e = Envelope::new(); + // add all receivers in to_header and cc_header + for receiver in self.to_header + .iter() + .chain(self.cc_header.iter()) + .chain(self.bcc_header.iter()) + { + match *receiver { + Address::Mailbox(ref m) => e.add_to(m.address.clone()), + Address::Group(_, ref ms) => { + for m in ms.iter() { + e.add_to(m.address.clone()); + } + } + } + } + if e.to.is_empty() { + return Err(Error::MissingTo); + } + e.set_from(match self.sender_header { + Some(x) => x.address.clone(), // if we have a sender_header, use it + None => { + // use a from header + debug_assert!(self.from_header.len()<=1); // else we'd have sender_header + match self.from_header.first() { + Some(a) => match *a { + // if we have a from header + Address::Mailbox(ref mailbox) => mailbox.address.clone(), // use it + Address::Group(_,ref mailbox_list) => match mailbox_list.first() { + // if it's an author group, use the first author + Some(mailbox) => mailbox.address.clone(), + // for an empty author group (the rarest of the rare cases) + None => return Err(Error::MissingFrom), // empty envelope sender + }, + }, + // if we don't have a from header + None => return Err(Error::MissingFrom), // empty envelope sender + } + } + }); + e + } + }; + // Add the collected addresses as mailbox-list all at once. + // The unwraps are fine because the conversions for Vec
never errs. + if !self.to_header.is_empty() { + self.message + .add_header(Header::new_with_value("To".into(), self.to_header).unwrap()); + } + if !self.from_header.is_empty() { + self.message.add_header( + Header::new_with_value("From".into(), self.from_header).unwrap(), + ); + } else { + return Err(Error::MissingFrom); + } + if !self.cc_header.is_empty() { + self.message + .add_header(Header::new_with_value("Cc".into(), self.cc_header).unwrap()); + } + if !self.reply_to_header.is_empty() { + self.message.add_header( + Header::new_with_value("Reply-To".into(), self.reply_to_header).unwrap(), + ); + } + + if !self.date_issued { + self.message + .add_header(("Date", Tm::rfc822z(&now()).to_string().as_ref())); + } + + self.message.add_header(("MIME-Version", "1.0")); + + let message_id = Uuid::new_v4(); + + if let Ok(header) = Header::new_with_value( + "Message-ID".to_string(), + format!("<{}.lettre@localhost>", message_id), + ) { + self.message.add_header(header) + } + + Ok(Email { + message: self.message.build(), + envelope: envelope, + message_id: message_id, + }) + } +} + +impl SendableEmail for Email { + fn to(&self) -> Vec { + self.envelope + .to + .iter() + .map(|x| EmailAddress::new(x.clone())) + .collect() + } + + fn from(&self) -> EmailAddress { + EmailAddress::new(self.envelope.from.clone()) + } + + fn message_id(&self) -> String { + format!("{}", self.message_id) + } + + fn message(self) -> String { + self.to_string() + } +} + +/// Email sendable by any type of client, giving access to all fields +pub trait ExtractableEmail { + /// From address + fn from_address(&self) -> Option; + /// To addresses + fn to_addresses(&self) -> Vec; + /// Cc addresses + fn cc_addresses(&self) -> Vec; + /// Bcc addresses + fn bcc_addresses(&self) -> Vec; + /// Replay-To addresses + fn reply_to_address(&self) -> String; + /// Subject + fn subject(&self) -> String; + /// Message ID + fn message_id(&self) -> String; + /// Other Headers + fn headers(&self) -> Vec; + /// html content + fn html(self) -> String; + /// text content + fn text(self) -> String; +} + + +#[cfg(test)] +mod test { + + use super::{Email, EmailBuilder, Envelope, IntoEmail, SimpleEmail}; + use email_format::{Header, MimeMessage}; + use lettre::{EmailAddress, SendableEmail}; + use time::now; + + use uuid::Uuid; + + #[test] + fn test_simple_email_builder() { + let email_builder = SimpleEmail::default(); + let date_now = now(); + + let email = email_builder + .to("user@localhost") + .from("user@localhost") + .cc(("cc@localhost", "Alias")) + .reply_to("reply@localhost") + .text("Hello World!") + .date(date_now.clone()) + .subject("Hello") + .header(("X-test", "value")) + .into_email() + .unwrap(); + + assert_eq!( + format!("{}", email), + format!( + "Subject: Hello\r\nContent-Type: text/plain; \ + charset=utf-8\r\nX-test: value\r\nTo: \r\nFrom: \ + \r\nCc: \"Alias\" \r\nReply-To: \ + \r\nDate: {}\r\nMIME-Version: 1.0\r\nMessage-ID: \ + <{}.lettre@localhost>\r\n\r\nHello World!\r\n", + date_now.rfc822z(), + email.message_id() + ) + ); + } + + #[test] + fn test_email_display() { + let current_message = Uuid::new_v4(); + + let mut email = Email { + message: MimeMessage::new_blank_message(), + envelope: Envelope { + to: vec![], + from: "".to_string(), + }, + message_id: current_message, + }; + + email.message.headers.insert( + Header::new_with_value( + "Message-ID".to_string(), + format!("<{}@rust-smtp>", current_message), + ).unwrap(), + ); + + email.message.headers.insert( + Header::new_with_value("To".to_string(), "to@example.com".to_string()).unwrap(), + ); + + email.message.body = "body".to_string(); + + assert_eq!( + format!("{}", email), + format!( + "Message-ID: <{}@rust-smtp>\r\nTo: to@example.com\r\n\r\nbody\r\n", + current_message + ) + ); + assert_eq!(current_message.to_string(), email.message_id()); + } + + #[test] + fn test_multiple_from() { + let email_builder = EmailBuilder::new(); + let date_now = now(); + let email = email_builder + .to("anna@example.com") + .from("dieter@example.com") + .from("joachim@example.com") + .date(&date_now) + .subject("Invitation") + .body("We invite you!") + .build() + .unwrap(); + assert_eq!( + format!("{}", email), + format!( + "Date: {}\r\nSubject: Invitation\r\nSender: \ + \r\nTo: \r\nFrom: \ + , \r\nMIME-Version: \ + 1.0\r\nMessage-ID: <{}.lettre@localhost>\r\n\r\nWe invite you!\r\n", + date_now.rfc822z(), + email.message_id() + ) + ); + } + + #[test] + fn test_email_builder() { + let email_builder = EmailBuilder::new(); + let date_now = now(); + + let email = email_builder + .to("user@localhost") + .from("user@localhost") + .cc(("cc@localhost", "Alias")) + .reply_to("reply@localhost") + .sender("sender@localhost") + .body("Hello World!") + .date(&date_now) + .subject("Hello") + .header(("X-test", "value")) + .build() + .unwrap(); + + assert_eq!( + format!("{}", email), + format!( + "Date: {}\r\nSubject: Hello\r\nX-test: value\r\nSender: \ + \r\nTo: \r\nFrom: \ + \r\nCc: \"Alias\" \r\nReply-To: \ + \r\nMIME-Version: 1.0\r\nMessage-ID: \ + <{}.lettre@localhost>\r\n\r\nHello World!\r\n", + date_now.rfc822z(), + email.message_id() + ) + ); + } + + #[test] + fn test_email_sendable() { + let email_builder = EmailBuilder::new(); + let date_now = now(); + + let email = email_builder + .to("user@localhost") + .from("user@localhost") + .cc(("cc@localhost", "Alias")) + .bcc("bcc@localhost") + .reply_to("reply@localhost") + .sender("sender@localhost") + .body("Hello World!") + .date(&date_now) + .subject("Hello") + .header(("X-test", "value")) + .build() + .unwrap(); + + assert_eq!(email.from().to_string(), "sender@localhost".to_string()); + assert_eq!( + email.to(), + vec![ + EmailAddress::new("user@localhost".to_string()), + EmailAddress::new("cc@localhost".to_string()), + EmailAddress::new("bcc@localhost".to_string()), + ] + ); + let content = format!("{}", email); + assert_eq!(email.message(), content); + } + +}