diff options
author | Joris | 2021-01-23 08:58:42 +0100 |
---|---|---|
committer | Joris | 2021-02-01 09:58:56 +0100 |
commit | a8e943c9a7abbde95c900d89fc5f2ed825e9afa3 (patch) | |
tree | 18ce24a52071933905bc35a68f1a1ec5978c9666 /src | |
parent | 9b85d7ff5e241f97141e85d60a5ce401963a35c3 (diff) |
Fix mail sending
The error was due to the utilization of the special character “—” in the
subject, with postfix’ sendmail.
Also directly use process commands to get async support.
Diffstat (limited to 'src')
-rw-r--r-- | src/jobs/weekly_report.rs | 11 | ||||
-rw-r--r-- | src/mail.rs | 126 | ||||
-rw-r--r-- | src/model/config.rs | 2 |
3 files changed, 94 insertions, 45 deletions
diff --git a/src/jobs/weekly_report.rs b/src/jobs/weekly_report.rs index 819d30b..b482f0d 100644 --- a/src/jobs/weekly_report.rs +++ b/src/jobs/weekly_report.rs @@ -17,10 +17,17 @@ pub async fn send( let users = db::users::list(pool).await; mail::send( config, - users.into_iter().map(|u| (u.email, u.name)).collect(), - "Budget — rapport hebdomadaire".to_string(), + users + .into_iter() + .map(|u| mail::Recipient { + name: u.name, + address: u.email, + }) + .collect(), + "Rapport hebdomadaire".to_string(), report, ) + .await } Err(err) => { error!("Error preparing weekly report from template: {:?}", err); diff --git a/src/mail.rs b/src/mail.rs index 52b5789..149a5ef 100644 --- a/src/mail.rs +++ b/src/mail.rs @@ -1,59 +1,101 @@ -use lettre::sendmail::SendmailTransport; -use lettre::{SendableEmail, Transport}; -use lettre_email::Email; +use chrono::Utc; +use std::io::{Error, ErrorKind}; +use std::process::{Output, Stdio}; +use tokio::io::AsyncWriteExt; +use tokio::process::Command; use crate::model::config::Config; -static FROM: &str = "contact@guyonvarch.me"; +static FROM_NAME: &str = "Budget"; +static FROM_ADDRESS: &str = "budget@guyonvarch.me"; -pub fn send( +#[derive(Clone)] +pub struct Recipient { + pub name: String, + pub address: String, +} + +pub async fn send( config: &Config, - to: Vec<(String, String)>, + recipients: Vec<Recipient>, subject: String, message: String, ) -> bool { - match prepare_email(to.clone(), subject.clone(), message.clone()) { - Ok(email) => { - if let Some(sendmail_path) = &config.sendmail_path { - let mut sender = - SendmailTransport::new_with_command(sendmail_path); - match sender.send(email) { - Ok(_) => true, - Err(err) => { - error!("Error sending email: {:?}", err); - false - } + let headers = format_headers(recipients.clone(), subject); + + info!( + "Sending mail{}\n{}", + if config.mock_mails { " (MOCK)" } else { "" }, + headers.clone() + ); + + if config.mock_mails { + true + } else { + let recipient_addresses = recipients + .clone() + .into_iter() + .map(|r| r.address) + .collect::<Vec<String>>(); + + let mut command = Command::new("sendmail"); + command.kill_on_drop(true); + command.arg("-f").arg(FROM_ADDRESS); + command.arg("--").args(recipient_addresses); + command + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + let message = format!("{}\n\n{}", headers, message); + match spawn(command, &message.into_bytes()).await { + Ok(output) => { + if output.status.success() { + info!("Mail sent"); + true + } else { + match String::from_utf8(output.stderr) { + Ok(error) => error!("Error sending email: {}", error), + _ => error!("Error sending email"), + }; + false } - } else { - let formatted_to = to - .into_iter() - .map(|t| t.0) - .collect::<Vec<String>>() - .join(", "); - info!( - "MOCK MAIL\nto: {}\nsubject: {}\n\n{}", - formatted_to, subject, message - ); - true } - } - Err(err) => { - error!("Error preparing email: {:?}", err); - false + Err(err) => { + error!("Error spawning command: {:?}", err); + false + } } } } -fn prepare_email( - to: Vec<(String, String)>, - subject: String, - message: String, -) -> Result<SendableEmail, lettre_email::error::Error> { - let mut email = Email::builder().from(FROM).subject(subject).text(message); +fn format_headers(recipients: Vec<Recipient>, subject: String) -> String { + let recipients = recipients + .into_iter() + .map(|r| format_address(r.name, r.address)) + .collect::<Vec<String>>() + .join(", "); - for (address, name) in to.iter() { - email = email.to((address, name)); - } + format!( + "Date: {}\nFrom: {}\nTo: {}\nSubject: {}", + Utc::now().to_rfc2822(), + format_address(FROM_NAME.to_string(), FROM_ADDRESS.to_string()), + recipients, + subject, + ) +} + +fn format_address(name: String, address: String) -> String { + format!("{} <{}>", name, address) +} - email.build().map(|e| e.into()) +async fn spawn(mut command: Command, stdin: &Vec<u8>) -> Result<Output, Error> { + let mut process = command.spawn()?; + process + .stdin + .as_mut() + .ok_or(Error::new(ErrorKind::Other, "Getting mutable stdin"))? + .write_all(stdin) + .await?; + process.wait_with_output().await } diff --git a/src/model/config.rs b/src/model/config.rs index 2c5455e..cebe1a6 100644 --- a/src/model/config.rs +++ b/src/model/config.rs @@ -3,5 +3,5 @@ use serde::Deserialize; #[derive(Clone, Deserialize)] pub struct Config { pub secure_cookies: bool, - pub sendmail_path: Option<String>, + pub mock_mails: bool, } |