From 572863e84ba4b96a2ec8fbca0cf466647d6cbae0 Mon Sep 17 00:00:00 2001 From: StefanSa Date: Wed, 4 Nov 2026 09:40:54 +0100 Subject: [PATCH] Preserve Milter SMTP reply code and text in delivery traces Populate the existing Code and Details trace fields for Milter ReplyCode actions without changing the SMTP response. Extend the existing session test to check the actual emitted event and complete reply. --- crates/smtp/src/inbound/milter/message.rs | 8 +++++ tests/src/smtp/inbound/milter.rs | 39 +++++++++++++++++++++-- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/crates/smtp/src/inbound/milter/message.rs b/crates/smtp/src/inbound/milter/message.rs index e910c2b..c92dbd9 100644 --- a/crates/smtp/src/inbound/milter/message.rs +++ b/crates/smtp/src/inbound/milter/message.rs @@ -93,6 +93,14 @@ impl Session { QueueId = queue_id, Id = milter.id.to_string(), Elapsed = time.elapsed(), + Code = match &action { + Action::ReplyCode { code, .. } => Some(code.to_vec().into_string()), + _ => None, + }, + Details = match &action { + Action::ReplyCode { text, .. } => Some(text.clone()), + _ => None, + }, ); return Err(match action { diff --git a/tests/src/smtp/inbound/milter.rs b/tests/src/smtp/inbound/milter.rs index fa51c4d..c915ec7 100644 --- a/tests/src/smtp/inbound/milter.rs +++ b/tests/src/smtp/inbound/milter.rs @@ -52,6 +52,7 @@ use tokio::{ net::{TcpListener, TcpStream}, sync::watch, }; +use trc::{Collector, EventType, Key, MilterEvent, ipc::subscriber::SubscriberBuilder}; #[derive(Debug, Deserialize)] struct HeaderTest { @@ -145,17 +146,51 @@ async fn milter_session() { .await; test.assert_no_events(); - // Test reply code + // Capture the actual event emitted by the SMTP session, not a reconstructed event. + let subscriber_id = "smtp-milter-reply-code".to_string(); + let subscriber = SubscriberBuilder::new(subscriber_id.clone()) + .set_interests([EventType::Milter(MilterEvent::ActionReplyCode)]) + .with_lossy(false); + Collector::union_interests(subscriber.interests.clone()); + let (_, mut events) = subscriber.register(); + tokio::time::timeout(Duration::from_secs(5), async { + while !Collector::get_subscribers().contains(&subscriber_id) { + tokio::time::sleep(Duration::from_millis(10)).await; + } + }) + .await + .expect("Milter trace subscriber was not registered"); + + // Test reply code: the SMTP response and its trace retain the Milter's code/text. session .send_message( "reply_code@doe.org", &["bill@foobar.org"], "test:no_dkim", - "321", + "321 test", ) .await; test.assert_no_events(); + let event = tokio::time::timeout(Duration::from_secs(5), async { + loop { + for event in events.recv().await.expect("Milter trace subscriber closed") { + if event.span_id() == Some(session.data.session_id) { + return event; + } + } + } + }) + .await + .expect("Milter reply-code event was not emitted"); + Collector::remove_subscriber(subscriber_id); + assert_eq!( + event.inner.typ, + EventType::Milter(MilterEvent::ActionReplyCode) + ); + assert_eq!(event.value_as_str(Key::Code), Some("321")); + assert_eq!(event.value_as_str(Key::Details), Some("test")); + // Test accept with header addition session .send_message( -- 2.53.0