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