spawn_access_control/notification/
email.rs1use crate::alert_system::{AlertNotification, NotificationError, EscalationLevel};
2use crate::async_trait;
3use lettre::{
4 transport::smtp::authentication::Credentials,
5 AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor,
6};
7use serde::{Serialize, Deserialize};
8
9#[derive(Clone, Debug, Serialize, Deserialize)]
10pub struct SmtpConfig {
11 pub host: String,
12 pub port: u16,
13 pub username: String,
14 pub password: String,
15}
16
17#[derive(Clone)]
18pub struct EmailConfig {
19 pub smtp: SmtpConfig,
20 pub templates: EmailTemplates,
21 pub recipients: EmailRecipients,
22}
23
24#[derive(Clone)]
25pub struct EmailTemplates {
26 pub alert_template: String,
27}
28
29#[derive(Clone)]
30pub struct EmailRecipients {
31 pub primary: Vec<String>,
32 pub emergency: Vec<String>,
33}
34
35#[derive(Clone)]
36pub struct EmailNotificationHandler {
37 config: EmailConfig,
38 transport: AsyncSmtpTransport<Tokio1Executor>,
39}
40
41impl EmailNotificationHandler {
42 pub fn new(config: EmailConfig) -> Result<Self, NotificationError> {
43 let creds = Credentials::new(
44 config.smtp.username.clone(),
45 config.smtp.password.clone(),
46 );
47
48 let transport = AsyncSmtpTransport::<Tokio1Executor>::relay(&config.smtp.host)
49 .map_err(|e| NotificationError::ConfigError(e.to_string()))?
50 .port(config.smtp.port)
51 .credentials(creds)
52 .build();
53
54 Ok(Self {
55 config,
56 transport,
57 })
58 }
59
60 fn render_alert_template(&self, notification: &AlertNotification) -> Result<String, NotificationError> {
61 Ok(format!("Alert: {}", notification.alert.message))
62 }
63}
64
65#[async_trait]
66impl crate::alert_system::NotificationHandler for EmailNotificationHandler {
67 async fn send_notification(&self, notification: &AlertNotification) -> Result<(), NotificationError> {
68 let body = self.render_alert_template(notification)?;
69 let subject = format!(
70 "[{:?}] Alert: {} - {}",
71 notification.alert.severity,
72 notification.alert.metric_name,
73 notification.alert.message
74 );
75
76 let recipients = match notification.escalation_level {
77 EscalationLevel::Critical => {
78 self.config.recipients.emergency.clone()
79 }
80 _ => self.config.recipients.primary.clone(),
81 };
82
83 let email = Message::builder()
84 .subject(subject)
85 .body(body)
86 .map_err(|e| NotificationError::ConfigError(e.to_string()))?;
87
88 for _recipient in recipients {
89 self.transport
90 .send(email.clone())
91 .await
92 .map_err(|e| NotificationError::SendError(e.to_string()))?;
93 }
94
95 Ok(())
96 }
97}