feat(resend): Idempotency-Key header support - #54
Conversation
|
@yonatangross is attempting to deploy a commit to the Vercel Labs Team on Vercel. A member of the Team first needs to authorize it. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c03d24fabb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
5b2b5e3 to
35a86b4
Compare
Implement idempotency key handling for POST /emails and POST /emails/batch, matching real Resend API behavior: - Same key + same payload returns cached response (200) - Same key + different payload returns 409 invalid_idempotent_request - No key sends normally without dedup - Key not exposed in GET /emails or GET /emails/:id responses - Batch fingerprints stored via store.setData() for payload comparison - Note: real Resend expires keys after 24h; emulator keeps them for session 4 files changed, +208 lines: entities.ts — add idempotency_key: string | null field store.ts — add idempotency_key to collection index for O(1) lookup emails.ts — idempotency logic for both /emails and /emails/batch resend.test.ts — 8 test cases covering dedup, 409, batch, no-leak Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The idempotency check for both /emails and /emails/batch only compared from, to, and subject fields. A retry reusing the same Idempotency-Key with different html, text, cc, bcc, reply_to, headers, tags, or scheduled_at would silently return the cached response instead of 409. Now uses a deterministic fingerprint of all request fields stored via the data store, matching the real Resend API behavior. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3d3ea10 to
db84324
Compare
|
Rebased onto current This is now used in production CI at Yonatan-HQ/platform: our emulate test tier builds an image from a fork-carry of v0.10.0 plus this PR (https://github.com/Yonatan-HQ/platform/pull/10686), and the tests that were |
Summary
Idempotency-Keyheader support toPOST /emailsandPOST /emails/batch409 invalid_idempotent_request, matching real Resend error handlingGET /emailsorGET /emails/:idresponsesChanges
entities.tsidempotency_key: string | nulltoResendEmailstore.tsidempotency_keyto collection index for O(1) lookuproutes/emails.ts/emailsand/emails/batchresend.test.tsHow it works
Single email (
POST /emails): ExtractIdempotency-Keyheader →findOneByindexed lookup → comparefrom/to/subject→ return cached or 409.Batch (
POST /emails/batch): Same pattern but uses a deterministic fingerprint stored viastore.setData()for payload comparison across the batch.TTL: Real Resend expires keys after 24h. The emulator keeps them for the session lifetime (documented in code comments as intentional divergence — emulator state is ephemeral anyway).
Test plan
invalid_idempotent_requestidempotency_keynot exposed in GET responsesto: "x"normalizes same asto: ["x"]for comparison🤖 Generated with Claude Code