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> { let truncated = if question.len() > 50 { format!("{}...", &question[..50]) } else { question.to_string() }; let from: Mailbox = format!("Q&A ").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> { 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 \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."); } }