feat: update email logic to subject not user +

This commit is contained in:
Jet 2026-03-25 23:25:49 -07:00
parent ede986080a
commit 6a652ed4f3
No known key found for this signature in database
5 changed files with 136 additions and 27 deletions

View file

@ -7,6 +7,7 @@ pub fn send_notification(
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])
@ -15,14 +16,14 @@ pub fn send_notification(
};
let from: Mailbox = format!("Q&A <qa@{mail_domain}>").parse()?;
let reply_to: Mailbox = format!("qa+{id}@{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!("Q&A #{id}: {truncated}"))
.subject(format!("{id} - {truncated}"))
.body(question.to_string())?;
let mailer = SmtpTransport::builder_dangerous("localhost")
@ -36,6 +37,44 @@ pub fn send_notification(
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() {
@ -50,16 +89,18 @@ pub fn strip_quoted_text(body: &str) -> String {
result.join("\n").trim().to_string()
}
pub fn extract_id_from_address(to: &str) -> Result<i64, Box<dyn std::error::Error>> {
let addr = to.trim();
let addr = if let Some(start) = addr.find('<') {
&addr[start + 1..addr.find('>').unwrap_or(addr.len())]
} else {
addr
};
let local = addr.split('@').next().unwrap_or("");
let id_str = local
.strip_prefix("qa+")
.ok_or("No qa+ prefix in address")?;
Ok(id_str.parse()?)
#[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.");
}
}