mail_list/
transport_builder.rs1use std::borrow::Cow;
2
3use lettre::{AsyncSmtpTransport, message::Mailbox};
4use percent_encoding::percent_decode_str;
5
6use crate::{MailListError, MailListResult, Smtps};
7
8#[derive(Debug, Default)]
9pub struct SmtpsBuilder {
10 from: Cow<'static, str>,
11 reply_to: Option<Cow<'static, str>>,
12 hello_name: Option<Cow<'static, str>>,
13}
14
15impl SmtpsBuilder {
16 pub fn new() -> Self {
17 Self::default()
18 }
19
20 pub fn set_from(&mut self, from: &str) -> &mut Self {
21 self.from = from.to_string().into();
22
23 self
24 }
25 pub fn set_reply_to(&mut self, reply_to: &str) -> &mut Self {
26 self.reply_to.replace(reply_to.to_string().into());
27
28 self
29 }
30
31 pub fn set_hello_name(&mut self, hello_name: &str) -> &mut Self {
32 self.hello_name.replace(hello_name.to_string().into());
33
34 self
35 }
36
37 pub fn build(self, smtps_uri: &str) -> MailListResult<Smtps> {
38 use url::Url;
39
40 let url =
41 Url::parse(smtps_uri).or(Err(MailListError::Smtps("Invalid smtps URI".to_string())))?;
42
43 if url.scheme() != "smtps" {
44 return Err(MailListError::Smtps("Invalid SMTPs scheme".to_string()));
45 };
46 let domain = url
47 .host_str()
48 .ok_or(MailListError::Smtps(
49 "Domain not found in SMTPs".to_string(),
50 ))?
51 .to_string();
52 let domain = percent_decode_str(&domain)
53 .decode_utf8()
54 .or(Err(MailListError::Smtps(
55 "Domain is not valid UTF-8".to_string(),
56 )))?
57 .to_string();
58
59 let ehlo_name = url.path().trim_start_matches('/');
60 let ehlo_name = percent_decode_str(ehlo_name)
61 .decode_utf8()
62 .or(Err(MailListError::Smtps(
63 "EHLO name is not valid UTF-8".to_string(),
64 )))?
65 .to_string();
66
67 #[cfg(feature = "tokio-runtime")]
68 let transport = AsyncSmtpTransport::<lettre::Tokio1Executor>::from_url(smtps_uri)
69 .map_err(|error| MailListError::Mailer(error.to_string()))?;
70
71 let mailer = if let Some(hello_name) = self.hello_name.as_ref() {
72 transport.hello_name(lettre::transport::smtp::extension::ClientId::Domain(
73 hello_name.to_string(),
74 ))
75 } else {
76 transport
77 };
78
79 let mailer = mailer.build();
80
81 let outcome = Smtps {
82 domain: domain.into(),
83 from: self
84 .from
85 .parse::<Mailbox>()
86 .map_err(|error| MailListError::Mailer(error.to_string()))?,
87 reply_to: self
88 .reply_to
89 .map(|value| {
90 value
91 .parse::<Mailbox>()
92 .map_err(|error| MailListError::Mailer(error.to_string()))
93 })
94 .transpose()?,
95 ehlo_name: if ehlo_name.is_empty() {
96 None
97 } else {
98 Some(ehlo_name.to_string().into())
99 },
100 mailer,
101 };
102
103 Ok(outcome)
104 }
105
106 pub async fn test_connection(self, smtps_uri: &str) -> MailListResult<()> {
107 #[cfg(feature = "tokio-runtime")]
108 let transport = AsyncSmtpTransport::<lettre::Tokio1Executor>::from_url(smtps_uri)
109 .map_err(|error| MailListError::Mailer(error.to_string()))?;
110
111 let mailer = if let Some(hello_name) = self.hello_name.as_ref() {
112 transport.hello_name(lettre::transport::smtp::extension::ClientId::Domain(
113 hello_name.to_string(),
114 ))
115 } else {
116 transport
117 };
118
119 #[cfg(feature = "tokio-runtime")]
120 let mailer: AsyncSmtpTransport<lettre::Tokio1Executor> = mailer.build();
121
122 mailer
123 .test_connection()
124 .await
125 .map_err(|error| MailListError::Mailer(error.to_string()))?;
126
127 Ok(())
128 }
129}