1
use crate::{
2
    error::{Error, Result},
3
    mail_config::MailConfig,
4
};
5
use lettre::{
6
    message::{
7
        header::{ContentType, MessageId, MimeVersion},
8
        Mailbox,
9
    },
10
    transport::{
11
        file::FileTransport,
12
        smtp::{authentication::Credentials, extension::ClientId},
13
    },
14
    Message, SmtpTransport, Transport,
15
};
16
use std::path::PathBuf;
17

            
18
/// This struct holds information about how to connect to the smtp server.
19
/// It also handles creating mails and storing sent mails in project folders.
20
pub struct Mailer {
21
    config: MailConfig,
22
}
23

            
24
9
fn addr(mail: &str, name: Option<&str>) -> Result<Mailbox> {
25
9
    Ok(Mailbox::new(
26
9
        name.map(|n| n.to_string()),
27
9
        mail.parse()
28
9
            .map_err(|e| Error::Msg(format!("Cannot parse mail address {}: {}", mail, e)))?,
29
    ))
30
9
}
31

            
32
impl Mailer {
33
9
    pub fn new(config: MailConfig) -> Result<Self> {
34
9
        Ok(Self { config })
35
9
    }
36
    /// Send a mail to the contact person of a proposal.
37
    /// A CC is sent to the NLnet alias for managing the project.
38
    /// The Reply-To is also set to that alias.
39
    /// The mail is saved in the `mail/` folder in the project directory.
40
3
    pub fn send_mail(
41
3
        &self,
42
3
        subject: &str,
43
3
        body: &str,
44
3
        to_mail: &str,
45
3
        to_name: &str,
46
3
        cc: &[&str],
47
3
    ) -> std::result::Result<(), Box<dyn std::error::Error>> {
48
3
        let mut builder = Message::builder();
49
3
        builder = builder.to(addr(to_mail, Some(to_name))?);
50
6
        for cc in cc {
51
3
            builder = builder.cc(addr(cc, None)?);
52
        }
53
3
        let email = builder
54
3
            .header(ContentType::TEXT_PLAIN)
55
3
            .header(MessageId::from(format!(
56
3
                "{}@{}",
57
3
                uuid::Uuid::new_v4(),
58
3
                self.config.mail_server
59
3
            )))
60
3
            .header(MimeVersion::new(1, 0))
61
3
            .from(addr(&self.config.sender, None)?)
62
3
            .subject(subject)
63
3
            .body(body.to_string())?;
64
3
        let mail_dir = "out".to_string();
65
3
        std::fs::create_dir_all(&mail_dir).map_err(|e| Error::Io {
66
            source: e,
67
            path: PathBuf::from(&mail_dir),
68
3
        })?;
69
3
        let file_transport = FileTransport::new(mail_dir);
70
3
        file_transport.send(&email)?;
71
3
        if !self.config.testing {
72
            let credentials =
73
                Credentials::new(self.config.username.clone(), self.config.password.clone());
74
            let mailer = SmtpTransport::relay(&self.config.mail_server)?
75
                .hello_name(ClientId::Domain(self.config.mail_server.clone()))
76
                .credentials(credentials)
77
                .build();
78
            mailer.send(&email)?;
79
3
        }
80
3
        Ok(())
81
3
    }
82
}