use crate::error::Result;
use async_trait::async_trait;
#[derive(Debug, Clone)]
pub struct Email {
pub from: String,
pub to: Vec<String>,
pub cc: Vec<String>,
pub bcc: Vec<String>,
pub subject: String,
pub text: Option<String>,
pub html: Option<String>,
pub reply_to: Option<String>,
}
impl Email {
pub fn new(from: impl Into<String>, to: impl Into<String>, subject: impl Into<String>) -> Self {
Self {
from: from.into(),
to: vec![to.into()],
cc: Vec::new(),
bcc: Vec::new(),
subject: subject.into(),
text: None,
html: None,
reply_to: None,
}
}
pub fn to(mut self, recipient: impl Into<String>) -> Self {
self.to.push(recipient.into());
self
}
pub fn to_many(mut self, recipients: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.to.extend(recipients.into_iter().map(|r| r.into()));
self
}
pub fn cc(mut self, recipient: impl Into<String>) -> Self {
self.cc.push(recipient.into());
self
}
pub fn bcc(mut self, recipient: impl Into<String>) -> Self {
self.bcc.push(recipient.into());
self
}
pub fn text(mut self, body: impl Into<String>) -> Self {
self.text = Some(body.into());
self
}
pub fn html(mut self, body: impl Into<String>) -> Self {
self.html = Some(body.into());
self
}
pub fn reply_to(mut self, address: impl Into<String>) -> Self {
self.reply_to = Some(address.into());
self
}
pub fn validate(&self) -> Result<()> {
if self.from.is_empty() {
return Err(crate::error::TidewayError::bad_request(
"Email 'from' is required",
));
}
if self.to.is_empty() {
return Err(crate::error::TidewayError::bad_request(
"Email 'to' is required",
));
}
if self.subject.is_empty() {
return Err(crate::error::TidewayError::bad_request(
"Email 'subject' is required",
));
}
if self.text.is_none() && self.html.is_none() {
return Err(crate::error::TidewayError::bad_request(
"Email must have either 'text' or 'html' body",
));
}
Ok(())
}
}
#[async_trait]
pub trait Mailer: Send + Sync {
async fn send(&self, email: &Email) -> Result<()>;
fn is_healthy(&self) -> bool;
}