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