Files
pl_mail/migrations
Paul LütznerandClaude Opus 5 561fc3f372 feat(mail): a message that bounced now says so, on the message that bounced
plMail could tell you a message had been READ. It could not tell you the
opposite. A bounce arrived in the Inbox as a machine-generated mail
titled "Undelivered Mail Returned to Sender" whose body is an SMTP
transcript, and the message it was about went on sitting in Sent saying
"Sent" — which at that point is a lie. The one delivery outcome a person
has to act on was the one outcome attached to nothing.

The structure was already here. A DSN is ReadReceiptCorrelator's twin:
same multipart/report envelope, same Original-Message-ID lookup, same
"stamp the sent row" payoff. BounceCorrelator is a separate service
anyway, because the two disagree on every judgement that matters.

ONLY `Action: failed`

A DSN reports four things and three of them are not bounces. `delayed`
in particular is a message STILL IN FLIGHT — Postfix emits one after
four hours and keeps trying for five days — and stamping it as
undelivered would tell the user their mail failed at the exact moment it
is being retried, with nothing to clear the stamp when it went through.
`delivered` and `relayed` are good news and set nothing.

THE BOUNCE IS NOT FILED AWAY

A read receipt is noise once correlated and gets marked read and taken
off the Inbox. A bounce stays exactly where it landed, unread. Its body
is routinely the only readable statement of what went wrong, and a
failure the user may have to act on is not something to tidy up on their
behalf.

NOTHING IS CLASSIFIED

The status is stored as the reporting MTA wrote it — "5.1.1", "4.4.7" —
and never reduced to a boolean "permanent". No address is disabled,
nothing is removed from a contact list, no resend is attempted. plMail
records what one server said about one delivery attempt. Deciding an
address is dead on that basis is a conclusion this codebase has been
wrong about before, and the sweep that led here said so repeatedly.

A BOUNCE WAS BEING READ AS A READ RECEIPT

Found by the first fixture written against this, and reachable before
it. Both are multipart/report and both carry Original-Message-ID, so a
DSN satisfied ReadReceiptCorrelator's body fallback, got correlated
there, and put "Read at …" on a message that was never delivered — not
merely missing information but the precise opposite of the truth.

The two field blocks have mutually exclusive discriminators, so that is
what tells them apart now: `Disposition:` is an MDN, `Action:` is a DSN.
Both arms of the detection refuse a delivery-status report first.

ON THE DEMO

The demo seeds a reply sent to a typo'd domain and the MAILER-DAEMON
answer four minutes later, correlated by the real BounceCorrelator
rather than by writing the columns directly. A demo that stamps the
result itself would stay green while the feature broke; this way the red
panel disappearing from the demo IS the alarm.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 00:52:32 +02:00
..
2026-07-02 13:12:28 +02:00
2026-07-02 13:12:28 +02:00
2026-07-02 13:12:28 +02:00
2026-07-02 13:12:28 +02:00
2026-07-02 13:12:28 +02:00
2026-07-02 13:12:28 +02:00
2026-07-02 13:12:28 +02:00
2026-07-02 13:12:28 +02:00
2026-07-14 12:02:21 +02:00
2026-07-16 12:59:19 +02:00
2026-07-17 10:34:32 +02:00
2026-07-17 12:47:20 +02:00
2026-07-17 18:19:47 +02:00
2026-07-17 18:19:47 +02:00
2026-07-19 20:38:41 +02:00
2026-07-19 20:38:41 +02:00
2026-07-20 10:56:25 +02:00
2026-07-20 10:56:25 +02:00
2026-07-21 13:40:57 +02:00
2026-07-23 13:53:37 +02:00
2026-07-23 14:17:49 +02:00
2026-07-24 13:56:18 +02:00
2026-07-24 13:56:18 +02:00
2026-07-24 22:26:19 +02:00
2026-07-25 10:47:50 +02:00
2026-07-25 10:47:50 +02:00
2026-07-26 10:58:31 +02:00
2026-07-27 12:15:24 +02:00
2026-07-27 13:10:36 +02:00
2026-07-27 13:10:36 +02:00
2026-07-28 00:26:47 +02:00
2026-07-28 12:33:46 +02:00
2026-07-28 13:37:30 +02:00
2026-07-28 22:25:00 +02:00
2fa
2026-07-31 07:18:26 +02:00