Skip to main content

mail_list/
transport_builder.rs

1use 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}