Skip to main content

email_notif/
send.rs

1use std::panic::{catch_unwind, resume_unwind, UnwindSafe};
2
3use crate::config::Config;
4use lettre::{transport::smtp::authentication::Credentials, Message, SmtpTransport, Transport};
5
6/// Struct containing config and a tag. Associated methods are used to send
7/// email and to enclose functions in status updates.
8pub struct EmailNotifier {
9    config: Config,
10    tag: String,
11}
12
13impl EmailNotifier {
14    /// Constructs a new `EmailNotifier`. The parameter `tag` is a descriptive
15    /// name given to the process that you are monitoring. This tag will be
16    /// included in the subject line of any sent emails. Configuration is
17    /// loaded from the default location.
18    ///
19    /// # Example
20    /// ```
21    /// use email_notif::EmailNotifier;
22    /// let em = EmailNotifier::new();
23    /// ```
24    pub fn new(tag: impl ToString) -> Self {
25        EmailNotifier {
26            config: Config::load(),
27            tag: tag.to_string(),
28        }
29    }
30
31    /// Method to send an email via SMTP with the given subject and plain-text
32    /// body.
33    fn send_email(&self, subject: String, body: String) {
34        let email = Message::builder()
35            .from(self.config.sender_email.parse().unwrap())
36            .to(self.config.recipient_email.parse().unwrap())
37            .subject(subject)
38            .body(body)
39            .unwrap();
40
41        let creds = Credentials::new(
42            self.config.sender_email.clone(),
43            self.config.password.clone(),
44        );
45        let mailer = SmtpTransport::relay(&self.config.smtp_server)
46            .unwrap()
47            .credentials(creds)
48            .build();
49
50        match mailer.send(&email) {
51            Ok(_) => (),
52            Err(e) => panic!("Could not send email: {:?}", e),
53        }
54    }
55
56    /// Send an update email about the running process, with the given body text.
57    pub fn send_update(&self, body: String) {
58        self.send_email(format!("{} Update", self.tag), body);
59    }
60
61    /// Send a message indicating the process has completed successfully.
62    pub fn send_success(&self) {
63        self.send_email(
64            format!("{} Complete", self.tag),
65            format!("{} has completed successfully.", self.tag),
66        );
67    }
68
69    /// Send a message indicating the process has resulted in a panic.
70    pub fn send_error(&self) {
71        self.send_email(
72            format!("{} Error!", self.tag),
73            format!("{} has encountered a error and has panicked.", self.tag),
74        );
75    }
76
77    /// Run a closure and send an email when the closure completes
78    /// successfully (`EmailNotifier::send_success`) or if the process
79    /// results in a panic, send an error message
80    /// (`EmailNotifier::send_error`)
81    ///
82    /// # Example
83    ///
84    /// ```
85    /// use email_notif::EmailNotifier;
86    /// EmailNotifier::new("Test").capture(|em|{
87    ///    for i in 0..10 {
88    ///      em.send_update(format!("iteration {i} complete."));
89    ///    }
90    /// });
91    /// ```
92    pub fn capture<F>(self, f: F)
93    where
94        F: UnwindSafe + FnOnce(&EmailNotifier) -> (),
95    {
96        match catch_unwind(|| {
97            f(&self);
98        }) {
99            Ok(_) => self.send_success(),
100            Err(e) => {
101                self.send_error();
102                resume_unwind(e);
103            }
104        };
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    #[test]
113    fn test_send_mail() {
114        let em = EmailNotifier::new("Test1");
115        em.send_email("Test".to_string(), "<b>test</b>".to_string());
116    }
117
118    #[test]
119    fn test_capture() {
120        EmailNotifier::new("Test2").capture(|_| {});
121    }
122
123    #[test]
124    #[should_panic]
125    fn test_capture_error() {
126        EmailNotifier::new("Test3").capture(|_| panic!("foo"));
127    }
128}