website/api/src/email.rs

106 lines
3 KiB
Rust

use lettre::message::Mailbox;
use lettre::transport::smtp::client::Tls;
use lettre::{Message, SmtpTransport, Transport};
pub fn send_notification(
id: i64,
question: &str,
notify_email: &str,
mail_domain: &str,
reply_domain: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let truncated = if question.len() > 50 {
format!("{}...", &question[..50])
} else {
question.to_string()
};
let from: Mailbox = format!("Q&A <qa@{mail_domain}>").parse()?;
let reply_to: Mailbox = format!("qa@{reply_domain}").parse()?;
let to: Mailbox = notify_email.parse()?;
let email = Message::builder()
.from(from)
.reply_to(reply_to)
.to(to)
.subject(format!("{id} - {truncated}"))
.body(question.to_string())?;
let mailer = SmtpTransport::builder_dangerous("localhost")
.tls(Tls::None)
.hello_name(lettre::transport::smtp::extension::ClientId::Domain(
mail_domain.to_string(),
))
.build();
mailer.send(&email)?;
Ok(())
}
pub fn extract_id_from_subject(subject: &str) -> Result<i64, Box<dyn std::error::Error>> {
let subject = subject.trim();
let start = subject
.char_indices()
.find_map(|(idx, ch)| ch.is_ascii_digit().then_some(idx))
.ok_or("Subject missing question id")?;
let digits: String = subject[start..]
.chars()
.take_while(|c| c.is_ascii_digit())
.collect();
if digits.is_empty() {
return Err("Subject missing numeric question id".into());
}
let remainder = subject[start + digits.len()..].trim_start();
if !(remainder.starts_with('-') || remainder.starts_with(':')) {
return Err("Subject missing separator after question id".into());
}
Ok(digits.parse()?)
}
pub fn extract_plain_text_body(contents: &str) -> String {
let normalized = contents.replace("\r\n", "\n");
let body = if let Some((headers, body)) = normalized.split_once("\n\n") {
if headers.lines().any(|line| line.contains(':')) {
body
} else {
normalized.as_str()
}
} else {
normalized.as_str()
};
strip_quoted_text(body)
}
pub fn strip_quoted_text(body: &str) -> String {
let mut result = Vec::new();
for line in body.lines() {
if line.starts_with('>') {
continue;
}
if line.starts_with("On ") && line.ends_with("wrote:") {
break;
}
result.push(line);
}
result.join("\n").trim().to_string()
}
#[cfg(test)]
mod tests {
use super::{extract_id_from_subject, extract_plain_text_body};
#[test]
fn extracts_id_from_subject() {
assert_eq!(extract_id_from_subject("Re: 42 - Hello").unwrap(), 42);
}
#[test]
fn extracts_plain_text_from_raw_email() {
let raw = "Subject: Q&A #42: Hello\r\nFrom: Jet <jet@example.com>\r\n\r\nThis is the answer.\r\n\r\nOn earlier mail wrote:\r\n> quoted";
assert_eq!(extract_plain_text_body(raw), "This is the answer.");
}
}