aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/jobs/weekly_report.rs11
-rw-r--r--src/mail.rs126
-rw-r--r--src/model/config.rs2
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,
}