156 lines
3.8 KiB
Rust
156 lines
3.8 KiB
Rust
#[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<u8>,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct SendableMessage {
|
|
pub from: String,
|
|
|
|
pub to: Vec<String>,
|
|
|
|
pub cc: Vec<String>,
|
|
|
|
pub bcc: Vec<String>,
|
|
|
|
pub reply_to: Option<String>,
|
|
|
|
pub organization: Option<String>,
|
|
|
|
pub subject: String,
|
|
|
|
pub text_body: String,
|
|
|
|
pub html_body: Option<String>,
|
|
|
|
pub in_reply_to: Option<String>,
|
|
|
|
pub references: Option<String>,
|
|
|
|
pub read_receipt_to: Option<String>,
|
|
|
|
pub priority: String,
|
|
|
|
pub extra_headers: Vec<(String, String)>,
|
|
|
|
pub attachments: Vec<AttachmentData>,
|
|
|
|
pub pgp: Option<PgpSendConfig>,
|
|
|
|
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<String>,
|
|
pub passphrase: String,
|
|
pub recipient_public_keys: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct AttachmentData {
|
|
pub filename: String,
|
|
|
|
pub content_type: String,
|
|
|
|
pub data: Vec<u8>,
|
|
|
|
pub content_id: Option<String>,
|
|
}
|
|
|
|
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::<String>()
|
|
.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::<String>()
|
|
.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
|
|
}
|