Merge pull request #178 from amousset/mailbox-public

feat(email): Export email_format types (fixes #148)
This commit is contained in:
Alexis Mousset
2017-07-19 09:09:03 +02:00
committed by GitHub
3 changed files with 970 additions and 973 deletions

View File

@@ -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()

View File

@@ -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<S: Into<String>, T: Into<String>> 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<S: Into<String>, T: Into<String>> 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<Email, Error>;
}
impl IntoEmail for SimpleEmail {
fn into_email(self) -> Result<Email, Error> {
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<Mailbox>,
to: Vec<Mailbox>,
cc: Vec<Mailbox>,
bcc: Vec<Mailbox>,
reply_to: Option<Mailbox>,
subject: Option<String>,
date: Option<Tm>,
html: Option<String>,
text: Option<String>,
// attachments: Vec<String>,
headers: Vec<Header>,
}
impl SimpleEmail {
/// Adds a generic header
pub fn header<A: IntoHeader>(mut self, header: A) -> SimpleEmail {
self.add_header(header);
self
}
/// Adds a generic header
pub fn add_header<A: IntoHeader>(&mut self, header: A) {
self.headers.push(header.into_header());
}
/// Adds a `From` header and stores the sender address
pub fn from<A: IntoMailbox>(mut self, address: A) -> SimpleEmail {
self.add_from(address);
self
}
/// Adds a `From` header and stores the sender address
pub fn add_from<A: IntoMailbox>(&mut self, address: A) {
self.from = Some(address.into_mailbox());
}
/// Adds a `To` header and stores the recipient address
pub fn to<A: IntoMailbox>(mut self, address: A) -> SimpleEmail {
self.add_to(address);
self
}
/// Adds a `To` header and stores the recipient address
pub fn add_to<A: IntoMailbox>(&mut self, address: A) {
self.to.push(address.into_mailbox());
}
/// Adds a `Cc` header and stores the recipient address
pub fn cc<A: IntoMailbox>(mut self, address: A) -> SimpleEmail {
self.add_cc(address);
self
}
/// Adds a `Cc` header and stores the recipient address
pub fn add_cc<A: IntoMailbox>(&mut self, address: A) {
self.cc.push(address.into_mailbox());
}
/// Adds a `Bcc` header and stores the recipient address
pub fn bcc<A: IntoMailbox>(mut self, address: A) -> SimpleEmail {
self.add_bcc(address);
self
}
/// Adds a `Bcc` header and stores the recipient address
pub fn add_bcc<A: IntoMailbox>(&mut self, address: A) {
self.bcc.push(address.into_mailbox());
}
/// Adds a `Reply-To` header
pub fn reply_to<A: IntoMailbox>(mut self, address: A) -> SimpleEmail {
self.add_reply_to(address);
self
}
/// Adds a `Reply-To` header
pub fn add_reply_to<A: IntoMailbox>(&mut self, address: A) {
self.reply_to = Some(address.into_mailbox());
}
/// Adds a `Subject` header
pub fn subject<S: Into<String>>(mut self, subject: S) -> SimpleEmail {
self.set_subject(subject);
self
}
/// Adds a `Subject` header
pub fn set_subject<S: Into<String>>(&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<S: Into<String>>(mut self, body: S) -> SimpleEmail {
self.set_text(body);
self
}
/// Sets the email body to plain text content
pub fn set_text<S: Into<String>>(&mut self, body: S) {
self.text = Some(body.into());
}
/// Sets the email body to HTML content
pub fn html<S: Into<String>>(mut self, body: S) -> SimpleEmail {
self.set_html(body);
self
}
/// Sets the email body to HTML content
pub fn set_html<S: Into<String>>(&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<Address>,
/// The sender addresses for the mail header
from_header: Vec<Address>,
/// The Cc addresses for the mail header
cc_header: Vec<Address>,
/// The Bcc addresses for the mail header
bcc_header: Vec<Address>,
/// The Reply-To addresses for the mail header
reply_to_header: Vec<Address>,
/// The sender address for the mail header
sender_header: Option<Mailbox>,
/// The envelope
envelope: Option<Envelope>,
/// 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<String>,
/// 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<S: Into<String>>(mut self, address: S) -> Self {
self.add_to(address);
self
}
/// Adds a receiver
pub fn add_to<S: Into<String>>(&mut self, address: S) {
self.to.push(address.into());
}
/// Sets the sender
pub fn from<S: Into<String>>(mut self, address: S) -> Self {
self.set_from(address);
self
}
/// Sets the sender
pub fn set_from<S: Into<String>>(&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<A: IntoHeader>(mut self, header: A) -> PartBuilder {
self.add_header(header);
self
}
/// Adds a generic header
pub fn add_header<A: IntoHeader>(&mut self, header: A) {
self.message.headers.insert(header.into_header());
}
/// Sets the body
pub fn body<S: Into<String>>(mut self, body: S) -> PartBuilder {
self.set_body(body);
self
}
/// Sets the body
pub fn set_body<S: Into<String>>(&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<S: Into<String>>(mut self, body: S) -> EmailBuilder {
self.message.set_body(body);
self
}
/// Sets the email body
pub fn set_body<S: Into<String>>(&mut self, body: S) {
self.message.set_body(body);
}
/// Add a generic header
pub fn header<A: IntoHeader>(mut self, header: A) -> EmailBuilder {
self.message.add_header(header);
self
}
/// Add a generic header
pub fn add_header<A: IntoHeader>(&mut self, header: A) {
self.message.add_header(header);
}
/// Adds a `From` header and stores the sender address
pub fn from<A: IntoMailbox>(mut self, address: A) -> EmailBuilder {
self.add_from(address);
self
}
/// Adds a `From` header and stores the sender address
pub fn add_from<A: IntoMailbox>(&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<A: IntoMailbox>(mut self, address: A) -> EmailBuilder {
self.add_to(address);
self
}
/// Adds a `To` header and stores the recipient address
pub fn add_to<A: IntoMailbox>(&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<A: IntoMailbox>(mut self, address: A) -> EmailBuilder {
self.add_cc(address);
self
}
/// Adds a `Cc` header and stores the recipient address
pub fn add_cc<A: IntoMailbox>(&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<A: IntoMailbox>(mut self, address: A) -> EmailBuilder {
self.add_bcc(address);
self
}
/// Adds a `Bcc` header and stores the recipient address
pub fn add_bcc<A: IntoMailbox>(&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<A: IntoMailbox>(mut self, address: A) -> EmailBuilder {
self.add_reply_to(address);
self
}
/// Adds a `Reply-To` header
pub fn add_reply_to<A: IntoMailbox>(&mut self, address: A) {
let mailbox = address.into_mailbox();
self.reply_to_header.push(Address::Mailbox(mailbox));
}
/// Adds a `Sender` header
pub fn sender<A: IntoMailbox>(mut self, address: A) -> EmailBuilder {
self.set_sender(address);
self
}
/// Adds a `Sender` header
pub fn set_sender<A: IntoMailbox>(&mut self, address: A) {
let mailbox = address.into_mailbox();
self.sender_header = Some(mailbox);
}
/// Adds a `Subject` header
pub fn subject<S: Into<String>>(mut self, subject: S) -> EmailBuilder {
self.set_subject(subject);
self
}
/// Adds a `Subject` header
pub fn set_subject<S: Into<String>>(&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<S: Into<String>>(mut self, body: S) -> EmailBuilder {
self.set_text(body);
self
}
/// Sets the email body to plain text content
pub fn set_text<S: Into<String>>(&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<S: Into<String>>(mut self, body: S) -> EmailBuilder {
self.set_html(body);
self
}
/// Sets the email body to HTML content
pub fn set_html<S: Into<String>>(&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<S: Into<String>, T: Into<String>>(
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<S: Into<String>, T: Into<String>>(
&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<Email, Error> {
// 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<Address> 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<EmailAddress> {
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<String>;
/// To addresses
fn to_addresses(&self) -> Vec<String>;
/// Cc addresses
fn cc_addresses(&self) -> Vec<String>;
/// Bcc addresses
fn bcc_addresses(&self) -> Vec<String>;
/// 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<String>;
/// 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: <user@localhost>\r\nFrom: \
<user@localhost>\r\nCc: \"Alias\" <cc@localhost>\r\nReply-To: \
<reply@localhost>\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: \
<dieter@example.com>\r\nTo: <anna@example.com>\r\nFrom: \
<dieter@example.com>, <joachim@example.com>\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: \
<sender@localhost>\r\nTo: <user@localhost>\r\nFrom: \
<user@localhost>\r\nCc: \"Alias\" <cc@localhost>\r\nReply-To: \
<reply@localhost>\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);
}
}

View File

@@ -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<S: Into<String>, T: Into<String>> 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<S: Into<String>, T: Into<String>> 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<Email, Error>;
}
impl IntoEmail for SimpleEmail {
fn into_email(self) -> Result<Email, Error> {
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<Mailbox>,
to: Vec<Mailbox>,
cc: Vec<Mailbox>,
bcc: Vec<Mailbox>,
reply_to: Option<Mailbox>,
subject: Option<String>,
date: Option<Tm>,
html: Option<String>,
text: Option<String>,
// attachments: Vec<String>,
headers: Vec<Header>,
}
impl SimpleEmail {
/// Adds a generic header
pub fn header<A: IntoHeader>(mut self, header: A) -> SimpleEmail {
self.add_header(header);
self
}
/// Adds a generic header
pub fn add_header<A: IntoHeader>(&mut self, header: A) {
self.headers.push(header.into_header());
}
/// Adds a `From` header and stores the sender address
pub fn from<A: IntoMailbox>(mut self, address: A) -> SimpleEmail {
self.add_from(address);
self
}
/// Adds a `From` header and stores the sender address
pub fn add_from<A: IntoMailbox>(&mut self, address: A) {
self.from = Some(address.into_mailbox());
}
/// Adds a `To` header and stores the recipient address
pub fn to<A: IntoMailbox>(mut self, address: A) -> SimpleEmail {
self.add_to(address);
self
}
/// Adds a `To` header and stores the recipient address
pub fn add_to<A: IntoMailbox>(&mut self, address: A) {
self.to.push(address.into_mailbox());
}
/// Adds a `Cc` header and stores the recipient address
pub fn cc<A: IntoMailbox>(mut self, address: A) -> SimpleEmail {
self.add_cc(address);
self
}
/// Adds a `Cc` header and stores the recipient address
pub fn add_cc<A: IntoMailbox>(&mut self, address: A) {
self.cc.push(address.into_mailbox());
}
/// Adds a `Bcc` header and stores the recipient address
pub fn bcc<A: IntoMailbox>(mut self, address: A) -> SimpleEmail {
self.add_bcc(address);
self
}
/// Adds a `Bcc` header and stores the recipient address
pub fn add_bcc<A: IntoMailbox>(&mut self, address: A) {
self.bcc.push(address.into_mailbox());
}
/// Adds a `Reply-To` header
pub fn reply_to<A: IntoMailbox>(mut self, address: A) -> SimpleEmail {
self.add_reply_to(address);
self
}
/// Adds a `Reply-To` header
pub fn add_reply_to<A: IntoMailbox>(&mut self, address: A) {
self.reply_to = Some(address.into_mailbox());
}
/// Adds a `Subject` header
pub fn subject<S: Into<String>>(mut self, subject: S) -> SimpleEmail {
self.set_subject(subject);
self
}
/// Adds a `Subject` header
pub fn set_subject<S: Into<String>>(&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<S: Into<String>>(mut self, body: S) -> SimpleEmail {
self.set_text(body);
self
}
/// Sets the email body to plain text content
pub fn set_text<S: Into<String>>(&mut self, body: S) {
self.text = Some(body.into());
}
/// Sets the email body to HTML content
pub fn html<S: Into<String>>(mut self, body: S) -> SimpleEmail {
self.set_html(body);
self
}
/// Sets the email body to HTML content
pub fn set_html<S: Into<String>>(&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<Address>,
/// The sender addresses for the mail header
from_header: Vec<Address>,
/// The Cc addresses for the mail header
cc_header: Vec<Address>,
/// The Bcc addresses for the mail header
bcc_header: Vec<Address>,
/// The Reply-To addresses for the mail header
reply_to_header: Vec<Address>,
/// The sender address for the mail header
sender_header: Option<Mailbox>,
/// The envelope
envelope: Option<Envelope>,
/// 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<String>,
/// 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<S: Into<String>>(mut self, address: S) -> Self {
self.add_to(address);
self
}
/// Adds a receiver
pub fn add_to<S: Into<String>>(&mut self, address: S) {
self.to.push(address.into());
}
/// Sets the sender
pub fn from<S: Into<String>>(mut self, address: S) -> Self {
self.set_from(address);
self
}
/// Sets the sender
pub fn set_from<S: Into<String>>(&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<A: IntoHeader>(mut self, header: A) -> PartBuilder {
self.add_header(header);
self
}
/// Adds a generic header
pub fn add_header<A: IntoHeader>(&mut self, header: A) {
self.message.headers.insert(header.into_header());
}
/// Sets the body
pub fn body<S: Into<String>>(mut self, body: S) -> PartBuilder {
self.set_body(body);
self
}
/// Sets the body
pub fn set_body<S: Into<String>>(&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<S: Into<String>>(mut self, body: S) -> EmailBuilder {
self.message.set_body(body);
self
}
/// Sets the email body
pub fn set_body<S: Into<String>>(&mut self, body: S) {
self.message.set_body(body);
}
/// Add a generic header
pub fn header<A: IntoHeader>(mut self, header: A) -> EmailBuilder {
self.message.add_header(header);
self
}
/// Add a generic header
pub fn add_header<A: IntoHeader>(&mut self, header: A) {
self.message.add_header(header);
}
/// Adds a `From` header and stores the sender address
pub fn from<A: IntoMailbox>(mut self, address: A) -> EmailBuilder {
self.add_from(address);
self
}
/// Adds a `From` header and stores the sender address
pub fn add_from<A: IntoMailbox>(&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<A: IntoMailbox>(mut self, address: A) -> EmailBuilder {
self.add_to(address);
self
}
/// Adds a `To` header and stores the recipient address
pub fn add_to<A: IntoMailbox>(&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<A: IntoMailbox>(mut self, address: A) -> EmailBuilder {
self.add_cc(address);
self
}
/// Adds a `Cc` header and stores the recipient address
pub fn add_cc<A: IntoMailbox>(&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<A: IntoMailbox>(mut self, address: A) -> EmailBuilder {
self.add_bcc(address);
self
}
/// Adds a `Bcc` header and stores the recipient address
pub fn add_bcc<A: IntoMailbox>(&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<A: IntoMailbox>(mut self, address: A) -> EmailBuilder {
self.add_reply_to(address);
self
}
/// Adds a `Reply-To` header
pub fn add_reply_to<A: IntoMailbox>(&mut self, address: A) {
let mailbox = address.into_mailbox();
self.reply_to_header.push(Address::Mailbox(mailbox));
}
/// Adds a `Sender` header
pub fn sender<A: IntoMailbox>(mut self, address: A) -> EmailBuilder {
self.set_sender(address);
self
}
/// Adds a `Sender` header
pub fn set_sender<A: IntoMailbox>(&mut self, address: A) {
let mailbox = address.into_mailbox();
self.sender_header = Some(mailbox);
}
/// Adds a `Subject` header
pub fn subject<S: Into<String>>(mut self, subject: S) -> EmailBuilder {
self.set_subject(subject);
self
}
/// Adds a `Subject` header
pub fn set_subject<S: Into<String>>(&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<S: Into<String>>(mut self, body: S) -> EmailBuilder {
self.set_text(body);
self
}
/// Sets the email body to plain text content
pub fn set_text<S: Into<String>>(&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<S: Into<String>>(mut self, body: S) -> EmailBuilder {
self.set_html(body);
self
}
/// Sets the email body to HTML content
pub fn set_html<S: Into<String>>(&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<S: Into<String>, T: Into<String>>(
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<S: Into<String>, T: Into<String>>(
&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<Email, Error> {
// 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<Address> 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<EmailAddress> {
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<String>;
/// To addresses
fn to_addresses(&self) -> Vec<String>;
/// Cc addresses
fn cc_addresses(&self) -> Vec<String>;
/// Bcc addresses
fn bcc_addresses(&self) -> Vec<String>;
/// 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<String>;
/// 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: <user@localhost>\r\nFrom: \
<user@localhost>\r\nCc: \"Alias\" <cc@localhost>\r\nReply-To: \
<reply@localhost>\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: \
<dieter@example.com>\r\nTo: <anna@example.com>\r\nFrom: \
<dieter@example.com>, <joachim@example.com>\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: \
<sender@localhost>\r\nTo: <user@localhost>\r\nFrom: \
<user@localhost>\r\nCc: \"Alias\" <cc@localhost>\r\nReply-To: \
<reply@localhost>\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);
}
}