]> Untitled Git - lemmy.git/blob - crates/utils/src/email.rs
a52ed405557c9f124d6816a8f217cd7bba491f9b
[lemmy.git] / crates / utils / src / email.rs
1 use crate::{settings::structs::Settings, LemmyError};
2 use html2text;
3 use lettre::{
4   message::{header, Mailbox, MultiPart, SinglePart},
5   transport::smtp::{authentication::Credentials, extension::ClientId},
6   Address,
7   Message,
8   SmtpTransport,
9   Transport,
10 };
11 use std::str::FromStr;
12 use uuid::Uuid;
13
14 pub fn send_email(
15   subject: &str,
16   to_email: &str,
17   to_username: &str,
18   html: &str,
19   settings: &Settings,
20 ) -> Result<(), LemmyError> {
21   let email_config = settings
22     .email
23     .to_owned()
24     .ok_or_else(|| LemmyError::from_message("no_email_setup"))?;
25   let domain = settings.hostname.to_owned();
26
27   let (smtp_server, smtp_port) = {
28     let email_and_port = email_config.smtp_server.split(':').collect::<Vec<&str>>();
29     if email_and_port.len() == 1 {
30       return Err(LemmyError::from_message(
31         "email.smtp_server needs a port, IE smtp.xxx.com:465",
32       ));
33     }
34
35     (
36       email_and_port[0],
37       email_and_port[1]
38         .parse::<u16>()
39         .expect("email needs a port"),
40     )
41   };
42
43   // the message length before wrap, 78, is somewhat arbritary but looks good to me
44   let plain_text = html2text::from_read(html.as_bytes(), 78);
45
46   let email = Message::builder()
47     .from(
48       email_config
49         .smtp_from_address
50         .parse()
51         .expect("email from address isn't valid"),
52     )
53     .to(Mailbox::new(
54       Some(to_username.to_string()),
55       Address::from_str(to_email).expect("email to address isn't valid"),
56     ))
57     .message_id(Some(format!("{}@{}", Uuid::new_v4(), settings.hostname)))
58     .subject(subject)
59     .multipart(
60       MultiPart::mixed().multipart(
61         MultiPart::alternative()
62           .singlepart(
63             SinglePart::builder()
64               .header(header::ContentType::TEXT_PLAIN)
65               .body(plain_text),
66           )
67           .multipart(
68             MultiPart::related().singlepart(
69               SinglePart::builder()
70                 .header(header::ContentType::TEXT_HTML)
71                 .body(html.to_string()),
72             ),
73           ),
74       ),
75     )
76     .expect("email built incorrectly");
77
78   // don't worry about 'dangeous'. it's just that leaving it at the default configuration
79   // is bad.
80
81   // Set the TLS
82   let builder_dangerous = SmtpTransport::builder_dangerous(smtp_server).port(smtp_port);
83
84   let mut builder = match email_config.tls_type.as_str() {
85     "starttls" => SmtpTransport::starttls_relay(smtp_server)?,
86     "tls" => SmtpTransport::relay(smtp_server)?,
87     _ => builder_dangerous,
88   };
89
90   // Set the creds if they exist
91   if let (Some(username), Some(password)) = (email_config.smtp_login, email_config.smtp_password) {
92     builder = builder.credentials(Credentials::new(username, password));
93   }
94
95   let mailer = builder.hello_name(ClientId::Domain(domain)).build();
96
97   let result = mailer.send(&email);
98
99   match result {
100     Ok(_) => Ok(()),
101     Err(e) => Err(LemmyError::from(e).with_message("email_send_failed")),
102   }
103 }