use std::collections::BTreeMap;
use base64::Engine;
use serde::{Serialize, Serializer};
use serde_json::Value;
#[derive(Clone, Debug, Serialize)]
#[serde(untagged)]
pub enum ReplyTo {
One(String),
Many(Vec<String>),
}
#[derive(Clone, Debug, Default, Serialize)]
pub struct Tracking {
#[serde(skip_serializing_if = "Option::is_none")]
pub opens: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub clicks: Option<bool>,
}
impl Tracking {
pub fn new() -> Self {
Self::default()
}
pub fn opens(mut self, enabled: bool) -> Self {
self.opens = Some(enabled);
self
}
pub fn clicks(mut self, enabled: bool) -> Self {
self.clicks = Some(enabled);
self
}
}
#[derive(Clone, Copy, Debug, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum UnsubscribeMode {
Generate,
None,
}
#[derive(Clone, Debug, Serialize)]
pub struct Unsubscribe {
pub mode: UnsubscribeMode,
#[serde(skip_serializing_if = "Option::is_none")]
pub display_name: Option<String>,
}
impl Unsubscribe {
pub fn generate() -> Self {
Self {
mode: UnsubscribeMode::Generate,
display_name: None,
}
}
pub fn none() -> Self {
Self {
mode: UnsubscribeMode::None,
display_name: None,
}
}
pub fn display_name(mut self, name: impl Into<String>) -> Self {
self.display_name = Some(name.into());
self
}
}
#[derive(Clone, Debug, Serialize)]
pub struct Attachment {
pub filename: String,
#[serde(serialize_with = "serialize_base64")]
pub content: Vec<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub content_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub content_id: Option<String>,
}
impl Attachment {
pub fn new(filename: impl Into<String>, content: impl Into<Vec<u8>>) -> Self {
Self {
filename: filename.into(),
content: content.into(),
content_type: None,
content_id: None,
}
}
pub fn content_type(mut self, content_type: impl Into<String>) -> Self {
self.content_type = Some(content_type.into());
self
}
pub fn content_id(mut self, content_id: impl Into<String>) -> Self {
self.content_id = Some(content_id.into());
self
}
}
fn serialize_base64<S: Serializer>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&base64::engine::general_purpose::STANDARD.encode(bytes))
}
#[derive(Clone, Debug, Serialize)]
pub struct SendEmail {
#[serde(skip_serializing_if = "Option::is_none")]
pub from: Option<String>,
pub to: Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub cc: Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub bcc: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reply_to: Option<ReplyTo>,
#[serde(skip_serializing_if = "Option::is_none")]
pub subject: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub html: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub template_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub headers: Option<BTreeMap<String, String>>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub attachments: Vec<Attachment>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub tags: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tracking: Option<Tracking>,
#[serde(skip_serializing_if = "Option::is_none")]
pub variables: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub campaign: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub topic: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub unsubscribe: Option<Unsubscribe>,
}
impl SendEmail {
pub fn new(from: impl Into<String>, to: impl IntoIterator<Item = impl Into<String>>) -> Self {
Self::recipients(to).from(from)
}
pub fn to(to: impl IntoIterator<Item = impl Into<String>>) -> Self {
Self::recipients(to)
}
fn recipients(to: impl IntoIterator<Item = impl Into<String>>) -> Self {
Self {
from: None,
to: to.into_iter().map(Into::into).collect(),
cc: Vec::new(),
bcc: Vec::new(),
reply_to: None,
subject: None,
text: None,
html: None,
template_id: None,
headers: None,
attachments: Vec::new(),
tags: Vec::new(),
tracking: None,
variables: None,
campaign: None,
topic: None,
unsubscribe: None,
}
}
pub fn from(mut self, from: impl Into<String>) -> Self {
self.from = Some(from.into());
self
}
pub fn cc(mut self, addrs: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.cc.extend(addrs.into_iter().map(Into::into));
self
}
pub fn bcc(mut self, addrs: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.bcc.extend(addrs.into_iter().map(Into::into));
self
}
pub fn reply_to(mut self, addr: impl Into<String>) -> Self {
self.reply_to = Some(ReplyTo::One(addr.into()));
self
}
pub fn reply_to_many(mut self, addrs: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.reply_to = Some(ReplyTo::Many(addrs.into_iter().map(Into::into).collect()));
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 template_id(mut self, template_id: impl Into<String>) -> Self {
self.template_id = Some(template_id.into());
self
}
pub fn header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
self.headers
.get_or_insert_with(BTreeMap::new)
.insert(name.into(), value.into());
self
}
pub fn headers(
mut self,
headers: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
) -> Self {
let map = self.headers.get_or_insert_with(BTreeMap::new);
for (name, value) in headers {
map.insert(name.into(), value.into());
}
self
}
pub fn attachment(mut self, attachment: Attachment) -> Self {
self.attachments.push(attachment);
self
}
pub fn attachments(mut self, attachments: impl IntoIterator<Item = Attachment>) -> Self {
self.attachments.extend(attachments);
self
}
pub fn tag(mut self, tag: impl Into<String>) -> Self {
self.tags.push(tag.into());
self
}
pub fn tags(mut self, tags: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.tags.extend(tags.into_iter().map(Into::into));
self
}
pub fn tracking(mut self, tracking: Tracking) -> Self {
self.tracking = Some(tracking);
self
}
pub fn variables<T: Serialize>(mut self, variables: T) -> Self {
self.variables = serde_json::to_value(variables).ok();
self
}
pub fn campaign(mut self, campaign: impl Into<String>) -> Self {
self.campaign = Some(campaign.into());
self
}
pub fn topic(mut self, topic: impl Into<String>) -> Self {
self.topic = Some(topic.into());
self
}
pub fn unsubscribe(mut self, unsubscribe: Unsubscribe) -> Self {
self.unsubscribe = Some(unsubscribe);
self
}
}
#[derive(Clone, Debug, Serialize)]
pub struct BatchEmail {
#[serde(skip_serializing_if = "Option::is_none")]
pub defaults: Option<Value>,
pub emails: Vec<SendEmail>,
}
impl BatchEmail {
pub fn new(emails: impl IntoIterator<Item = SendEmail>) -> Self {
Self {
defaults: None,
emails: emails.into_iter().collect(),
}
}
pub fn defaults<T: Serialize>(mut self, defaults: T) -> Self {
self.defaults = serde_json::to_value(defaults).ok();
self
}
pub fn push(mut self, email: SendEmail) -> Self {
self.emails.push(email);
self
}
}