diff options
Diffstat (limited to 'src/mail.rs')
-rw-r--r-- | src/mail.rs | 126 |
1 files changed, 84 insertions, 42 deletions
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 } |