# Blink Queue

> Background task queue and cron schedules. Enqueue tasks, named FIFO queues with parallelism, auto-retry. Requires Blink Backend (Pro+).

- Skill: `blink-new/blink-queue` (Agent Skill)
- Install (CLI): `npx skillmds@latest add blink-new/blink-queue`
- Raw SKILL.md: https://api.skillmd.com/api/skills/blink-new/blink-queue/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Productivity
- Author: blink-new (https://skillmd.com/u/blink-new)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/blink-new/blink-queue

---


## MCP Tools

`blink_queue_enqueue` · `blink_queue_schedule` · `blink_queue_list` · `blink_queue_stats` · `blink_queue_cancel` · `blink_queue_get` · `blink_queue_dlq_retry` · `blink_queue_create_queue`

## Getting Started

```bash
# Check backend is deployed (required for queue delivery)
blink backend status

# Enqueue a task
blink queue enqueue send-welcome-email --payload '{"to":"user@example.com"}'

# Create a cron schedule
blink queue schedule create daily-digest "0 9 * * *" --payload '{"reportType":"daily"}'

# List tasks
blink queue list --status pending

# View stats
blink queue stats
```

**Min SDK: `@blinkdotnew/sdk >= 2.9.0`**

## Critical Rules

1. **Backend required** — deploy backend first; queue delivery fails without it
2. **Write handler first** — add `POST /api/queue` to `backend/index.ts` BEFORE enqueueing
3. **Schedule name = taskName** — handler receives schedule `name` as `taskName`
4. **Deploy after handler changes** — `blink backend deploy`
5. **Return 2xx** — non-2xx triggers retry, then dead-letter queue

## Step 1 — Add Handler to `backend/index.ts`

```typescript
import { createClient } from '@blinkdotnew/sdk'

app.post('/api/queue', async (c) => {
  const blink = createClient({ projectId: c.env.BLINK_PROJECT_ID, secretKey: c.env.BLINK_SECRET_KEY })
  // Your queue URL is public. Verify every delivery came from Blink Queue before
  // acting on it — one check covers both enqueued tasks and cron ticks. Read the
  // RAW body: the signature is over the exact bytes, so parse only after verifying.
  const body = await c.req.text()
  const ok = await blink.queue.verify({
    signature: c.req.header('upstash-signature') ?? '',
    body,
    signingKey: c.env.BLINK_QUEUE_SIGNING_KEY,
    nextSigningKey: c.env.BLINK_QUEUE_SIGNING_KEY_NEXT,  // platform-injected; used during key rotation
  })
  if (!ok) return c.json({ error: 'invalid signature' }, 401)

  const { taskName, payload } = JSON.parse(body)

  switch (taskName) {
    case 'send-welcome-email':
      await sendEmail(payload.to, payload.displayName)
      return c.json({ ok: true })

    case 'daily-digest':
      await generateDigest(payload.reportType)
      return c.json({ ok: true })

    default:
      return c.json({ error: `Unknown task: ${taskName}` }, 400)
  }
})
```

`BLINK_QUEUE_SIGNING_KEY` is injected into your backend env automatically — you do
not set it. `verify()` returns `true`/`false` and throws only if the key is missing.

**Payload by source:**

| Source | Delivered body |
|--------|---------------|
| `blink.queue.enqueue(taskName, payload)` | `{ taskName, taskId, payload }` |
| `blink.queue.schedule(name, cron, payload)` | `{ taskName: name, payload }` (no taskId) |

## Step 2 — Deploy, Then Enqueue

```bash
blink backend deploy
blink queue enqueue send-welcome-email --payload '{"to":"user@example.com"}' --queue emails --delay 10s
```

## SDK Methods

```typescript
// Enqueue
await blink.queue.enqueue('send-welcome-email', { to: 'user@example.com' })
await blink.queue.enqueue('send-welcome-email', { to: 'user@example.com' }, {
  queue: 'emails', delay: '5m', retries: 3,
})

// Schedule — name IS the taskName
await blink.queue.schedule('daily-digest', '0 9 * * *', { type: 'daily' })
await blink.queue.schedule('daily-digest', '0 9 * * *', { type: 'daily' }, {
  timezone: 'America/New_York',
})

// Manage schedules
await blink.queue.pauseSchedule('daily-digest')
await blink.queue.resumeSchedule('daily-digest')
await blink.queue.deleteSchedule('daily-digest')

// Named queues (create once, idempotent)
await blink.queue.createQueue('emails', { parallelism: 10 })

// Inspect & manage
const tasks = await blink.queue.list({ status: 'pending', queue: 'emails' })
await blink.queue.cancel(taskId)
const dead = await blink.queue.listDead()
await blink.queue.retryDead(taskId)
const stats = await blink.queue.stats()
```

## Cron Patterns

| Cron | When |
|------|------|
| `0 9 * * *` | Daily at 9am |
| `0 * * * *` | Every hour |
| `*/15 * * * *` | Every 15 minutes |
| `0 9 * * 1` | Every Monday at 9am |

## Common Errors

| Error | Cause | Fix |
|-------|-------|-----|
| Tasks not delivering | Backend not deployed | `blink backend deploy` |
| `Unknown task: X` | Missing case in handler | Add matching case |
| Tasks retrying | Handler returning non-2xx | Return `c.json({ ok: true })` |
| `cancel` returns 409 | Task not pending | Can only cancel `'pending'` tasks |

## Billing

1 credit = 1,000 tasks (0.001 credits/task). Retries and reads are free. Pro+ only.

