Issue Description
Delivery Status Notifications are generated with doubled angle brackets around the Message-ID:
Message-ID: <<18cad7807852b60b.f790fe4fd1ab9090.76e3c80726c2224c@mail.example.org>>
RFC 5322 defines msg-id = "<" id-left "@" id-right ">" with id-left = dot-atom-text; < is not valid atext, so this header is malformed. Consumers that key on Message-ID — threading, duplicate suppression, loop detection, log correlation — may fail to match it, and strict parsers reject it.
The message being bounced had a well-formed, single-bracketed Message-ID, so the doubling is introduced by the DSN generator rather than inherited from the original.
Probable cause: crates/smtp/src/queue/dsn.rs:339 wraps the id in angle brackets before handing it to the builder:
.message_id(format!("<{}@{}>", make_boundary("."), reporting_mta))
mail-builder then adds its own, unconditionally, in src/headers/message_id.rs:106:
output.write_all(b"<")?;
output.write_all(id.as_bytes())?;
output.write_all(b">")?;
MessageId takes the bare id and owns the delimiters, so the caller should not supply them. Dropping the brackets at the call site looks like it would fix it:
.message_id(format!("{}@{}", make_boundary("."), reporting_mta))
crates/http/src/form/mod.rs:160 pre-wraps in the same way before calling .message_id(). I have not observed that path in the wild, but it goes through the same builder.
For contrast, crates/imap/src/op/fetch.rs:881 also wraps in angle brackets, but that value is not passed to mail-builder, so it looks correct as written.
Expected Behavior
The DSN carries a single-bracketed, RFC 5322 conformant header:
Message-ID: <18cad7807852b60b.f790fe4fd1ab9090.76e3c80726c2224c@mail.example.org>
Expected Behavior
The header is emitted with doubled brackets, as shown above.
Both the Message-ID and the MIME boundary in the same DSN come from make_boundary, whose first field derives from SystemTime::now().as_nanos(). In the bounce I received they share a nanosecond-level prefix, which is what convinced me the header was generated by this code path rather than altered in transit:
Message-ID: 18cad7807852b60b.f790fe4fd1ab9090.76e3c80726c2224c
boundary: 18cad7807860371a_2cb9a36d49c2f6d7_88535f8daf886636
Reproduction Steps
- Send a message to an address that will hard-fail, so a DSN is generated.
- Inspect the raw source of the resulting bounce.
The Message-ID header shows doubled angle brackets.
Stalwart Version
v0.16.x
Installation Method
Docker
Database Backend
RocksDB
Blob Storage
RocksDB
Search Engine
Internal
Directory Backend
Internal
I have reviewed the documentation and FAQ and confirm that my issue is NOT addressed there.
on
I have searched this support forum (open and closed topics) and confirm this is not a duplicate.
on
I understand that topics in this category are triaged by a bot first but a human reply will follow up. If I’d prefer a human-only reply, I’ll add the no-ai tag to my topic.
on