948 lines
28 KiB
Rust
948 lines
28 KiB
Rust
//! Lettre is a mailer written in Rust. lettre_email provides a simple email builder.
|
|
//!
|
|
|
|
#![doc(html_root_url = "https://docs.rs/lettre_email/0.8.2")]
|
|
#![deny(missing_docs, missing_debug_implementations, missing_copy_implementations, trivial_casts,
|
|
trivial_numeric_casts, unsafe_code, unstable_features, unused_import_braces,
|
|
unused_qualifications)]
|
|
|
|
extern crate base64;
|
|
extern crate email as email_format;
|
|
extern crate lettre;
|
|
extern crate mime;
|
|
extern crate time;
|
|
extern crate uuid;
|
|
|
|
pub mod error;
|
|
|
|
pub use email_format::{Address, Header, Mailbox, MimeMessage, MimeMultipartType};
|
|
use error::Error;
|
|
use lettre::{EmailAddress, Envelope, Error as EmailError, SendableEmail};
|
|
use mime::Mime;
|
|
use std::fs::File;
|
|
use std::io::Read;
|
|
use std::path::Path;
|
|
use time::{now, Tm};
|
|
use uuid::Uuid;
|
|
use std::str::FromStr;
|
|
|
|
/// 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 address 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);
|
|
}
|
|
|
|
/// Adds an attachment to the message
|
|
pub fn attachment<S: Into<String>>(mut self, path: S) -> SimpleEmail {
|
|
self.add_attachment(path);
|
|
self
|
|
}
|
|
|
|
/// Adds an attachment to the message
|
|
pub fn add_attachment<S: Into<String>>(&mut self, path: S) {
|
|
self.attachments.push(path.into());
|
|
}
|
|
|
|
/// 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 representation
|
|
#[derive(PartialEq, Eq, Clone, Debug)]
|
|
pub struct Email {
|
|
/// Message
|
|
message: Vec<u8>,
|
|
/// Envelope
|
|
envelope: Envelope,
|
|
/// Message-ID
|
|
message_id: Uuid,
|
|
}
|
|
|
|
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 built `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;
|
|
}
|
|
|
|
/// Adds an attachment to the email
|
|
pub fn attachment(
|
|
mut self,
|
|
path: &Path,
|
|
filename: Option<&str>,
|
|
content_type: &Mime,
|
|
) -> Result<EmailBuilder, Error> {
|
|
self.set_attachment(path, filename, content_type)?;
|
|
Ok(self)
|
|
}
|
|
|
|
/// Adds an attachment to the email
|
|
/// If filename is not provided, the name of the file will be used.
|
|
pub fn set_attachment(
|
|
&mut self,
|
|
path: &Path,
|
|
filename: Option<&str>,
|
|
content_type: &Mime,
|
|
) -> Result<(), Error> {
|
|
let file = File::open(path);
|
|
let body = match file {
|
|
Ok(mut f) => {
|
|
let mut data = Vec::new();
|
|
let read = f.read_to_end(&mut data);
|
|
match read {
|
|
Ok(_) => data,
|
|
Err(e) => {
|
|
return Err(From::from(e));
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
return Err(From::from(e));
|
|
}
|
|
};
|
|
|
|
let actual_filename = match filename {
|
|
Some(name) => name,
|
|
None => match path.file_name() {
|
|
Some(name) => match name.to_str() {
|
|
Some(name) => name,
|
|
None => return Err(Error::CannotParseFilename),
|
|
},
|
|
None => return Err(Error::CannotParseFilename),
|
|
},
|
|
};
|
|
|
|
let encoded_body = base64::encode(&body);
|
|
let content = PartBuilder::new()
|
|
.body(encoded_body)
|
|
.header((
|
|
"Content-Disposition",
|
|
format!("attachment; filename=\"{}\"", actual_filename),
|
|
))
|
|
.header(("Content-Type", content_type.to_string()))
|
|
.header(("Content-Transfer-Encoding", "base64"))
|
|
.build();
|
|
|
|
self.set_message_type(MimeMultipartType::Mixed);
|
|
self.add_child(content);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// 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) {
|
|
let text = PartBuilder::new()
|
|
.body(body)
|
|
.header((
|
|
"Content-Type",
|
|
format!("{}", mime::TEXT_PLAIN_UTF_8).as_ref(),
|
|
))
|
|
.build();
|
|
self.add_child(text);
|
|
}
|
|
|
|
/// 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) {
|
|
let html = PartBuilder::new()
|
|
.body(body)
|
|
.header((
|
|
"Content-Type",
|
|
format!("{}", mime::TEXT_HTML_UTF_8).as_ref(),
|
|
))
|
|
.build();
|
|
self.add_child(html);
|
|
}
|
|
|
|
/// 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_UTF_8).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::builder();
|
|
// 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(EmailAddress::from_str(&m.address)?),
|
|
Address::Group(_, ref ms) => for m in ms.iter() {
|
|
e.add_to(EmailAddress::from_str(&m.address.clone())?);
|
|
},
|
|
}
|
|
}
|
|
e.set_from(EmailAddress::from_str(&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::Email(EmailError::MissingFrom)), // empty envelope sender
|
|
},
|
|
},
|
|
// if we don't have a from header
|
|
None => return Err(Error::Email(EmailError::MissingFrom)), // empty envelope sender
|
|
}
|
|
}
|
|
})?);
|
|
e.build()?
|
|
}
|
|
};
|
|
// 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::Email(EmailError::MissingFrom));
|
|
}
|
|
if !self.cc_header.is_empty() {
|
|
self.message
|
|
.add_header(Header::new_with_value("Cc".into(), self.cc_header).unwrap());
|
|
}
|
|
if !self.bcc_header.is_empty() {
|
|
self.message
|
|
.add_header(Header::new_with_value("Bcc".into(), self.bcc_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().as_string().into_bytes(),
|
|
envelope,
|
|
message_id,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl<'a> SendableEmail<'a, &'a [u8]> for Email {
|
|
fn envelope(&self) -> Envelope {
|
|
self.envelope.clone()
|
|
}
|
|
|
|
fn message_id(&self) -> String {
|
|
self.message_id.to_string()
|
|
}
|
|
|
|
fn message(&'a self) -> Box<&[u8]> {
|
|
Box::new(self.message.as_slice())
|
|
}
|
|
}
|
|
|
|
/// 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::EmailBuilder;
|
|
use lettre::{EmailAddress, SendableEmail};
|
|
use time::now;
|
|
|
|
#[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!("{}", String::from_utf8_lossy(email.message().as_ref())),
|
|
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"))
|
|
.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!(
|
|
format!("{}", String::from_utf8_lossy(email.message().as_ref())),
|
|
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\n\
|
|
Bcc: <bcc@localhost>\r\nReply-To: <reply@localhost>\r\n\
|
|
MIME-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.envelope().from().unwrap().to_string(),
|
|
"sender@localhost".to_string()
|
|
);
|
|
assert_eq!(
|
|
email.envelope().to(),
|
|
vec![
|
|
EmailAddress::new("user@localhost".to_string()).unwrap(),
|
|
EmailAddress::new("cc@localhost".to_string()).unwrap(),
|
|
EmailAddress::new("bcc@localhost".to_string()).unwrap(),
|
|
].as_slice()
|
|
);
|
|
}
|
|
|
|
}
|