/*
* authservice email/mod.rs
* - email thread, most of everything needed for emails are in this file
*
* Copyright (C) 2025 Real Microsoft, LLC
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
*/
mod templates;
use lettre::{Message, SmtpTransport, Transport};
use lettre::message::header::ContentType;
use lettre::transport::smtp::authentication::Credentials;
use log::{error, info};
use tokio::sync::{mpsc, OnceCell};
use crate::{SMTP_DISABLE, SMTP_PASSWORD, SMTP_URL, SMTP_USERNAME};
pub enum EmailMessage {
VerificationCode {
to: String,
username: String,
url: String,
},
DeveloperWarning {
message: String,
},
Announcement {
to: String,
username: String,
title: String,
text_contents: String,
}
}
static EMAIL_SENDER: OnceCell> = OnceCell::const_new();
const EMAIL_RETRY_TIME_SECS: u64 = 10;
const DEVWARNING_TARGET: &str = "AskLyphe Developer ";
pub fn send_devwarn_email(message: &str) {
EMAIL_SENDER.get().unwrap().send(EmailMessage::DeveloperWarning {
message: message.to_string(),
}).unwrap();
}
pub fn send_verification_code_email(to: &str, username: &str, url: &str) {
EMAIL_SENDER.get().unwrap().send(EmailMessage::VerificationCode {
to: to.to_string(),
username: username.to_string(),
url: url.to_string(),
}).unwrap();
}
pub fn send_announcement_email(to: &str, username: &str, title: &str, content: &str) {
EMAIL_SENDER.get().unwrap().send(EmailMessage::Announcement {
to: to.to_string(),
username: username.to_string(),
title: title.to_string(),
text_contents: content.to_string(),
}).unwrap()
}
pub async fn email_init() {
let (email_sender, email_receiver) = mpsc::unbounded_channel();
EMAIL_SENDER.set(email_sender).unwrap();
tokio::spawn(async move {
info!("email thread spawned");
if *SMTP_DISABLE {
info!("SMTP disable");
}
let creds = Credentials::new(SMTP_USERNAME.to_string(), SMTP_PASSWORD.to_string());
let mailer = SmtpTransport::starttls_relay(&SMTP_URL).expect("FAILED TO CONNECT TO EMAIL SMTP RELAY")
.credentials(creds)
.build();
let mut rx = email_receiver;
loop {
if let Ok(message) = rx.try_recv() {
match message {
EmailMessage::VerificationCode { to, username, url } => {
if *SMTP_DISABLE {
info!("not sending smtp verification code due to smtp disable");
continue;
}
let body = templates::gen_verification_code_email(&url);
let email = Message::builder()
.from("Vore Microcomputers No-Reply ".parse().unwrap())
.reply_to("Vore Microcomputers Support ".parse().unwrap());
let to_mbox = format!("{username} <{to}>").parse();
if let Err(e) = to_mbox {
error!("couldn't parse \"to\" field for verification code email! \"{username}\" \"{to}\"");
send_devwarn_email(&format!("couldn't parse \"to\" field for verification code email! \"{username}\" \"{to}\" you need to send the email manually and investigate"));
continue;
}
let email = email.to(to_mbox.unwrap())
.subject("AskLyphe Account Verification")
.header(ContentType::TEXT_PLAIN)
.body(body);
if let Err(e) = email {
error!("couldn't generate email for verification! \"{e}\" \"{username}\" \"{to}\"");
send_devwarn_email(&format!("couldn't generate verification code email! \"{e}\" \"{username}\" \"{to}\" you need to send the email manually and investigate"));
continue;
}
let email = email.unwrap();
if let Err(e) = mailer.send(&email) {
error!("couldn't send email for verification! \"{e}\" \"{username}\" \"{to}\"");
send_devwarn_email(&format!("couldn't send verification code email! \"{e}\" \"{username}\" \"{to}\" you need to send the email manually and investigate"));
continue;
}
}
EmailMessage::DeveloperWarning { message } => {
if *SMTP_DISABLE {
info!("not sending smtp developer warning due to smtp disable");
continue;
}
let body = templates::gen_developer_warning_email(&message);
let email = Message::builder()
.from("Vore Microcomputers No-Reply ".parse().unwrap())
.to(DEVWARNING_TARGET.parse().unwrap())
.subject("AskLyphe Developer Warning")
.header(ContentType::TEXT_PLAIN)
.body(body);
if let Err(e) = email {
error!("couldn't generate email for developer warning!! \"{e}\" \"{message}\"");
continue;
}
let email = email.unwrap();
if let Err(e) = mailer.send(&email) {
error!("couldn't send email for developer warning!!! \"{e}\" \"{message}\"");
continue;
}
}
EmailMessage::Announcement { to, username, title, text_contents } => {
if *SMTP_DISABLE {
info!("not sending smtp announcement due to smtp disable");
continue;
}
let body = templates::gen_announcement_email(&text_contents);
let email = Message::builder()
.from("Vore Microcomputers No-Reply ".parse().unwrap())
.reply_to("Vore Microcomputers Support ".parse().unwrap());
let to_mbox = format!("{username} <{to}>").parse();
if let Err(e) = to_mbox {
error!("couldn't parse \"to\" field for announcement email! \"{username}\" \"{to}\"");
send_devwarn_email(&format!("couldn't parse \"to\" field for announcement email! \"{username}\" \"{to}\" you need to send the email manually and investigate"));
continue;
}
let email = email.to(to_mbox.unwrap())
.subject(format!("AskLyphe Announcement: {title}").as_str())
.header(ContentType::TEXT_PLAIN)
.body(body);
if let Err(e) = email {
error!("couldn't generate email for announcement! \"{e}\" \"{username}\" \"{to}\"");
send_devwarn_email(&format!("couldn't generate announcement email! \"{e}\" \"{username}\" \"{to}\" you need to send the email manually and investigate"));
continue;
}
let email = email.unwrap();
if let Err(e) = mailer.send(&email) {
error!("couldn't send email for announcement! \"{e}\" \"{username}\" \"{to}\"");
send_devwarn_email(&format!("couldn't send announcement email! \"{e}\" \"{username}\" \"{to}\" you need to send the email manually and investigate"));
continue;
}
}
}
}
}
});
}