#[derive(Debug, Clone)] pub struct SmtpCredentials { pub host: String, pub port: u16, pub tls: bool, pub tls_verify: bool, pub email: String, pub password: String, } #[derive(Debug, Clone)] pub struct SmtpSendResult { pub message_id: String, pub rfc822: Vec, } #[derive(Clone)] pub struct SendableMessage { pub from: String, pub to: Vec, pub cc: Vec, pub bcc: Vec, pub reply_to: Option, pub organization: Option, pub subject: String, pub text_body: String, pub html_body: Option, pub in_reply_to: Option, pub references: Option, pub read_receipt_to: Option, pub priority: String, pub extra_headers: Vec<(String, String)>, pub attachments: Vec, pub pgp: Option, pub max_message_size_bytes: usize, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum PgpMode { None, Sign, Encrypt, SignEncrypt, } #[derive(Clone)] pub struct PgpSendConfig { pub mode: PgpMode, pub inline: bool, pub secret_key_armor: Option, pub passphrase: String, pub recipient_public_keys: Vec, } #[derive(Debug, Clone)] pub struct AttachmentData { pub filename: String, pub content_type: String, pub data: Vec, pub content_id: Option, } pub fn apply_optional_headers( builder: lettre::message::MessageBuilder, message: &SendableMessage, ) -> lettre::message::MessageBuilder { use lettre::message::header::{HeaderName, HeaderValue}; let mut builder = apply_optional_header_values( builder, message.read_receipt_to.as_deref(), message.organization.as_deref(), &message.priority, ); for (name, value) in &message.extra_headers { if let Ok(name) = HeaderName::new_from_ascii(name.clone()) { let clean_value = value .chars() .map(|ch| if matches!(ch, '\r' | '\n') { ' ' } else { ch }) .collect::() .trim() .to_string(); if !clean_value.is_empty() { builder = builder.raw_header(HeaderValue::new(name, clean_value)); } } } builder } pub fn apply_optional_header_values( mut builder: lettre::message::MessageBuilder, read_receipt_to: Option<&str>, organization: Option<&str>, priority: &str, ) -> lettre::message::MessageBuilder { use lettre::message::header::{HeaderName, HeaderValue}; fn clean(value: &str) -> String { value .chars() .map(|ch| if matches!(ch, '\r' | '\n') { ' ' } else { ch }) .collect::() .trim() .to_string() } fn raw(name: &'static str, value: String) -> HeaderValue { HeaderValue::new(HeaderName::new_from_ascii_str(name), value) } if let Some(address) = read_receipt_to.map(clean).filter(|value| !value.is_empty()) { builder = builder.raw_header(raw("Disposition-Notification-To", address)); } if let Some(organization) = organization.map(clean).filter(|value| !value.is_empty()) { builder = builder.raw_header(raw("Organization", organization)); } let normalized_priority = priority.trim().to_ascii_lowercase(); match normalized_priority.as_str() { "high" => { builder = builder .raw_header(raw("Importance", "high".to_string())) .raw_header(raw("X-Priority", "1 (Highest)".to_string())); } "low" => { builder = builder .raw_header(raw("Importance", "low".to_string())) .raw_header(raw("X-Priority", "5 (Lowest)".to_string())); } _ => {} } builder }