# Queues

> Use Cloudflare Queues for asynchronous background work, producers, consumers, batching, retries, idempotent message handling, delayed processing, fan-out, and decoupling request latency. Use when Worker code should do something later without needing strict ordering.

- Skill: `zllovesuki/queues` (Agent Skill)
- Install (CLI): `npx skillmds@latest add zllovesuki/queues`
- Raw SKILL.md: https://api.skillmd.com/api/skills/zllovesuki/queues/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: zllovesuki (https://skillmd.com/u/zllovesuki)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/zllovesuki/queues

---

# Queues

Use this skill when a Worker should enqueue background work and return quickly.

## Best uses

- Sending emails or webhooks after a request.
- Generating reports or exports.
- Ingesting events.
- Fan-out processing.
- Cache warming.
- Non-blocking AI or embedding jobs.

Use Workflows instead for ordered, inspectable, multi-step processes. Use Durable Objects instead for realtime coordination or shared progress state.

## Producer pattern

```ts
export interface Env {
  JOBS: Queue<JobMessage>;
}

type JobMessage = {
  jobId: string;
  tenantId: string;
  kind: "send-email" | "generate-export";
  payload: unknown;
};

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const job: JobMessage = {
      jobId: crypto.randomUUID(),
      tenantId: "tenant_123",
      kind: "generate-export",
      payload: { requestedAt: new Date().toISOString() }
    };

    await env.JOBS.send(job);
    return Response.json({ queued: true, jobId: job.jobId }, { status: 202 });
  }
} satisfies ExportedHandler<Env>;
```

## Consumer pattern

```ts
export default {
  async queue(batch: MessageBatch<JobMessage>, env: Env, ctx: ExecutionContext): Promise<void> {
    for (const message of batch.messages) {
      try {
        await handleJob(message.body, env);
        message.ack();
      } catch (error) {
        console.error("queue job failed", {
          jobId: message.body.jobId,
          error: error instanceof Error ? error.message : String(error)
        });
        message.retry({ delaySeconds: 30 });
      }
    }
  }
} satisfies ExportedHandler<Env>;
```

## Wrangler config

```jsonc
{
  "queues": {
    "producers": [
      { "binding": "JOBS", "queue": "jobs" }
    ],
    "consumers": [
      {
        "queue": "jobs",
        "max_batch_size": 10,
        "max_batch_timeout": 5
      }
    ]
  }
}
```

## Message handling rules

- Treat delivery as at-least-once; make handlers idempotent.
- Do not assume global ordering.
- Keep messages small; store large payloads in R2 or D1 and send references.
- Include `jobId`, `tenantId`, `kind`, schema version, and creation time.
- Validate the message body before acting.
- Acknowledge only after required durable side effects complete.
- Add observability around retries, failures, and queue depth.

## Idempotent consumer example

```ts
async function handleJob(job: JobMessage, env: Env) {
  const claimed = await env.DB.prepare(
    `INSERT INTO processed_jobs (job_id, processed_at)
     VALUES (?, ?)
     ON CONFLICT(job_id) DO NOTHING
     RETURNING job_id`
  ).bind(job.jobId, new Date().toISOString()).first<{ job_id: string }>();

  if (!claimed) return; // already processed

  // Perform side effect here.
}
```

## Anti-patterns

- Queue message contains multi-megabyte file data instead of an R2 key.
- Business logic depends on exact ordering across many producers.
- Consumer does not record idempotency and duplicates cause harm.
- Queue is used as a hidden workflow engine with unclear progress state.

