email_clients/clients/
smtp.rs1use crate::configuration::EmailConfiguration;
2use crate::email::{EmailAddress, EmailObject};
3use crate::traits::EmailTrait;
4use async_trait::async_trait;
5use lettre::message::MultiPart;
6use lettre::transport::smtp::authentication::Credentials;
7use lettre::transport::smtp::SMTP_PORT;
8use lettre::{AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor};
9use log::info;
10use secrecy::ExposeSecret;
11use secrecy::Secret;
12
13#[derive(
14 Debug, PartialEq, Eq, Clone, serde::Deserialize, serde::Serialize, Default, PartialOrd,
15)]
16pub enum TlsMode {
17 #[default]
18 Local,
19 Tls, StartTls, }
22#[derive(Debug, Clone, serde::Deserialize)]
23pub struct SmtpConfig {
24 pub sender: EmailAddress,
25 pub relay: String,
26 pub username: String,
27 pub password: Secret<String>,
28 pub port: u16,
29 pub tls: TlsMode,
30}
31
32impl Default for SmtpConfig {
33 fn default() -> Self {
34 Self {
35 sender: "".into(),
36 relay: "localhost".to_owned(),
37 username: "".to_string(),
38 port: SMTP_PORT,
39 tls: TlsMode::Local,
40 password: Secret::from("".to_string()),
41 }
42 }
43}
44
45impl SmtpConfig {
46 pub fn sender(mut self, value: impl Into<EmailAddress>) -> Self {
55 self.sender = value.into();
56 self
57 }
58
59 pub fn relay(mut self, value: impl AsRef<str>) -> Self {
68 self.relay = value.as_ref().to_string();
69 self
70 }
71
72 pub fn username(mut self, value: impl AsRef<str>) -> Self {
81 self.username = value.as_ref().to_string();
82 self
83 }
84
85 pub fn password(mut self, value: impl AsRef<str>) -> Self {
95 self.password = Secret::new(value.as_ref().to_string());
96 self
97 }
98
99 pub fn port(mut self, value: u16) -> Self {
108 self.port = value;
109 self
110 }
111
112 pub fn tls(mut self, value: TlsMode) -> Self {
121 self.tls = value;
122 self
123 }
124}
125
126impl From<SmtpConfig> for EmailConfiguration {
127 fn from(value: SmtpConfig) -> Self {
148 EmailConfiguration::SMTP(value)
149 }
150}
151
152#[derive(Clone, Debug, Default)]
153pub struct SmtpClient {
154 config: SmtpConfig,
155}
156
157impl SmtpClient {
158 fn get_transport(&self) -> AsyncSmtpTransport<Tokio1Executor> {
159 let settings = &self.config;
160 let creds = Credentials::new(
161 settings.username.to_owned(),
162 settings.password.expose_secret().to_owned(),
163 );
164
165 match settings.tls {
166 TlsMode::Local => {
167 AsyncSmtpTransport::<Tokio1Executor>::builder_dangerous(settings.relay.as_str())
168 .port(settings.port)
169 .timeout(Some(std::time::Duration::from_secs(10)))
170 .build()
171 }
172 TlsMode::Tls => AsyncSmtpTransport::<Tokio1Executor>::relay(settings.relay.as_str())
173 .unwrap()
174 .credentials(creds)
175 .port(settings.port)
176 .build(),
177 TlsMode::StartTls => {
178 AsyncSmtpTransport::<Tokio1Executor>::starttls_relay(settings.relay.as_str())
179 .unwrap()
180 .credentials(creds)
181 .port(settings.port)
182 .build()
183 }
184 }
185 }
186
187 pub fn new(config: SmtpConfig) -> Self {
188 info!("Starting smtp client");
189 Self { config }
190 }
191}
192
193#[async_trait]
194impl EmailTrait for SmtpClient {
195 fn get_sender(&self) -> EmailAddress {
196 self.config.sender.clone()
197 }
198
199 async fn send_emails(&self, email: EmailObject) -> crate::Result<()> {
200 let transport = self.get_transport();
201 let email_body = MultiPart::alternative_plain_html(email.plain, email.html);
202
203 let mut message_builder = Message::builder()
204 .from(self.get_sender().try_into()?)
205 .reply_to(self.get_sender().try_into()?);
206 for addr in email.to {
207 message_builder = message_builder.to(addr.try_into()?)
208 }
209 let message = message_builder
210 .subject(email.subject)
211 .multipart(email_body)?;
212 transport.send(message).await?;
213 Ok(())
214 }
215}