1use std::time::Duration;
2
3use async_trait::async_trait;
4use lettre::{
5 transport::smtp::authentication::{Credentials, Mechanism},
6 SmtpTransport, Transport,
7};
8
9pub struct Message<'a> {
10 pub title: &'a str,
11 pub body: &'a str,
12 pub to: &'a [&'a str],
13}
14
15#[derive(Debug, PartialEq)]
16pub struct PushResult<'a> {
17 pub email: &'a str,
18 pub success: bool,
19 pub reason: Option<String>,
20}
21
22#[derive(Debug, PartialEq)]
23pub struct Response<'a> {
24 pub results: Vec<PushResult<'a>>,
25}
26
27pub struct Client {
28 client_id: String,
29 cli: SmtpTransport,
30}
31
32impl Client {
33 pub async fn new(client_id: &str, client_secret: &str, push_url: &str) -> Client {
34 let creds = Credentials::new(client_id.to_string(), client_secret.to_string());
35 let mailer = SmtpTransport::starttls_relay(push_url)
36 .unwrap()
37 .authentication(vec![Mechanism::Login])
38 .port(587)
39 .credentials(creds)
40 .timeout(Duration::from_secs(10).into())
41 .pool_config(Default::default())
42 .build();
43
44 Self {
45 client_id: client_id.to_string(),
46 cli: mailer,
47 }
48 }
49}
50
51#[async_trait]
52impl<'b> super::Pusher<'b, Message<'b>, Response<'b>> for Client {
53 async fn push(&self, msg: &'b Message) -> Result<Response<'b>, super::Error> {
54 let mut results = Vec::new();
55
56 for to in msg.to {
57 let to_mail = match format!("<{to}>").parse::<lettre::message::Mailbox>() {
58 Ok(o) => o,
59 Err(e) => {
60 results.push(PushResult {
61 email: to,
62 success: false,
63 reason: Some(e.to_string()),
64 });
65 continue;
66 }
67 };
68
69 let msg = match lettre::Message::builder()
70 .from(format!("<{}>", self.client_id).parse().unwrap())
71 .to(to_mail)
72 .body(msg.body.to_string())
73 {
74 Ok(o) => o,
75 Err(e) => {
76 results.push(PushResult {
77 email: to,
78 success: false,
79 reason: Some(e.to_string()),
80 });
81 continue;
82 }
83 };
84
85 match self.cli.send(&msg) {
86 Ok(resp) => {
88 if resp.is_positive() {
89 results.push(PushResult {
90 email: to,
91 success: true,
92 reason: None,
93 });
94 } else {
95 results.push(PushResult {
96 email: to,
97 success: false,
98 reason: Some(resp.code().to_string()),
99 });
100 }
101 }
102 Err(e) => results.push(PushResult {
103 email: to,
104 success: false,
105 reason: Some(e.to_string()),
106 }),
107 };
108 }
109
110 Ok(Response { results })
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use crate::Pusher;
117
118 #[tokio::test]
119 async fn test_push() {
120 let client_id = std::env::var("EMAIL_CLIENT_ID").unwrap();
121 let client_secret = std::env::var("EMAIL_CLIENT_SECRET").unwrap();
122
123 let cli = super::Client::new(&client_id, &client_secret, "smtp.office365.com").await;
124 let to = vec!["public@hikit.io", "1234"];
125 let msg = super::Message {
126 title: "Hello Email",
127 body: "This is test.",
128 to: to.as_slice(),
129 };
130 let res = cli.retry_push(&msg).await;
131 println!("{:?}", res);
132 }
133}