Skip to content

Idempotency

SimpleQ collapses duplicate publishes at the API boundary. Set idempotencyKey on publish, and any later publish with the same key on the same queue returns the existing job — same job ID, same payload, no duplicate work queued. Your publisher becomes safe to retry without coordination.

How to use idempotencyKey

Pass idempotencyKey alongside payload on publish:

bash
curl -X POST "$SQ_API_URL/v1/queues/my-queue/jobs" \
  -H "Authorization: Bearer $SIMPLEQ_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "idempotencyKey": "welcome-email-user-123",
    "payload": { "userId": "123" }
  }'
js
import { SimpleQ } from '@simpleq/sdk';

const simpleq = new SimpleQ({ apiKey: process.env.SIMPLEQ_API_KEY });

const job = await simpleq.publish('my-queue', {
  idempotencyKey: 'welcome-email-user-123',
  payload: { userId: '123' },
});
python
import os, httpx

res = httpx.post(
    f"{os.environ['SQ_API_URL']}/v1/queues/my-queue/jobs",
    headers={"authorization": f"Bearer {os.environ['SIMPLEQ_API_KEY']}"},
    json={
        "idempotencyKey": "welcome-email-user-123",
        "payload": {"userId": "123"},
    },
)
job = res.json()

Use a key that's unique to the business action you're triggering:

  • welcome-email-user-{userId} — sending the welcome email exactly once per user
  • summarize-doc-{docId}-v{revision} — summarizing a specific document revision
  • daily-digest-{userId}-{YYYY-MM-DD} — daily-cadence work

The response always returns the canonical job (the first one, on a duplicate publish), so you can read the job ID and poll its status without needing to track dedup state on your end.

Dedupe window

Deduplication lasts for the job's retention lifetime — 72 hours. Once the original job ages out of retention, the same key creates a fresh job. Date-scoped keys (like the daily-digest pattern above) sidestep this entirely.

Why this matters at the boundary

A reliable publisher is one that can safely retry: a network blip, an app restart mid-request, an ambiguous timeout. With idempotencyKey in place, every one of those retries is harmless — SimpleQ collapses the duplicates and returns the existing job. Your client code doesn't need to remember what it already sent, and you never accidentally fire the same business action twice because the network burped.

The TypeScript SDK applies this automatically: simpleq.publish() attaches a generated idempotencyKey and reuses it across its built-in retries, so a retried publish can never create a duplicate job. Pass your own key (as above) when you want business-level dedupe across separate calls.

Reliable transport, so your product can move

The publish boundary is where reliability has to start: if the entry point isn't safe to retry, nothing downstream can recover cleanly. With idempotencyKey collapsing duplicates and durable delivery handling everything that comes after — retries on failure, defer on backpressure, dead-letter as a last resort — your publisher and your workers both have a stable, predictable foundation. You spend your time on what each job does, not on building dedup layers around the transport.