bestool_alertd/targets/
email.rs1use mailgun_rs::{EmailAddress, Mailgun, Message};
2use miette::{IntoDiagnostic, Result, WrapErr, miette};
3use tracing::debug;
4
5use crate::{EmailConfig, alert::AlertDefinition};
6
7#[derive(serde::Deserialize, serde::Serialize, Clone, Debug)]
8#[serde(rename_all = "snake_case")]
9pub struct TargetEmail {
10 pub addresses: Vec<String>,
11}
12
13impl TargetEmail {
14 pub async fn send(
15 &self,
16 alert: &AlertDefinition,
17 email: Option<&EmailConfig>,
18 subject: &str,
19 body: &str,
20 dry_run: bool,
21 ) -> Result<()> {
22 let body = {
23 let parser = pulldown_cmark::Parser::new(body);
24 let mut html_output = String::new();
25 pulldown_cmark::html::push_html(&mut html_output, parser);
26 html_output
27 };
28
29 if dry_run {
30 println!("-------------------------------");
31 println!("Alert: {}", alert.file.display());
32 println!("Recipients: {}", self.addresses.join(", "));
33 println!("Subject: {subject}");
34 println!("Body: {body}");
35 return Ok(());
36 }
37
38 debug!(?self.addresses, "sending email");
39 let email_config = email.ok_or_else(|| miette!("missing email config"))?;
40 let sender = EmailAddress::address(&email_config.from);
41 let message = Message {
42 to: self
43 .addresses
44 .iter()
45 .map(|email| EmailAddress::address(email))
46 .collect(),
47 subject: subject.into(),
48 html: body,
49 ..Default::default()
50 };
51 let mailgun = Mailgun {
52 api_key: email_config.mailgun_api_key.clone(),
53 domain: email_config.mailgun_domain.clone(),
54 };
55 mailgun
56 .async_send(mailgun_rs::MailgunRegion::US, &sender, message, None)
57 .await
58 .into_diagnostic()
59 .wrap_err("sending email")
60 .map(drop)
61 }
62}