use crate::{Attachment, Result, MailError};
use lettre::Address;
#[derive(Debug, Clone)]
pub struct Message {
pub(crate) from: Option<String>,
pub(crate) to: Vec<String>,
pub(crate) cc: Vec<String>,
pub(crate) bcc: Vec<String>,
pub(crate) reply_to: Option<String>,
pub(crate) subject: Option<String>,
pub(crate) text: Option<String>,
pub(crate) html: Option<String>,
pub(crate) attachments: Vec<Attachment>,
}
impl Message {
pub fn new() -> Self {
Self {
from: None,
to: Vec::new(),
cc: Vec::new(),
bcc: Vec::new(),
reply_to: None,
subject: None,
text: None,
html: None,
attachments: Vec::new(),
}
}
pub fn from(mut self, from: impl Into<String>) -> Self {
self.from = Some(from.into());
self
}
pub fn to(mut self, to: impl Into<String>) -> Self {
self.to.push(to.into());
self
}
pub fn cc(mut self, cc: impl Into<String>) -> Self {
self.cc.push(cc.into());
self
}
pub fn bcc(mut self, bcc: impl Into<String>) -> Self {
self.bcc.push(bcc.into());
self
}
pub fn reply_to(mut self, reply_to: impl Into<String>) -> Self {
self.reply_to = Some(reply_to.into());
self
}
pub fn subject(mut self, subject: impl Into<String>) -> Self {
self.subject = Some(subject.into());
self
}
pub fn text(mut self, text: impl Into<String>) -> Self {
self.text = Some(text.into());
self
}
pub fn html(mut self, html: impl Into<String>) -> Self {
self.html = Some(html.into());
self
}
pub fn attach(mut self, attachment: Attachment) -> Self {
self.attachments.push(attachment);
self
}
pub(crate) fn validate(&self) -> Result<()> {
if self.from.is_none() {
return Err(MailError::MissingField("from".to_string()));
}
if self.to.is_empty() {
return Err(MailError::MissingField("to".to_string()));
}
if self.subject.is_none() {
return Err(MailError::MissingField("subject".to_string()));
}
if self.text.is_none() && self.html.is_none() {
return Err(MailError::MissingField("text or html".to_string()));
}
if let Some(from) = &self.from {
from.parse::<Address>()
.map_err(|_| MailError::InvalidAddress(format!("from: {from}")))?;
}
for to in &self.to {
to.parse::<Address>()
.map_err(|_| MailError::InvalidAddress(format!("to: {to}")))?;
}
for cc in &self.cc {
cc.parse::<Address>()
.map_err(|_| MailError::InvalidAddress(format!("cc: {cc}")))?;
}
for bcc in &self.bcc {
bcc.parse::<Address>()
.map_err(|_| MailError::InvalidAddress(format!("bcc: {bcc}")))?;
}
if let Some(reply_to) = &self.reply_to {
reply_to
.parse::<Address>()
.map_err(|_| MailError::InvalidAddress(format!("reply_to: {reply_to}")))?;
}
Ok(())
}
}
impl Default for Message {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::Message;
#[test]
fn validate_rejects_bad_recipient() {
let msg = Message::new()
.from("sender@example.com")
.to("bad-address")
.subject("hello")
.text("body");
assert!(msg.validate().is_err());
}
}