{{ message }}
Repository navigation
fix(inbound): key a stored raw on its Message-ID so a relay retry is idempotent - #169
Merged
Merged
Conversation
…idempotent
A relayed message that is delivered twice (the normal outcome of a relay
retry after a timeout) was stored twice: intake gave every accepted message
a fresh `inbound/${Date.now()}-${newId()}.eml` key, so two posts of the same
message produced two different keys, two queue messages, and two mailbox
copies with different ids and the same RFC Message-ID.
Derive the storage key from the RFC Message-ID, scoped by mailbox, and fall
back to a content hash when the header is absent. The inbound queue consumer
already dedupes by `(mailboxId, rawR2Key)`, so a retry now overwrites the
same object and is skipped instead of duplicating the mailbox copy. Scoping
by mailbox keeps the message id unique when the same message reaches two
mailboxes.
Fixes hieunc229#167
MinhOmega
added a commit
to MinhOmega/Cmail
that referenced
this pull request
Oct 23, 2026
…y retries are idempotent
This was referenced Oct 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Fixes #167
Summary
A relayed message that is delivered twice — the normal outcome of a relay retry after a timeout — was stored twice.
intakeIncomingMailgave every accepted message a freshinbound/${Date.now()}-${newId()}.emlkey, so two posts of the same message produced two different keys, two queue messages, and two mailbox copies with different ids and the same RFCMessage-ID.Change
src/lib/email/intake.tsnow derives the storage key from the RFCMessage-ID(scoped by mailbox), falling back to a content hash when the header is absent. The inbound queue consumer already dedupes by(mailboxId, rawR2Key), so a retry now overwrites the same object and is skipped instead of duplicating the mailbox copy.inboundStorageKey/extractMessageIdHeaderinsrc/lib/email/inbound-id.ts— a lightweight header read (case-insensitive, folded lines unfolded), no full MIME parse.mailboxIdkeepsmessages.idunique when the same message reaches two mailboxes (it is derived from the key).Testing
tests/inbound-key.test.mjs(bundled with esbuild): 8/8 pass — header extraction (case-insensitive, folded, absent), key stability per mailbox, per-mailbox and per-Message-ID differences, and the content-hash fallback.npx tsc --noEmitclean.vp lintcannot run on this checkout (Node 20.19.2;vp/oxlint needs Node 22 fornode:fs/promises.glob). Unrelated pre-existing environment limitation.Other comments
This change was written by an AI coding agent (OpenCode).