first commit
RustBox CI / rustbox (push) Waiting to run

This commit is contained in:
Mateusz Gruszczyński committed 2026-10-29 15:24:59 +01:00
commit bc5fbd6ba9
180 files changed
+63750

No files matched your search

+167
View File
@@ -0,0 +1,167 @@
//! SMTP data types.
/// Parameters needed to establish an SMTP connection.
/// Passed explicitly to every trait method so the trait stays stateless.
#[derive(Debug, Clone)]
pub struct SmtpCredentials {
pub host: String,
pub port: u16,
pub tls: bool,
pub email: String,
pub password: String,
}
#[derive(Debug, Clone)]
pub struct SmtpSendResult {
pub message_id: String,
pub rfc822: Vec<u8>,
}
/// A message ready to be sent via SMTP.
#[derive(Clone)]
pub struct SendableMessage {
/// Sender email address.
pub from: String,
/// Primary recipients.
pub to: Vec<String>,
/// CC recipients.
pub cc: Vec<String>,
/// BCC recipients.
pub bcc: Vec<String>,
/// Optional Reply-To mailbox.
pub reply_to: Option<String>,
/// Optional organization label from the selected identity.
pub organization: Option<String>,
/// Subject line.
pub subject: String,
/// Plain-text body.
pub text_body: String,
/// Optional HTML body.
pub html_body: Option<String>,
/// In-Reply-To header value for threading.
pub in_reply_to: Option<String>,
/// References header value for threading.
pub references: Option<String>,
/// Address that should receive an MDN/read receipt request.
pub read_receipt_to: Option<String>,
/// Message priority: `high`, `normal`, or `low`.
pub priority: String,
/// Administrator-configured headers applied to every outgoing copy.
pub extra_headers: Vec<(String, String)>,
/// File attachments.
pub attachments: Vec<AttachmentData>,
/// Optional OpenPGP transformation applied to the MIME entity.
pub pgp: Option<PgpSendConfig>,
}
#[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>,
}
/// A single file attachment to include in an outgoing message.
#[derive(Debug, Clone)]
pub struct AttachmentData {
/// Filename as it should appear to the recipient.
pub filename: String,
/// MIME content type (e.g. "application/pdf").
pub content_type: String,
/// Raw file content.
pub data: Vec<u8>,
/// Optional Content-ID for inline images (referenced via `cid:` in HTML).
/// When set, the attachment is treated as an inline image rather than a
/// regular file attachment.
pub content_id: Option<String>,
}
/// Apply RustBox-managed optional headers shared by SMTP delivery and the
/// copy appended to IMAP Sent. Header names are static and values are stripped
/// of CR/LF before they reach lettre's raw header API.
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
}
/// Apply the optional outgoing headers without requiring a full SendableMessage.
/// This is also used for RFC822 draft snapshots appended to IMAP.
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
}