withIdempotency
Deduplicate sends within a time window
withIdempotency caches the result of a send and replays it for matching keys instead of re-sending. On the first send for a given key, the pipeline runs normally and the result is stored. On subsequent sends with the same key within the TTL, the cached result is returned and the downstream pipeline never runs.
Failures are not cached — a thrown error leaves the slot empty so the next attempt is treated as fresh.
import { withIdempotency } from '@betternotify/core/middlewares';
import { inMemoryIdempotencyStore } from '@betternotify/core/stores';
const orderConfirmation = rpc
.email()
.input(z.object({ orderId: z.string(), email: z.string() }))
.subject(({ input }) => `Order ${input.orderId} confirmed`)
.template(adapter)
.use(withIdempotency({
store: inMemoryIdempotencyStore(),
key: ({ input }) => `order:${input.orderId}`,
ttl: 24 * 60 * 60_000,
}));Options
Prop
Type
Key design
The key is explicit by design — silently deduplicating payload-equal sends is rarely the intended behavior. Derive keys from business identifiers:
withIdempotency({
store,
key: ({ input }) => `invoice:${input.invoiceId}`,
ttl: 60 * 60_000,
})Two concurrent first-time sends with the same key will both run the pipeline. A future setIfAbsent addition to the store interface can close this gap.