use std::fmt;
use lettre::message::header::ContentType;
use lettre::message::{Attachment, Mailbox, Message, MessageBuilder, MultiPart, SinglePart};
use crate::mail::error::EmailError;
#[derive(Clone)]
pub struct Email {
builder: MessageBuilder,
}
impl Email {
#[must_use]
pub fn builder() -> Self {
Self {
builder: Message::builder(),
}
}
#[must_use]
pub fn from_builder(builder: MessageBuilder) -> Self {
Self { builder }
}
#[must_use]
pub fn from(mut self, mailbox: Mailbox) -> Self {
self.builder = self.builder.from(mailbox);
self
}
#[must_use]
pub fn reply_to(mut self, mailbox: Mailbox) -> Self {
self.builder = self.builder.reply_to(mailbox);
self
}
#[must_use]
pub fn to(mut self, mailbox: Mailbox) -> Self {
self.builder = self.builder.to(mailbox);
self
}
#[must_use]
pub fn cc(mut self, mailbox: Mailbox) -> Self {
self.builder = self.builder.cc(mailbox);
self
}
#[must_use]
pub fn bcc(mut self, mailbox: Mailbox) -> Self {
self.builder = self.builder.bcc(mailbox);
self
}
#[must_use]
pub fn subject(mut self, subject: impl Into<String>) -> Self {
self.builder = self.builder.subject(subject);
self
}
pub fn plain(self, body: impl Into<String>) -> Result<Message, EmailError> {
let body: String = body.into();
self.builder
.header(ContentType::TEXT_PLAIN)
.body(body)
.map_err(EmailError::build)
}
pub fn html(self, body: impl Into<String>) -> Result<Message, EmailError> {
let html_part = SinglePart::builder()
.header(ContentType::TEXT_HTML)
.body(body.into());
self.builder
.singlepart(html_part)
.map_err(EmailError::build)
}
pub fn alternative(
self,
plain: impl Into<String>,
html: impl Into<String>,
) -> Result<Message, EmailError> {
let multipart = MultiPart::alternative_plain_html(plain.into(), html.into());
self.builder.multipart(multipart).map_err(EmailError::build)
}
pub fn mixed(
self,
body: MultiPart,
attachments: Vec<EmailAttachment>,
) -> Result<Message, EmailError> {
let mut mixed = MultiPart::mixed().multipart(body);
for attachment in attachments {
mixed = mixed.singlepart(attachment.into_lettre());
}
self.builder.multipart(mixed).map_err(EmailError::build)
}
pub fn plain_with_attachments(
self,
body: impl Into<String>,
attachments: Vec<EmailAttachment>,
) -> Result<Message, EmailError> {
let body = MultiPart::alternative_plain_html(body.into(), String::new());
self.mixed(body, attachments)
}
pub fn alternative_with_attachments(
self,
plain: impl Into<String>,
html: impl Into<String>,
attachments: Vec<EmailAttachment>,
) -> Result<Message, EmailError> {
let body = MultiPart::alternative_plain_html(plain.into(), html.into());
self.mixed(body, attachments)
}
#[must_use]
pub fn into_builder(self) -> MessageBuilder {
self.builder
}
}
pub struct EmailAttachment {
filename: String,
body: Vec<u8>,
content_type: ContentType,
}
impl EmailAttachment {
pub fn new(
filename: impl Into<String>,
body: Vec<u8>,
content_type: &str,
) -> Result<Self, EmailError> {
let content_type = ContentType::parse(content_type).map_err(EmailError::content_type)?;
Ok(Self {
filename: filename.into(),
body,
content_type,
})
}
pub(crate) fn into_lettre(self) -> SinglePart {
Attachment::new(self.filename).body(self.body, self.content_type)
}
}
impl fmt::Debug for EmailAttachment {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("EmailAttachment")
.field("filename", &self.filename)
.field("content_type", &self.content_type)
.field("body_len", &self.body.len())
.finish_non_exhaustive()
}
}