QStash

An HTTP message queue with retries, delays and schedules, in one command.

AI/LLM: this page is available in plain markdown at /docs/resources/qstash.md

npx extraorbital add qstash
✓ Provisioned qstash/default
✓ Wrote 4 variables to .env

  QSTASH_URL, QSTASH_TOKEN, QSTASH_CURRENT_SIGNING_KEY, QSTASH_NEXT_SIGNING_KEY

A project that mentions QSTASH_URL or QSTASH_TOKEN gets this from extraorbital provision without naming it.

What it writes

VariableExample
QSTASH_URLhttps://qstash.extraorbital.dev/v2/publish
QSTASH_TOKENeyJVc2VySUQ…
QSTASH_CURRENT_SIGNING_KEYsig_7f2a…
QSTASH_NEXT_SIGNING_KEYsig_9c4e…

Publish a message with a target URL and QStash delivers it with retries, delays and schedules. There is no connection to hold open, which suits serverless functions and agents that sleep between runs. The two signing keys let you verify deliveries and rotate without downtime.

Options

npx extraorbital add qstash [--slug <name>]
FlagDefaultDescription
--slugdefaultA second, fully separate set of credentials

Prototype allowance

1,000 messages per day and 50 GB of bandwidth free on the Prototype plan, counted across your whole account and shared by up to five queues. Past it, $1.00/100K messages — our own cost, with nothing added, and retries are still free — on a paid plan.

Use it

Publish with a delay:

publish.ts
import { Client } from "@upstash/qstash";

const qstash = new Client({ token: process.env.QSTASH_TOKEN! });

await qstash.publishJSON({
  url: "https://my-agent.example.com/api/jobs",
  body: { task: "summarize", docId: "doc-42" },
  delay: 60, // seconds
});

Verify on the receiving end, so only your queue can trigger the handler:

app/api/jobs/route.ts
import { Receiver } from "@upstash/qstash";

const receiver = new Receiver({
  currentSigningKey: process.env.QSTASH_CURRENT_SIGNING_KEY!,
  nextSigningKey: process.env.QSTASH_NEXT_SIGNING_KEY!,
});

export async function POST(request: Request) {
  const body = await request.text();
  const ok = await receiver.verify({
    signature: request.headers.get("Upstash-Signature")!,
    body,
  });
  if (!ok) return new Response("Invalid signature", { status: 401 });

  return Response.json({ received: JSON.parse(body) });
}

Next

  • Redis — lists and streams you poll yourself
  • Pricing — plans, allowances and rates