Working on email templating, updating with swagger
This commit is contained in:
103
api/src/smtp/mod.rs
Normal file
103
api/src/smtp/mod.rs
Normal file
@@ -0,0 +1,103 @@
|
||||
use crate::error::ApiResult;
|
||||
use chrono::{Datelike, Utc};
|
||||
use handlebars::Handlebars;
|
||||
use lettre::message::header::ContentType;
|
||||
use lettre::message::{Mailbox, MultiPart, SinglePart};
|
||||
use lettre::transport::smtp::authentication::Credentials;
|
||||
use lettre::{Address, Message, SmtpTransport, Transport};
|
||||
use serde::Serialize;
|
||||
use std::env;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
static MAILER: OnceLock<SmtpTransport> = OnceLock::new();
|
||||
static FROM_ADDRESS: OnceLock<Mailbox> = OnceLock::new();
|
||||
static REGISTRY: OnceLock<Handlebars> = OnceLock::new();
|
||||
|
||||
fn mailer() -> &'static SmtpTransport {
|
||||
MAILER.get_or_init(|| {
|
||||
let server = env::var("SMTP_SERVER").expect("SMTP_SERVER missing");
|
||||
let username = env::var("SMTP_USERNAME").expect("SMTP_USERNAME missing");
|
||||
let password = env::var("SMTP_PASSWORD").expect("SMTP_PASSWORD missing");
|
||||
let creds = Credentials::new(username, password);
|
||||
SmtpTransport::relay(&server)
|
||||
.expect("invalid SMTP_SERVER")
|
||||
.credentials(creds)
|
||||
.build()
|
||||
})
|
||||
}
|
||||
|
||||
fn from_address() -> &'static Mailbox {
|
||||
FROM_ADDRESS.get_or_init(|| {
|
||||
let raw = env::var("SMTP_FROM").expect("SMTP_FROM missing");
|
||||
let addr = raw.parse().expect("SMTP_FROM invalid");
|
||||
Mailbox::new(Some("Aviation Data".into()), addr)
|
||||
})
|
||||
}
|
||||
|
||||
fn registry() -> &'static Handlebars<'static> {
|
||||
REGISTRY.get_or_init(|| Handlebars::new())
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct PasswordResetCtx {
|
||||
logo_url: String,
|
||||
link: String,
|
||||
domain: String,
|
||||
year: i32,
|
||||
}
|
||||
|
||||
pub fn send_password_reset(to: &str, token: &str) -> ApiResult<()> {
|
||||
let base_url = env::var("EXTERNAL_URL")?.trim_end_matches('/').to_string();
|
||||
let link = format!("{base_url}/profile/reset?token={token}");
|
||||
let subject = "Reset your password";
|
||||
|
||||
let plain = format!(
|
||||
"Hello,\n\n\
|
||||
We received a password reset request. Click the link below:\n\n\
|
||||
{link}\n\n\
|
||||
This link expires in 24 hours. If you didn't request this, please ignore.\n\n\
|
||||
Cheers,\n\
|
||||
\tAviation Data",
|
||||
link = link
|
||||
);
|
||||
|
||||
let ctx = PasswordResetCtx {
|
||||
logo_url: format!("{}/logo.svg", base_url),
|
||||
link: link.clone(),
|
||||
domain: base_url,
|
||||
year: Utc::now().year(),
|
||||
};
|
||||
|
||||
let template_html = include_str!("../.././templates/password_reset.html");
|
||||
let html = registry().render_template(template_html, &ctx).unwrap();
|
||||
|
||||
send_email(to, subject, plain, html)
|
||||
}
|
||||
|
||||
pub fn send_email(to: &str, subject: &str, header: String, html: String) -> ApiResult<()> {
|
||||
let to_address = to.parse::<Address>()?;
|
||||
let to_mailbox = Mailbox::new(None, to_address);
|
||||
|
||||
// Build the email
|
||||
let email = Message::builder()
|
||||
.from(from_address().clone())
|
||||
.to(to_mailbox)
|
||||
.subject(subject)
|
||||
.multipart(
|
||||
MultiPart::alternative()
|
||||
.singlepart(
|
||||
SinglePart::builder()
|
||||
.header(ContentType::TEXT_PLAIN)
|
||||
.body(header),
|
||||
)
|
||||
.singlepart(
|
||||
SinglePart::builder()
|
||||
.header(ContentType::TEXT_HTML)
|
||||
.body(html),
|
||||
),
|
||||
)?;
|
||||
|
||||
// Send the email
|
||||
mailer().send(&email)?;
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user