]> Untitled Git - lemmy.git/blob - crates/utils/src/email.rs
fba624666e7c45283de569001889f4c5f88917f0
[lemmy.git] / crates / utils / src / email.rs
1 use crate::{error::LemmyError, settings::structs::Settings};
2 use html2text;
3 use lettre::{
4   message::{Mailbox, MultiPart},
5   transport::smtp::{authentication::Credentials, extension::ClientId},
6   Address,
7   AsyncTransport,
8   Message,
9 };
10 use std::str::FromStr;
11 use uuid::Uuid;
12
13 pub mod translations {
14   rosetta_i18n::include_translations!();
15 }
16
17 type AsyncSmtpTransport = lettre::AsyncSmtpTransport<lettre::Tokio1Executor>;
18
19 pub async fn send_email(
20   subject: &str,
21   to_email: &str,
22   to_username: &str,
23   html: &str,
24   settings: &Settings,
25 ) -> Result<(), LemmyError> {
26   let email_config = settings
27     .email
28     .clone()
29     .ok_or_else(|| LemmyError::from_message("no_email_setup"))?;
30   let domain = settings.hostname.clone();
31
32   let (smtp_server, smtp_port) = {
33     let email_and_port = email_config.smtp_server.split(':').collect::<Vec<&str>>();
34     let email = *email_and_port
35       .first()
36       .ok_or_else(|| LemmyError::from_message("missing an email"))?;
37     let port = email_and_port
38       .get(1)
39       .ok_or_else(|| {
40         LemmyError::from_message("email.smtp_server needs a port, IE smtp.xxx.com:465")
41       })?
42       .parse::<u16>()?;
43
44     (email, port)
45   };
46
47   // use usize::MAX as the line wrap length, since lettre handles the wrapping for us
48   let plain_text = html2text::from_read(html.as_bytes(), usize::MAX);
49
50   let email = Message::builder()
51     .from(
52       email_config
53         .smtp_from_address
54         .parse()
55         .expect("email from address isn't valid"),
56     )
57     .to(Mailbox::new(
58       Some(to_username.to_string()),
59       Address::from_str(to_email).expect("email to address isn't valid"),
60     ))
61     .message_id(Some(format!("<{}@{}>", Uuid::new_v4(), settings.hostname)))
62     .subject(subject)
63     .multipart(MultiPart::alternative_plain_html(
64       plain_text,
65       html.to_string(),
66     ))
67     .expect("email built incorrectly");
68
69   // don't worry about 'dangeous'. it's just that leaving it at the default configuration
70   // is bad.
71
72   // Set the TLS
73   let builder_dangerous = AsyncSmtpTransport::builder_dangerous(smtp_server).port(smtp_port);
74
75   let mut builder = match email_config.tls_type.as_str() {
76     "starttls" => AsyncSmtpTransport::starttls_relay(smtp_server)?,
77     "tls" => AsyncSmtpTransport::relay(smtp_server)?,
78     _ => builder_dangerous,
79   };
80
81   // Set the creds if they exist
82   let smtp_password = std::env::var("LEMMY_SMTP_PASSWORD")
83     .ok()
84     .or(email_config.smtp_password);
85
86   if let (Some(username), Some(password)) = (email_config.smtp_login, smtp_password) {
87     builder = builder.credentials(Credentials::new(username, password));
88   }
89
90   let mailer = builder.hello_name(ClientId::Domain(domain)).build();
91
92   let result = mailer.send(email).await;
93
94   match result {
95     Ok(_) => Ok(()),
96     Err(e) => Err(LemmyError::from_error_message(e, "email_send_failed")),
97   }
98 }