DEV Community

Alex Spinov
Alex Spinov

Posted on

Upstash QStash Has Free Serverless Message Queues — Here's How to Build Async Workflows

Need a message queue for serverless? Traditional queues like RabbitMQ need a persistent server. QStash works over HTTP.

What is QStash?

QStash is a serverless message queue and task scheduler from Upstash. Send a message via HTTP, QStash delivers it to your endpoint — with retries, delays, and scheduling.

Free Tier

  • 500 messages/day
  • 3 retries per message
  • Cron schedules
  • Delay and deduplication

Quick Start

bun add @upstash/qstash
Enter fullscreen mode Exit fullscreen mode
import { Client } from '@upstash/qstash';

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

// Send a message to your API endpoint
await qstash.publishJSON({
  url: 'https://your-app.com/api/process-order',
  body: { orderId: '123', userId: 'user_456' },
});
Enter fullscreen mode Exit fullscreen mode

Receiving Messages

// api/process-order.ts (your endpoint)
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(req: Request) {
  // Verify the message came from QStash
  const body = await req.text();
  const isValid = await receiver.verify({
    signature: req.headers.get('upstash-signature')!,
    body,
  });

  if (!isValid) return new Response('Unauthorized', { status: 401 });

  const data = JSON.parse(body);
  await processOrder(data.orderId);

  return new Response('OK');
}
Enter fullscreen mode Exit fullscreen mode

Delayed Messages

// Send email 1 hour after signup
await qstash.publishJSON({
  url: 'https://your-app.com/api/welcome-email',
  body: { userId: 'user_123' },
  delay: 3600, // 1 hour in seconds
});
Enter fullscreen mode Exit fullscreen mode

Scheduled Messages (Cron)

// Run every day at 9 AM
await qstash.schedules.create({
  destination: 'https://your-app.com/api/daily-report',
  cron: '0 9 * * *',
  body: JSON.stringify({ type: 'daily' }),
});

// List schedules
const schedules = await qstash.schedules.list();

// Delete schedule
await qstash.schedules.delete('schedule_id');
Enter fullscreen mode Exit fullscreen mode

Fan-out (Send to Multiple Endpoints)

await qstash.batchJSON([
  {
    url: 'https://your-app.com/api/send-email',
    body: { orderId: '123' },
  },
  {
    url: 'https://your-app.com/api/update-inventory',
    body: { orderId: '123' },
  },
  {
    url: 'https://your-app.com/api/notify-warehouse',
    body: { orderId: '123' },
  },
]);
Enter fullscreen mode Exit fullscreen mode

Workflow Chains

import { Workflow } from '@upstash/qstash';

const workflow = new Workflow({ client: qstash });

await workflow.run('order-flow', async (ctx) => {
  // Step 1
  const payment = await ctx.call('charge-payment', {
    url: 'https://your-app.com/api/charge',
    body: { amount: 99.99 },
  });

  // Step 2 (runs after step 1 succeeds)
  await ctx.call('reserve-inventory', {
    url: 'https://your-app.com/api/inventory',
    body: { items: ['item-1'] },
  });

  // Step 3
  await ctx.call('send-confirmation', {
    url: 'https://your-app.com/api/email',
    body: { template: 'order-confirmed' },
  });
});
Enter fullscreen mode Exit fullscreen mode

QStash vs Alternatives

Feature QStash SQS BullMQ Inngest
Serverless Yes Yes No (needs Redis) Yes
HTTP-based Yes SDK Redis protocol Yes
Cron Yes EventBridge bull-board Yes
Retries Built-in Built-in Built-in Built-in
Free Tier 500 msg/day 1M msg/mo N/A 5K runs/mo
Edge Yes No No Yes

Need async data processing? Check out my Apify actors — queue-based web scraping at scale. For custom solutions, email spinov001@gmail.com.

Top comments (0)