1use crate::email::Email;
4use sova_core::Result;
5
6#[derive(Clone, Debug, Default)]
8pub struct Envelope {
9 pub subject: String,
10 pub from: Option<String>,
11 pub cc: Vec<String>,
12 pub bcc: Vec<String>,
13}
14
15impl Envelope {
16 pub fn new(subject: impl Into<String>) -> Self {
17 Self {
18 subject: subject.into(),
19 ..Default::default()
20 }
21 }
22
23 pub fn from(mut self, addr: impl Into<String>) -> Self {
24 self.from = Some(addr.into());
25 self
26 }
27
28 pub fn cc(mut self, addr: impl Into<String>) -> Self {
29 self.cc.push(addr.into());
30 self
31 }
32
33 pub fn bcc(mut self, addr: impl Into<String>) -> Self {
34 self.bcc.push(addr.into());
35 self
36 }
37}
38
39#[derive(Clone)]
40enum ContentInner {
41 Html {
42 html: String,
43 text: Option<String>,
44 },
45 Text(String),
46 #[cfg(feature = "templates")]
47 View {
48 name: String,
49 ctx: serde_json::Value,
50 text: Option<String>,
51 },
52 #[cfg(feature = "markdown")]
53 Markdown {
54 md: String,
55 with_text: bool,
57 },
58 #[cfg(all(feature = "templates", feature = "markdown"))]
59 MarkdownView {
60 name: String,
61 ctx: serde_json::Value,
62 with_text: bool,
63 },
64}
65
66#[derive(Clone)]
68pub struct Content {
69 inner: ContentInner,
70}
71
72impl Content {
73 pub fn html(html: impl Into<String>) -> Self {
74 Self {
75 inner: ContentInner::Html {
76 html: html.into(),
77 text: None,
78 },
79 }
80 }
81
82 pub fn html_with_text(html: impl Into<String>, text: impl Into<String>) -> Self {
83 Self {
84 inner: ContentInner::Html {
85 html: html.into(),
86 text: Some(text.into()),
87 },
88 }
89 }
90
91 pub fn text(text: impl Into<String>) -> Self {
92 Self {
93 inner: ContentInner::Text(text.into()),
94 }
95 }
96
97 #[cfg(feature = "templates")]
99 pub fn view<T: serde::Serialize>(name: impl Into<String>, ctx: T) -> Self {
100 let ctx = serde_json::to_value(ctx).unwrap_or(serde_json::Value::Null);
101 Self {
102 inner: ContentInner::View {
103 name: name.into(),
104 ctx,
105 text: None,
106 },
107 }
108 }
109
110 #[cfg(feature = "templates")]
112 pub fn view_with_text<T: serde::Serialize>(
113 name: impl Into<String>,
114 ctx: T,
115 text: impl Into<String>,
116 ) -> Self {
117 let ctx = serde_json::to_value(ctx).unwrap_or(serde_json::Value::Null);
118 Self {
119 inner: ContentInner::View {
120 name: name.into(),
121 ctx,
122 text: Some(text.into()),
123 },
124 }
125 }
126
127 #[cfg(feature = "markdown")]
129 pub fn markdown(md: impl Into<String>) -> Self {
130 Self {
131 inner: ContentInner::Markdown {
132 md: md.into(),
133 with_text: true,
134 },
135 }
136 }
137
138 #[cfg(all(feature = "templates", feature = "markdown"))]
140 pub fn markdown_view<T: serde::Serialize>(name: impl Into<String>, ctx: T) -> Self {
141 let ctx = serde_json::to_value(ctx).unwrap_or(serde_json::Value::Null);
142 Self {
143 inner: ContentInner::MarkdownView {
144 name: name.into(),
145 ctx,
146 with_text: true,
147 },
148 }
149 }
150
151 pub(crate) fn apply(self, mut email: Email) -> Email {
152 match self.inner {
153 ContentInner::Html { html, text } => {
154 email = email.html(html);
155 if let Some(t) = text {
156 email = email.text(t);
157 }
158 email
159 }
160 ContentInner::Text(text) => email.text(text),
161 #[cfg(feature = "templates")]
162 ContentInner::View { name, ctx, text } => {
163 email = email.view(name, ctx);
164 if let Some(t) = text {
165 email = email.text(t);
166 }
167 email
168 }
169 #[cfg(feature = "markdown")]
170 ContentInner::Markdown { md, with_text } => {
171 email = email.markdown(md);
172 if !with_text {
173 }
175 let _ = with_text;
176 email
177 }
178 #[cfg(all(feature = "templates", feature = "markdown"))]
179 ContentInner::MarkdownView {
180 name,
181 ctx,
182 with_text,
183 } => {
184 let _ = with_text;
185 email.markdown_view(name, ctx)
186 }
187 }
188 }
189}
190
191pub trait Mailable: Send + Sync {
193 fn envelope(&self) -> Envelope;
194 fn content(&self) -> Content;
195
196 fn build(&self, mut email: Email) -> Email {
198 let env = self.envelope();
199 email = email.subject(env.subject);
200 if let Some(from) = env.from {
201 email = email.from(from);
202 }
203 for addr in env.cc {
204 email = email.cc(addr);
205 }
206 for addr in env.bcc {
207 email = email.bcc(addr);
208 }
209 self.content().apply(email)
210 }
211}
212
213impl Email {
214 pub async fn send_mail<M: Mailable>(self, mail: M) -> Result<()> {
216 mail.build(self).send().await
217 }
218}
219
220impl crate::client::MailClient {
221 pub async fn send_mail<M: Mailable>(
223 &self,
224 to: impl Into<String>,
225 mail: M,
226 ) -> Result<()> {
227 self.compose().to(to).send_mail(mail).await
228 }
229}