LC-77-DEAD-LETTER: optional dead-letter IMAP folder for poison messages #203

Closed
opened 2026-05-25 21:22:17 +02:00 by longjacksonle · 0 comments

Background

LC-77 v1's poll loop marks every processed UID \Seen after one attempt, success or fail. A malformed message that drops on parse, resolve, or post stays in the polled mailbox (lets-chat doesn't delete polled mail; it only marks Seen). The operator's only diagnostic is the structured target=email_ingress::drop log line with the reason + detail + from + message_id fields.

Some operators may want a recoverable trail: a copy of the malformed message in a separate folder, where they can inspect what was rejected (sender, headers, body) without grepping logs.

When to act

If operators ask for recoverable rejected mail, OR if the structured log proves insufficient for diagnosing real-world drop cases.

Scope

Add an optional dead-letter IMAP folder that the poll loop COPIES failed messages into before marking the source UID \Seen.

  • New optional admin setting: imap_inbox_config.dead_letter_folder (e.g. "INBOX/lets-chat-rejected").
  • When unset (default): current v1 behavior (\Seen-after-attempt only).
  • When set: on any ProcessOutcome::Dropped, before marking \Seen, issue UID COPY <uid> <dead-letter-folder>. If the COPY fails (folder doesn't exist, IMAP server doesn't support it), log INFO and proceed; don't gate the \Seen mark on it.

Anti-scope

  • Doesn't change the no-bounce posture. Senders still get no feedback.
  • Doesn't try to auto-create the dead-letter folder. The operator must create it at their IMAP provider.
  • Doesn't dead-letter attachment-drops (those are non-fatal to the parent message; the parent posts normally). Only message-level drops with a DropReason go to the dead-letter folder.

Test requirements

  • Mock the IMAP COPY command (or use a greenmail fixture); confirm a drop case both COPIES and STORE +Seens.
  • Confirm a successful-post case does NOT COPY.

References

  • docs/email-ingress.md "Not supported" section names this as LC-77-DEAD-LETTER.
  • The LC-77 commit-3 brainstorm explicitly chose at-least-once + always-\Seen over dead-letter for v1: "Lean (a) for v1: mark \Seen on every attempt, log failures structured with Message-ID + reason + sender, document that operators can configure their MTA to also Cc a dead-letter mailbox if they want recoverability."
## Background LC-77 v1's poll loop marks every processed UID `\Seen` after one attempt, success or fail. A malformed message that drops on parse, resolve, or post stays in the polled mailbox (lets-chat doesn't delete polled mail; it only marks Seen). The operator's only diagnostic is the structured `target=email_ingress::drop` log line with the `reason` + `detail` + `from` + `message_id` fields. Some operators may want a recoverable trail: a copy of the malformed message in a separate folder, where they can inspect what was rejected (sender, headers, body) without grepping logs. ## When to act If operators ask for recoverable rejected mail, OR if the structured log proves insufficient for diagnosing real-world drop cases. ## Scope Add an optional dead-letter IMAP folder that the poll loop COPIES failed messages into before marking the source UID `\Seen`. - New optional admin setting: `imap_inbox_config.dead_letter_folder` (e.g. `"INBOX/lets-chat-rejected"`). - When unset (default): current v1 behavior (`\Seen`-after-attempt only). - When set: on any `ProcessOutcome::Dropped`, before marking `\Seen`, issue `UID COPY <uid> <dead-letter-folder>`. If the COPY fails (folder doesn't exist, IMAP server doesn't support it), log INFO and proceed; don't gate the `\Seen` mark on it. ## Anti-scope - Doesn't change the no-bounce posture. Senders still get no feedback. - Doesn't try to auto-create the dead-letter folder. The operator must create it at their IMAP provider. - Doesn't dead-letter attachment-drops (those are non-fatal to the parent message; the parent posts normally). Only message-level drops with a `DropReason` go to the dead-letter folder. ## Test requirements - Mock the IMAP COPY command (or use a greenmail fixture); confirm a drop case both COPIES and STORE +Seens. - Confirm a successful-post case does NOT COPY. ## References - `docs/email-ingress.md` "Not supported" section names this as `LC-77-DEAD-LETTER`. - The LC-77 commit-3 brainstorm explicitly chose at-least-once + always-`\Seen` over dead-letter for v1: "Lean (a) for v1: mark \Seen on every attempt, log failures structured with Message-ID + reason + sender, document that operators can configure their MTA to also Cc a dead-letter mailbox if they want recoverability."
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat#203
No description provided.