use crate::email::Email;
use sova_core::Result;
#[derive(Clone, Debug, Default)]
pub struct Envelope {
pub subject: String,
pub from: Option<String>,
pub cc: Vec<String>,
pub bcc: Vec<String>,
}
impl Envelope {
pub fn new(subject: impl Into<String>) -> Self {
Self {
subject: subject.into(),
..Default::default()
}
}
pub fn from(mut self, addr: impl Into<String>) -> Self {
self.from = Some(addr.into());
self
}
pub fn cc(mut self, addr: impl Into<String>) -> Self {
self.cc.push(addr.into());
self
}
pub fn bcc(mut self, addr: impl Into<String>) -> Self {
self.bcc.push(addr.into());
self
}
}
#[derive(Clone)]
enum ContentInner {
Html {
html: String,
text: Option<String>,
},
Text(String),
#[cfg(feature = "templates")]
View {
name: String,
ctx: serde_json::Value,
text: Option<String>,
},
#[cfg(feature = "markdown")]
Markdown {
md: String,
with_text: bool,
},
#[cfg(all(feature = "templates", feature = "markdown"))]
MarkdownView {
name: String,
ctx: serde_json::Value,
with_text: bool,
},
}
#[derive(Clone)]
pub struct Content {
inner: ContentInner,
}
impl Content {
pub fn html(html: impl Into<String>) -> Self {
Self {
inner: ContentInner::Html {
html: html.into(),
text: None,
},
}
}
pub fn html_with_text(html: impl Into<String>, text: impl Into<String>) -> Self {
Self {
inner: ContentInner::Html {
html: html.into(),
text: Some(text.into()),
},
}
}
pub fn text(text: impl Into<String>) -> Self {
Self {
inner: ContentInner::Text(text.into()),
}
}
#[cfg(feature = "templates")]
pub fn view<T: serde::Serialize>(name: impl Into<String>, ctx: T) -> Self {
let ctx = serde_json::to_value(ctx).unwrap_or(serde_json::Value::Null);
Self {
inner: ContentInner::View {
name: name.into(),
ctx,
text: None,
},
}
}
#[cfg(feature = "templates")]
pub fn view_with_text<T: serde::Serialize>(
name: impl Into<String>,
ctx: T,
text: impl Into<String>,
) -> Self {
let ctx = serde_json::to_value(ctx).unwrap_or(serde_json::Value::Null);
Self {
inner: ContentInner::View {
name: name.into(),
ctx,
text: Some(text.into()),
},
}
}
#[cfg(feature = "markdown")]
pub fn markdown(md: impl Into<String>) -> Self {
Self {
inner: ContentInner::Markdown {
md: md.into(),
with_text: true,
},
}
}
#[cfg(all(feature = "templates", feature = "markdown"))]
pub fn markdown_view<T: serde::Serialize>(name: impl Into<String>, ctx: T) -> Self {
let ctx = serde_json::to_value(ctx).unwrap_or(serde_json::Value::Null);
Self {
inner: ContentInner::MarkdownView {
name: name.into(),
ctx,
with_text: true,
},
}
}
pub(crate) fn apply(self, mut email: Email) -> Email {
match self.inner {
ContentInner::Html { html, text } => {
email = email.html(html);
if let Some(t) = text {
email = email.text(t);
}
email
}
ContentInner::Text(text) => email.text(text),
#[cfg(feature = "templates")]
ContentInner::View { name, ctx, text } => {
email = email.view(name, ctx);
if let Some(t) = text {
email = email.text(t);
}
email
}
#[cfg(feature = "markdown")]
ContentInner::Markdown { md, with_text } => {
email = email.markdown(md);
if !with_text {
}
let _ = with_text;
email
}
#[cfg(all(feature = "templates", feature = "markdown"))]
ContentInner::MarkdownView {
name,
ctx,
with_text,
} => {
let _ = with_text;
email.markdown_view(name, ctx)
}
}
}
}
pub trait Mailable: Send + Sync {
fn envelope(&self) -> Envelope;
fn content(&self) -> Content;
fn build(&self, mut email: Email) -> Email {
let env = self.envelope();
email = email.subject(env.subject);
if let Some(from) = env.from {
email = email.from(from);
}
for addr in env.cc {
email = email.cc(addr);
}
for addr in env.bcc {
email = email.bcc(addr);
}
self.content().apply(email)
}
}
impl Email {
pub async fn send_mail<M: Mailable>(self, mail: M) -> Result<()> {
mail.build(self).send().await
}
}
impl crate::client::MailClient {
pub async fn send_mail<M: Mailable>(
&self,
to: impl Into<String>,
mail: M,
) -> Result<()> {
self.compose().to(to).send_mail(mail).await
}
}