use lettre::{ message::{Mailbox, MultiPart, SinglePart}, AsyncTransport, Message, }; use rinja::Template; use crate::{models::User, utils::ApplicationError}; use super::Mailer; impl Mailer { pub async fn send_forgot_password_mail( &self, user: &User, token: &str, ) -> Result<(), ApplicationError> { let message = build(&self.hostname, &user.name, &user.email, token)?; self.transport.send(message).await?; Ok(()) } } #[derive(Template)] #[template(path = "emails/forgot_password.txt")] struct ForgotPasswordMailTemplatePlain<'a> { name: &'a str, reset_url: &'a str, } #[derive(Template)] #[template(path = "emails/forgot_password.html")] struct ForgotPasswordMailTemplateHtml<'a> { name: &'a str, reset_url: &'a str, } fn build( hostname: &str, name: &str, email: &str, token: &str, ) -> Result { let reset_url = format!("https://{hostname}/reset-password?token={token}"); let plain = ForgotPasswordMailTemplatePlain { name, reset_url: &reset_url, } .to_string(); let html = ForgotPasswordMailTemplateHtml { name, reset_url: &reset_url, } .to_string(); let message = Message::builder() .from("noreply ".parse()?) .reply_to("noreply ".parse()?) .to(Mailbox::new(Some(name.to_string()), email.parse()?)) .subject("Brass: Zurücksetzen des Passworts angefordert") .multipart( MultiPart::alternative() .singlepart(SinglePart::plain(plain)) .singlepart(SinglePart::html(html)), ) .unwrap(); Ok(message) } #[cfg(test)] mod tests { use super::*; use crate::utils::test_helper::assert_mail_snapshot; use lettre::{transport::stub::StubTransport, Transport}; #[test] fn build_mail_snapshot() { let message = build( "brasiwa-leipzig.de", "Max Mustermann", "max@example.com", "123456789", ) .unwrap(); let sender = StubTransport::new_ok(); sender.send(&message).unwrap(); let messages = sender.messages(); let (_, sent_message) = messages.first().unwrap(); assert_mail_snapshot!(sent_message); } }