1use lettre::{
2 transport::smtp::{self, authentication::Credentials},
3 Address, Message, Transport,
4};
5
6#[derive(Clone)]
7pub struct EmailClientConfig {
8 pub smtp_host: String,
9 pub smtp_port: u16,
10 pub smtp_username: String,
11 pub smtp_password: String,
12}
13
14#[derive(Clone)]
15pub struct EmailClient {
16 config: EmailClientConfig,
17}
18
19impl EmailClient {
20 pub fn new(config: EmailClientConfig) -> Self {
21 EmailClient { config }
22 }
23
24 pub async fn send_email(
25 &self,
26 to: String,
27 subject: String,
28 body: String,
29 ) -> Result<(), Box<dyn std::error::Error>> {
30 let smtp_config = smtp::SmtpTransport::starttls_relay(&self.config.smtp_host)
31 .unwrap()
32 .port(self.config.smtp_port)
33 .credentials(Credentials::new(
34 self.config.smtp_username.clone(),
35 self.config.smtp_password.clone(),
36 ))
37 .build();
38
39 let _conn = smtp_config.test_connection()?;
40
41 let address = self.config.smtp_username.parse::<Address>()?;
42
43 let mailbox = lettre::message::Mailbox::new(None, address);
44
45 let email = Message::builder()
46 .from(mailbox)
47 .to(to.parse().unwrap())
48 .subject(subject)
49 .body(body)
50 .unwrap();
51
52 smtp_config.send(&email)?;
53
54 Ok(())
55 }
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61 use dotenv::dotenv;
62 use std::env;
63
64 #[tokio::test]
65 async fn test_smtp_connection() -> Result<(), Box<dyn std::error::Error>> {
66 dotenv().ok();
67
68 let smtp_host = env::var("SMTP_HOST").expect("SMTP_HOST is missing");
69 let smtp_port = env::var("SMTP_PORT")
70 .expect("SMTP_PORT is missing")
71 .parse::<u16>()?;
72 let smtp_username = env::var("SMTP_USERNAME").expect("SMTP_USERNAME is missing");
73 let smtp_password = env::var("SMTP_PASSWORD").expect("SMTP_PASSWORD is missing");
74
75 let config = EmailClientConfig {
76 smtp_host,
77 smtp_port,
78 smtp_username,
79 smtp_password,
80 };
81
82 let email_client = EmailClient::new(config);
83
84 let result = email_client
86 .send_email(
87 "test@example.com".to_string(),
88 "Test Connection".to_string(),
89 "Test".to_string(),
90 )
91 .await;
92
93 assert!(result.is_ok());
95
96 Ok(())
97 }
98}