aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJoris2021-01-23 08:58:42 +0100
committerJoris2021-02-01 09:58:56 +0100
commita8e943c9a7abbde95c900d89fc5f2ed825e9afa3 (patch)
tree18ce24a52071933905bc35a68f1a1ec5978c9666 /src
parent9b85d7ff5e241f97141e85d60a5ce401963a35c3 (diff)
downloadbudget-a8e943c9a7abbde95c900d89fc5f2ed825e9afa3.tar.gz
budget-a8e943c9a7abbde95c900d89fc5f2ed825e9afa3.tar.bz2
budget-a8e943c9a7abbde95c900d89fc5f2ed825e9afa3.zip
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.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,
}