Skip to content

feat: add per-email idempotency keys to batch send - #40

Merged
bjarn merged 1 commit into
feat/messages-resendfrom
feat/batch-idempotency-keys
Oct 18, 2026
Merged

bjarn merged 1 commit into
feat/messages-resendfrom
feat/batch-idempotency-keys

Conversation

@bjarn

@bjarn bjarn commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

Stacked on #39 — merge that first.

What changed

Non-breaking; suggested bump: minor.

  • New exported type BatchEmailMessage: an EmailMessage with an optional per-email idempotency_key. emails.sendBatch() now accepts (BatchEmailMessage | EmailBuilder)[]. EmailMessage[] still works as before.
  • Each key goes in its own batch item as idempotency_key. It is separate from the request-level idempotencyKey option, which still sends the Idempotency-Key header.
  • If an email with the same key and content was accepted in the last 24 hours, the API returns the original result and does not send it again. A retried batch therefore sends only the emails that were not accepted yet.
  • Builders get a key by spreading their wire format: { ...builder.build(), idempotency_key }. No builder setter was added, so builder.send() and emails.send() stay unchanged and EmailMessage (the single-send type) does not get the field.
  • The API validates the keys (1-255 characters, unique within a batch, 409 Conflict when a key is reused with different content). The SDK adds no client-side checks.
  • Docblock and README: on a mid-batch failure, error.details.accepted lists the emails that were already accepted ({ index, message_id }).

Usage

await lettermint.emails.sendBatch(
  [
    { ...invoice, idempotency_key: `invoice-${invoice.id}` },
    { ...welcome.to('john@example.com').build(), idempotency_key: 'welcome-john' },
  ],
  { idempotencyKey: 'batch-2026-10-18' } // optional, for the request as a whole
);

Checks

Run locally on Node 26.5.0:

  • npm run lint: passed (33 files, no fixes)
  • npm run type-check: passed
  • npm run generate:check: generated files up to date (with the generator), and the header check passed without it
  • npm run test:coverage: 319 passed, 8 suites
  • npm run build: CJS, ESM and DTS builds succeeded
  • npm run test:smoke: ESM import, CJS require and type declarations ok

Not run locally

  • The Node 20, 22 and 24 CI matrix. Only Node 26 was run locally.

Suggested release tag

v3.2.0 if released together with #39, otherwise the next minor after it.

🤖 Generated with Claude Code

@bjarn
bjarn requested a review from a team as a code owner October 18, 2026 15:19
Add the `BatchEmailMessage` type, an `EmailMessage` with an optional
`idempotency_key`, and accept it in `emails.sendBatch()`. Each key is sent
in its own batch item, separate from the request-level `idempotencyKey`
option. A retried batch returns the original result of every email that
was already accepted and sends only the rest. Builders get a key by
spreading `builder.build()`. `send()` is unchanged.

Document that a failed batch lists the emails accepted before the failure
in `error.details.accepted`.

Co-Authored-By: Claude <noreply@anthropic.com>
@bjarn
bjarn force-pushed the feat/batch-idempotency-keys branch from 2ebf52a to e7ca763 Compare October 18, 2026 16:08
@bjarn
bjarn added this pull request to stack #41 October 18, 2026 17:20
@bjarn
bjarn merged commit b4fe74d into main Oct 18, 2026
4 checks passed
@bjarn
bjarn deleted the feat/batch-idempotency-keys branch October 18, 2026 20:36
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.

2 participants