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_NAME: &str = "Budget"; static FROM_ADDRESS: &str = "budget@guyonvarch.me"; #[derive(Clone)] pub struct Recipient { pub name: String, pub address: String, } pub async fn send( config: &Config, recipients: Vec, subject: String, message: String, ) -> bool { 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::>(); 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 } } Err(err) => { error!("Error spawning command: {:?}", err); false } } } } fn format_headers(recipients: Vec, subject: String) -> String { let recipients = recipients .into_iter() .map(|r| format_address(r.name, r.address)) .collect::>() .join(", "); 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) } async fn spawn(mut command: Command, stdin: &Vec) -> Result { 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 }