1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6use crate::address::{Address, ToAddress};
7use crate::attachment::Attachment;
8
9#[derive(Debug, Clone, Default, Serialize, Deserialize)]
35pub struct Email {
36 pub from: Option<Address>,
38 pub to: Vec<Address>,
40 pub cc: Vec<Address>,
42 pub bcc: Vec<Address>,
44 pub reply_to: Vec<Address>,
46 pub subject: String,
48 pub text_body: Option<String>,
50 pub html_body: Option<String>,
52 pub attachments: Vec<Attachment>,
54 pub headers: HashMap<String, String>,
56 pub assigns: HashMap<String, serde_json::Value>,
58 pub private: HashMap<String, serde_json::Value>,
60 pub provider_options: HashMap<String, serde_json::Value>,
62}
63
64impl Email {
65 pub fn new() -> Self {
67 Self::default()
68 }
69
70 pub fn from(mut self, addr: impl ToAddress) -> Self {
77 self.from = Some(addr.to_address());
78 self
79 }
80
81 pub fn to(mut self, addr: impl ToAddress) -> Self {
86 self.to.push(addr.to_address());
87 self
88 }
89
90 pub fn put_to(mut self, addrs: Vec<Address>) -> Self {
92 self.to = addrs;
93 self
94 }
95
96 pub fn cc(mut self, addr: impl ToAddress) -> Self {
99 self.cc.push(addr.to_address());
100 self
101 }
102
103 pub fn put_cc(mut self, addrs: Vec<Address>) -> Self {
105 self.cc = addrs;
106 self
107 }
108
109 pub fn bcc(mut self, addr: impl ToAddress) -> Self {
112 self.bcc.push(addr.to_address());
113 self
114 }
115
116 pub fn put_bcc(mut self, addrs: Vec<Address>) -> Self {
118 self.bcc = addrs;
119 self
120 }
121
122 pub fn reply_to(mut self, addr: impl ToAddress) -> Self {
127 self.reply_to.push(addr.to_address());
128 self
129 }
130
131 pub fn put_reply_to(mut self, addrs: Vec<Address>) -> Self {
133 self.reply_to = addrs;
134 self
135 }
136
137 pub fn subject(mut self, subject: impl Into<String>) -> Self {
139 self.subject = subject.into();
140 self
141 }
142
143 pub fn text_body(mut self, body: impl Into<String>) -> Self {
145 self.text_body = Some(body.into());
146 self
147 }
148
149 pub fn html_body(mut self, body: impl Into<String>) -> Self {
151 self.html_body = Some(body.into());
152 self
153 }
154
155 pub fn attachment(mut self, attachment: Attachment) -> Self {
157 self.attachments.push(attachment);
158 self
159 }
160
161 pub fn header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
163 self.headers.insert(name.into(), value.into());
164 self
165 }
166
167 pub fn provider_option(
180 mut self,
181 key: impl Into<String>,
182 value: impl Into<serde_json::Value>,
183 ) -> Self {
184 self.provider_options.insert(key.into(), value.into());
185 self
186 }
187
188 pub fn assign(mut self, key: impl Into<String>, value: impl Into<serde_json::Value>) -> Self {
198 self.assigns.insert(key.into(), value.into());
199 self
200 }
201
202 pub fn put_private(
214 mut self,
215 key: impl Into<String>,
216 value: impl Into<serde_json::Value>,
217 ) -> Self {
218 self.private.insert(key.into(), value.into());
219 self
220 }
221
222 pub fn is_valid(&self) -> bool {
224 self.from.is_some() && !self.to.is_empty()
225 }
226
227 pub fn all_recipients(&self) -> Vec<&Address> {
229 self.to
230 .iter()
231 .chain(self.cc.iter())
232 .chain(self.bcc.iter())
233 .collect()
234 }
235
236 pub fn has_attachments(&self) -> bool {
238 !self.attachments.is_empty()
239 }
240
241 pub fn inline_attachments(&self) -> Vec<&Attachment> {
243 self.attachments.iter().filter(|a| a.is_inline()).collect()
244 }
245
246 pub fn regular_attachments(&self) -> Vec<&Attachment> {
248 self.attachments.iter().filter(|a| !a.is_inline()).collect()
249 }
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255
256 #[test]
257 fn test_builder() {
258 let email = Email::new()
259 .from("sender@example.com")
260 .to("recipient@example.com")
261 .subject("Test")
262 .text_body("Hello");
263
264 assert_eq!(email.from.unwrap().email, "sender@example.com");
265 assert_eq!(email.to.len(), 1);
266 assert_eq!(email.to[0].email, "recipient@example.com");
267 assert_eq!(email.subject, "Test");
268 assert_eq!(email.text_body, Some("Hello".to_string()));
269 }
270
271 #[test]
272 fn test_multiple_recipients() {
273 let email = Email::new()
274 .to("one@example.com")
275 .to("two@example.com")
276 .cc("cc@example.com")
277 .bcc("bcc@example.com");
278
279 assert_eq!(email.to.len(), 2);
280 assert_eq!(email.cc.len(), 1);
281 assert_eq!(email.bcc.len(), 1);
282 assert_eq!(email.all_recipients().len(), 4);
283 }
284
285 #[test]
286 fn test_with_name() {
287 let email = Email::new().from(("Alice", "alice@example.com"));
288
289 let from = email.from.unwrap();
290 assert_eq!(from.email, "alice@example.com");
291 assert_eq!(from.name, Some("Alice".to_string()));
292 }
293
294 #[test]
295 fn test_is_valid() {
296 let invalid = Email::new().to("recipient@example.com");
297 assert!(!invalid.is_valid());
298
299 let valid = Email::new()
300 .from("sender@example.com")
301 .to("recipient@example.com");
302 assert!(valid.is_valid());
303 }
304
305 #[test]
306 fn test_headers() {
307 let email = Email::new()
308 .header("X-Custom", "value")
309 .header("X-Priority", "1");
310
311 assert_eq!(email.headers.get("X-Custom"), Some(&"value".to_string()));
312 assert_eq!(email.headers.get("X-Priority"), Some(&"1".to_string()));
313 }
314
315 #[test]
316 fn test_provider_options() {
317 let email = Email::new().provider_option("template_id", "welcome-email");
318
319 assert_eq!(
320 email.provider_options.get("template_id"),
321 Some(&serde_json::json!("welcome-email"))
322 );
323 }
324
325 #[test]
326 fn test_to_address_trait() {
327 struct User {
328 name: String,
329 email: String,
330 }
331
332 impl ToAddress for User {
333 fn to_address(&self) -> Address {
334 Address::with_name(&self.name, &self.email)
335 }
336 }
337
338 let user = User {
339 name: "Alice".to_string(),
340 email: "alice@example.com".to_string(),
341 };
342
343 let email = Email::new().to(&user);
344 assert_eq!(email.to[0].email, "alice@example.com");
345 assert_eq!(email.to[0].name, Some("Alice".to_string()));
346 }
347
348 #[test]
349 fn test_to_address_trait_all_methods() {
350 struct Contact {
351 name: String,
352 email: String,
353 }
354
355 impl ToAddress for Contact {
356 fn to_address(&self) -> Address {
357 Address::with_name(&self.name, &self.email)
358 }
359 }
360
361 let sender = Contact {
362 name: "Sender".to_string(),
363 email: "sender@example.com".to_string(),
364 };
365 let recipient = Contact {
366 name: "Recipient".to_string(),
367 email: "recipient@example.com".to_string(),
368 };
369 let cc_contact = Contact {
370 name: "CC".to_string(),
371 email: "cc@example.com".to_string(),
372 };
373 let bcc_contact = Contact {
374 name: "BCC".to_string(),
375 email: "bcc@example.com".to_string(),
376 };
377 let reply_contact = Contact {
378 name: "Reply".to_string(),
379 email: "reply@example.com".to_string(),
380 };
381
382 let email = Email::new()
383 .from(&sender)
384 .to(&recipient)
385 .cc(&cc_contact)
386 .bcc(&bcc_contact)
387 .reply_to(&reply_contact);
388
389 assert_eq!(email.from.as_ref().unwrap().email, "sender@example.com");
390 assert_eq!(email.to[0].email, "recipient@example.com");
391 assert_eq!(email.cc[0].email, "cc@example.com");
392 assert_eq!(email.bcc[0].email, "bcc@example.com");
393 assert_eq!(email.reply_to[0].email, "reply@example.com");
394 }
395}