aboutsummaryrefslogtreecommitdiff
path: root/src/mail.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/mail.rs')
-rw-r--r--src/mail.rs126
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
}