|
| 1 | +use crate::notification::email::{EmailMessage, EmailService}; |
| 2 | +use anyhow::anyhow; |
| 3 | +use async_trait::async_trait; |
| 4 | +use serde::{Deserialize, Serialize}; |
| 5 | +use tracing::error; |
| 6 | + |
| 7 | +#[derive(Debug, Clone)] |
| 8 | +pub struct MailjetConfig { |
| 9 | + pub api_key: String, |
| 10 | + pub api_secret_key: String, |
| 11 | + pub url: url::Url, |
| 12 | +} |
| 13 | + |
| 14 | +pub struct MailjetService { |
| 15 | + config: MailjetConfig, |
| 16 | + client: reqwest::Client, |
| 17 | +} |
| 18 | + |
| 19 | +impl MailjetService { |
| 20 | + pub fn new(config: &MailjetConfig) -> Self { |
| 21 | + let client = reqwest::Client::new(); |
| 22 | + Self { |
| 23 | + config: config.to_owned(), |
| 24 | + client, |
| 25 | + } |
| 26 | + } |
| 27 | +} |
| 28 | + |
| 29 | +#[derive(Debug, Clone, Serialize)] |
| 30 | +struct MailjetReq { |
| 31 | + #[serde(rename = "Messages")] |
| 32 | + pub messages: Vec<MailjetMessage>, |
| 33 | +} |
| 34 | + |
| 35 | +#[derive(Debug, Clone, Deserialize)] |
| 36 | +struct MailjetResp { |
| 37 | + #[serde(rename = "Messages")] |
| 38 | + pub messages: Vec<MailjetRespMessage>, |
| 39 | +} |
| 40 | + |
| 41 | +#[derive(Debug, Clone, Serialize)] |
| 42 | +struct MailjetMessage { |
| 43 | + #[serde(rename = "From")] |
| 44 | + pub from: MailjetFrom, |
| 45 | + #[serde(rename = "To")] |
| 46 | + pub to: Vec<MailjetTo>, |
| 47 | + #[serde(rename = "Subject")] |
| 48 | + pub subject: String, |
| 49 | + #[serde(rename = "HTMLPart")] |
| 50 | + pub html_part: String, |
| 51 | +} |
| 52 | + |
| 53 | +impl From<EmailMessage> for MailjetMessage { |
| 54 | + fn from(value: EmailMessage) -> Self { |
| 55 | + Self { |
| 56 | + from: MailjetFrom { email: value.from }, |
| 57 | + to: vec![MailjetTo { email: value.to }], |
| 58 | + subject: value.subject, |
| 59 | + html_part: value.body, |
| 60 | + } |
| 61 | + } |
| 62 | +} |
| 63 | + |
| 64 | +#[derive(Debug, Clone, Serialize)] |
| 65 | +struct MailjetFrom { |
| 66 | + #[serde(rename = "Email")] |
| 67 | + pub email: String, |
| 68 | +} |
| 69 | + |
| 70 | +#[derive(Debug, Clone, Serialize)] |
| 71 | +struct MailjetTo { |
| 72 | + #[serde(rename = "Email")] |
| 73 | + pub email: String, |
| 74 | +} |
| 75 | + |
| 76 | +#[derive(Debug, Clone, Deserialize)] |
| 77 | +struct MailjetRespMessage { |
| 78 | + #[serde(rename = "Status")] |
| 79 | + pub status: String, |
| 80 | +} |
| 81 | + |
| 82 | +#[async_trait] |
| 83 | +impl EmailService for MailjetService { |
| 84 | + async fn send(&self, msg: super::EmailMessage) -> Result<(), anyhow::Error> { |
| 85 | + let mailjet_msg = MailjetReq { |
| 86 | + messages: vec![MailjetMessage::from(msg)], |
| 87 | + }; |
| 88 | + |
| 89 | + let url = self.config.url.join("/v3.1/send").expect("mailjet path"); |
| 90 | + let request = self.client.post(url).json(&mailjet_msg).basic_auth( |
| 91 | + self.config.api_key.clone(), |
| 92 | + Some(self.config.api_secret_key.clone()), |
| 93 | + ); |
| 94 | + let res = request.send().await.map_err(|e| { |
| 95 | + error!("Failed to send email: {e}"); |
| 96 | + anyhow!("Failed to send email") |
| 97 | + })?; |
| 98 | + |
| 99 | + let resp: MailjetResp = res.json().await.map_err(|e| { |
| 100 | + error!("Failed to parse email response: {e}"); |
| 101 | + anyhow!("Failed to parse email response") |
| 102 | + })?; |
| 103 | + |
| 104 | + match resp.messages.first() { |
| 105 | + Some(msg) => { |
| 106 | + if msg.status != "success" { |
| 107 | + error!("Invalid email sending response: {}", &msg.status); |
| 108 | + Err(anyhow!("Invalid email sending response: {}", &msg.status)) |
| 109 | + } else { |
| 110 | + Ok(()) |
| 111 | + } |
| 112 | + } |
| 113 | + None => { |
| 114 | + error!("Invalid email response - got no status"); |
| 115 | + Err(anyhow!("Invalid email response - got no status")) |
| 116 | + } |
| 117 | + } |
| 118 | + } |
| 119 | +} |
0 commit comments