* [PATCH proxmox v2 1/1] sendmail: add additional header map
@ 2026-06-16 9:54 Nicolas Frey
0 siblings, 0 replies; only message in thread
From: Nicolas Frey @ 2026-06-16 9:54 UTC (permalink / raw)
To: pve-devel
with corresponding methods to add them.
this adds a new enum `MailHeader` in order to have a controlled set of
headers (currently 'Reply-To', 'In-Reply-To', 'Cc', 'Bcc', as they
seemed sensible and might get used more often)
if later down the line it is necessary to add arbitrary headers, the
enum can be expanded to a `Custom(String)` variant, though this would
explicitly need to point out that the caller is responsible for the
overlap/correctness.
Signed-off-by: Nicolas Frey <n.frey@proxmox.com>
---
Notes:
Changes since v1 (thanks Shannon!):
* use a limited set of headers restricted by an enum
* check is_ascii on the header bodies and correctly encode them
* use BTreeMap instead of HashMap, as it is a small set of Headers,
improving the memory footprint and performance
proxmox-sendmail/src/lib.rs | 75 +++++++++++++++++++++++++++++++++++++
1 file changed, 75 insertions(+)
diff --git a/proxmox-sendmail/src/lib.rs b/proxmox-sendmail/src/lib.rs
index db751305..5a04199b 100644
--- a/proxmox-sendmail/src/lib.rs
+++ b/proxmox-sendmail/src/lib.rs
@@ -3,6 +3,8 @@
//! and alternative html parts to one or multiple receivers via ``sendmail``.
//!
+use std::collections::BTreeMap;
+use std::fmt::Display;
use std::io::Write;
use std::process::{Command, Stdio};
@@ -236,6 +238,30 @@ impl Attachment<'_> {
}
}
+/// List of headers which can be set as additional headers in an email to be sent by proxmox-sendmail.
+/// This enum may contain any standard headers found in the list of
+/// [IANA Message Headers](https://www.iana.org/assignments/message-headers/message-headers.xhtml)
+/// with Protocal "mail"
+#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq)]
+pub enum MailHeader {
+ ReplyTo,
+ InReplyTo,
+ Cc,
+ Bcc,
+}
+
+impl Display for MailHeader {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let name = match self {
+ MailHeader::ReplyTo => "Reply-To",
+ MailHeader::InReplyTo => "In-Reply-To",
+ MailHeader::Cc => "Cc",
+ MailHeader::Bcc => "Bcc",
+ };
+ write!(f, "{name}")
+ }
+}
+
/// This struct is used to define mails that are to be sent via the `sendmail` command.
pub struct Mail<'a> {
mail_author: String,
@@ -247,6 +273,7 @@ pub struct Mail<'a> {
attachments: Vec<Attachment<'a>>,
mask_participants: bool,
noreply: Option<Recipient>,
+ additional_headers: BTreeMap<MailHeader, &'a str>,
}
impl<'a> Mail<'a> {
@@ -265,6 +292,7 @@ impl<'a> Mail<'a> {
attachments: Vec::new(),
mask_participants: true,
noreply: None,
+ additional_headers: BTreeMap::new(),
}
}
@@ -393,6 +421,18 @@ impl<'a> Mail<'a> {
self
}
+ /// Set an additional header that is listed in [`MailHeader`] with its corresponding body.
+ pub fn set_header(&mut self, header: MailHeader, body: &'a str) {
+ self.additional_headers.insert(header, body);
+ }
+
+ /// Builder-style method to set an additional header that is listed in [`MailHeader`] with its
+ /// corresponding body.
+ pub fn with_header(mut self, header: MailHeader, body: &'a str) -> Self {
+ self.set_header(header, body);
+ self
+ }
+
/// Sends the email. This will fail if no recipients have been added.
///
/// Note: An `Auto-Submitted: auto-generated` header is added to avoid triggering OOO and
@@ -534,6 +574,7 @@ impl<'a> Mail<'a> {
|| !self.mail_author.is_ascii()
|| !self.body_txt.is_ascii()
|| encoded_to
+ || self.additional_headers.iter().any(|(_, b)| !b.is_ascii())
{
header.push_str("MIME-Version: 1.0\n");
}
@@ -584,6 +625,15 @@ impl<'a> Mail<'a> {
let rfc2822_date = proxmox_time::epoch_to_rfc2822(now)
.with_context(|| "could not convert epoch to rfc2822 date")?;
writeln!(header, "Date: {rfc2822_date}")?;
+
+ for (h, b) in &self.additional_headers {
+ if !b.is_ascii() {
+ writeln!(header, "{h}: =?utf-8?B?{}?=", proxmox_base64::encode(b))?;
+ } else {
+ writeln!(header, "{h}: {b}")?;
+ }
+ }
+
header.push_str("Auto-Submitted: auto-generated;\n");
Ok(header)
@@ -681,6 +731,31 @@ mod test {
assert!(result.is_err());
}
+ #[test]
+ fn additional_headers() {
+ let mail = Mail::new("Sender", "mail@example.com", "hi", "body")
+ .with_recipient_and_name("Jane Doe", "j.doe@example.com")
+ .with_header(MailHeader::ReplyTo, "mail@example.com")
+ .with_header(MailHeader::Cc, "cc1@example.com,cc2@example.com");
+ let body = mail.format_mail(0).expect("could not format mail");
+
+ assert_lines_equal_ignore_date(
+ &body,
+ r#"Subject: hi
+From: Sender <mail@example.com>
+To: Jane Doe <j.doe@example.com>
+Date: Thu, 01 Jan 1970 01:00:00 +0100
+Reply-To: mail@example.com
+Cc: cc1@example.com,cc2@example.com
+Auto-Submitted: auto-generated;
+Content-Type: text/plain;
+ charset="UTF-8"
+Content-Transfer-Encoding: 7bit
+
+body"#,
+ )
+ }
+
#[test]
fn simple_ascii_text_mail() {
let mail = Mail::new(
--
2.47.3
^ permalink raw reply related [flat|nested] only message in thread
only message in thread, other threads:[~2026-06-16 9:54 UTC | newest]
Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-16 9:54 [PATCH proxmox v2 1/1] sendmail: add additional header map Nicolas Frey
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox