Skip to content

fix: don't let token-bearing emails linger in the durable store#65

Merged
andregoepel merged 1 commit into
mainfrom
bugfix/bound-email-durability
Jul 4, 2026
Merged

fix: don't let token-bearing emails linger in the durable store#65
andregoepel merged 1 commit into
mainfrom
bugfix/bound-email-durability

Conversation

@andregoepel

Copy link
Copy Markdown
Owner

Closes #55 (F6 — token-bearing email bodies & recipient PII persisted in the Wolverine outbox).

What

Identity emails (confirmation / password-reset links and codes) are sent through the Wolverine durable outbox/inbox, so each MailMessage body — a short-lived auth token plus the recipient's email — is persisted in Postgres. Successful sends are removed promptly, but failed messages were dead-lettered and lingered indefinitely, and a stuck message could sit across an outage. This bounds that:

  • [DeliverWithin] on MailMessage — an undelivered message is discarded after 1 hour (far beyond normal sub-second delivery, so the happy path is unaffected) instead of persisting as a token-bearing row.
  • Handler Configure(HandlerChain) convention — on failure, retry a few times (100ms / 1s / 5s) then Discard rather than move the body to the dead-letter table.

Net: no MailMessage body outlives its usefulness or accumulates in dead letters; successful sends were already auto-removed.

Trade-off

Discarding on permanent failure means a failing SMTP config silently drops identity emails (Wolverine still logs each discard) rather than leaving dead-letter rows to inspect. That's the right call for token/PII payloads, and the synchronous admin Email Settings → Send test path (which surfaces SMTP errors directly in the UI, bypassing the bus) remains the way to diagnose SMTP problems.

Scope note

This is the in-code half of the remediation. Restricting DB/backup access and overall retention are operational; the token TTL/single-use itself lives in marten-identity.

Design notes

The Wolverine 6.16 APIs used ([DeliverWithin(seconds)], chain.OnAnyException().RetryWithCooldown(...).Then.Discard(), the static Configure(HandlerChain) convention) were verified against the package's own API surface and a compile probe before use.

Tests

MailMessageDurabilityTests guards that the [DeliverWithin] cap and the Configure convention method stay present and correctly shaped — these are framework-driven controls, so a silent removal would re-open the issue with no compile error. Existing handler tests unchanged.

Verification

  • dotnet build -c Release — 0 errors.
  • dotnet test -c Release — 80/80 passing (3 new).

Identity emails flow through the Wolverine durable outbox/inbox, so a
MailMessage body — a short-lived auth token (confirmation / reset) plus
the recipient's address — is persisted in Postgres. Successful sends are
removed promptly, but failed messages were dead-lettered and lingered,
and a stuck message could sit across an outage.

Bound the lifetime of that PII/token payload:
- [DeliverWithin] on MailMessage discards an undelivered message after an
  hour instead of persisting it (far beyond normal sub-second delivery).
- The handler's Configure convention retries a few times on failure, then
  discards rather than moving the token-bearing body to the dead-letter
  table. The synchronous admin "send test" path remains for diagnosing
  SMTP problems.

Closes #55
@andregoepel
andregoepel merged commit afe4598 into main Jul 4, 2026
3 checks passed
@andregoepel
andregoepel deleted the bugfix/bound-email-durability branch July 5, 2026 03:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Security] Token-bearing email bodies & recipient PII persisted in Wolverine outbox (F6)

1 participant