mailsis_utils/transformers/
message_id.rs1use tracing::{debug, info};
9
10use crate::{EmailMessage, MessageTransformer, TransformFuture};
11
12pub struct MessageIdTransformer {
18 domain: String,
19}
20
21impl MessageIdTransformer {
22 pub fn new(domain: String) -> Self {
24 info!(domain = %domain, "Message-ID transformer initialized");
25 Self { domain }
26 }
27}
28
29impl MessageTransformer for MessageIdTransformer {
30 fn transform<'a>(&'a self, message: &'a mut EmailMessage) -> TransformFuture<'a> {
31 Box::pin(async move {
32 if let Some(existing_id) = message.header("Message-ID") {
33 let cleaned = existing_id
34 .strip_prefix('<')
35 .and_then(|s| s.strip_suffix('>'))
36 .unwrap_or(existing_id)
37 .to_string();
38 debug!(
39 old_id = %message.message_id,
40 mime_id = %cleaned,
41 "Syncing message_id from existing MIME header"
42 );
43 message.message_id = cleaned;
44 } else {
45 let value = format!("<{}@{}>", message.message_id, self.domain);
46 debug!(
47 message_id = %message.message_id,
48 "Injecting Message-ID header"
49 );
50 message.prepend_header("Message-ID", &value);
51 }
52 })
53 }
54
55 fn name(&self) -> &str {
56 "message_id"
57 }
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63
64 #[tokio::test]
65 async fn test_inject_message_id_when_missing() {
66 let transformer = MessageIdTransformer::new("example.com".to_string());
67 let mut message = EmailMessage::from_raw(
68 "sender@example.com",
69 "rcpt@example.com",
70 "Subject: Hello\r\n\r\nBody text",
71 );
72 let original_id = message.message_id.clone();
73
74 transformer.transform(&mut message).await;
75 message.rebuild();
76
77 assert!(message
78 .raw()
79 .starts_with(&format!("Message-ID: <{original_id}@example.com>\r\n")));
80 assert_eq!(message.message_id, original_id);
81 }
82
83 #[tokio::test]
84 async fn test_sync_message_id_from_existing_header() {
85 let transformer = MessageIdTransformer::new("example.com".to_string());
86 let mut message = EmailMessage::from_raw(
87 "sender@example.com",
88 "rcpt@example.com",
89 "Message-ID: <abc123@mail.example.com>\r\nSubject: Hello\r\n\r\nBody text",
90 );
91
92 transformer.transform(&mut message).await;
93
94 assert_eq!(message.message_id, "abc123@mail.example.com");
95 assert!(message.raw().starts_with("Message-ID:"));
97 }
98
99 #[tokio::test]
100 async fn test_sync_message_id_case_insensitive() {
101 let transformer = MessageIdTransformer::new("example.com".to_string());
102 let mut message = EmailMessage::from_raw(
103 "sender@example.com",
104 "rcpt@example.com",
105 "message-id: <lowercase@example.com>\r\nSubject: Test\r\n\r\nBody",
106 );
107
108 transformer.transform(&mut message).await;
109
110 assert_eq!(message.message_id, "lowercase@example.com");
111 }
112
113 #[tokio::test]
114 async fn test_inject_into_plain_text_body() {
115 let transformer = MessageIdTransformer::new("localhost".to_string());
116 let mut message =
117 EmailMessage::from_raw("sender@example.com", "rcpt@example.com", "Just plain text");
118 let original_id = message.message_id.clone();
119
120 transformer.transform(&mut message).await;
121 message.rebuild();
122
123 assert!(message
124 .raw()
125 .starts_with(&format!("Message-ID: <{original_id}@localhost>\r\n")));
126 assert!(message.raw().ends_with("Just plain text"));
127 }
128
129 #[tokio::test]
130 async fn test_apply_transformers() {
131 let transformers: Vec<Box<dyn MessageTransformer>> = vec![Box::new(
132 MessageIdTransformer::new("example.com".to_string()),
133 )];
134 let mut message = EmailMessage::from_raw(
135 "sender@example.com",
136 "rcpt@example.com",
137 "Subject: Test\r\n\r\nBody",
138 );
139
140 <MessageIdTransformer as MessageTransformer>::apply(&transformers, &mut message).await;
141
142 assert!(message.raw().contains("Message-ID:"));
143 }
144
145 #[tokio::test]
146 async fn test_message_id_without_angle_brackets() {
147 let transformer = MessageIdTransformer::new("example.com".to_string());
148 let mut message = EmailMessage::from_raw(
149 "sender@example.com",
150 "rcpt@example.com",
151 "Message-ID: bare-id@example.com\r\nSubject: Test\r\n\r\nBody",
152 );
153
154 transformer.transform(&mut message).await;
155
156 assert_eq!(message.message_id, "bare-id@example.com");
157 }
158}