1use std::borrow::Cow;
2
3use lettre::{
4 Address, AsyncSmtpTransport, AsyncTransport, Message, message::Mailbox,
5 transport::smtp::response::Severity,
6};
7
8use crate::{EmailEnvelopeDetails, MailListError, MailListResult};
9
10#[derive(Debug)]
11pub struct Smtps {
12 pub(crate) domain: Cow<'static, str>,
13 pub(crate) from: Mailbox,
14 pub(crate) reply_to: Option<Mailbox>,
15 pub(crate) ehlo_name: Option<Cow<'static, str>>,
16 #[cfg(feature = "tokio-runtime")]
17 pub(crate) mailer: AsyncSmtpTransport<lettre::Tokio1Executor>,
18}
19
20impl Smtps {
21 pub fn set_reply_to(
22 &mut self,
23 reply_to_name: &str,
24 reply_to_address: &str,
25 ) -> MailListResult<&mut Self> {
26 self.reply_to.replace(Mailbox::new(
27 Some(reply_to_name.to_string()),
28 reply_to_address
29 .parse::<Address>()
30 .map_err(|error| MailListError::Mailer(error.to_string()))?,
31 ));
32
33 Ok(self)
34 }
35
36 pub fn domain(&self) -> &str {
37 self.domain.as_ref()
38 }
39
40 pub fn ehlo_name(&self) -> Option<&Cow<'_, str>> {
41 self.ehlo_name.as_ref()
42 }
43
44 pub fn from(&self) -> &Mailbox {
45 &self.from
46 }
47
48 pub fn reply_to(&self) -> Option<&Mailbox> {
49 self.reply_to.as_ref()
50 }
51
52 #[cfg(feature = "tokio-runtime")]
53 pub fn mailer(&self) -> &AsyncSmtpTransport<lettre::Tokio1Executor> {
54 &self.mailer
55 }
56
57 pub async fn send(&self, message: &EmailEnvelopeDetails) -> MailListResult<()> {
58 let email = Message::builder().from(self.from().clone());
59
60 let email = if let Some(reply_to) = self.reply_to() {
61 email.reply_to(reply_to.clone())
62 } else {
63 email
64 };
65
66 let email = email
67 .to(message
68 .to
69 .parse::<Mailbox>()
70 .map_err(|error| MailListError::Mailer(error.to_string()))?)
71 .subject(message.subject.as_str())
72 .header(lettre::message::header::ContentType::TEXT_HTML)
73 .body(message.body.to_string())
74 .map_err(|error| MailListError::Mailer(error.to_string()))?;
75
76 let response = self
77 .mailer
78 .send(email)
79 .await
80 .map_err(|error| MailListError::MailDelivery(error.to_string()))?;
81
82 if response.code().severity == Severity::PositiveCompletion {
83 Ok(())
84 } else {
85 Err(MailListError::Smtps(
86 response.message().map(|msg| msg.to_string()).collect(),
87 ))
88 }
89 }
90
91 pub async fn test_connection(&self) -> MailListResult<bool> {
92 self.mailer
93 .test_connection()
94 .await
95 .map_err(|error| MailListError::Mailer(error.to_string()))
96 }
97}