Skip to main content

QStash vs Inngest vs AWS SQS 2026

·APIScout Team
qstashinngestaws sqsmessage queueserverlessapibackground jobs2026

QStash vs Inngest vs AWS SQS 2026

Serverless changed background job processing in ways most developers did not anticipate. When your functions run ephemerally and time out after seconds, you cannot just spin up a worker that listens to a queue indefinitely. You need a message queue that delivers work to your serverless functions via HTTP — not one your functions need to poll.

Three tools dominate this space in 2026: Upstash QStash, Inngest, and AWS SQS. They look similar on the surface but occupy entirely different positions on the complexity/capability spectrum. Getting the choice wrong means either paying for features you will never use or hitting walls at scale that require a painful migration.

TL;DR

QStash wins for simplicity: HTTP-first, zero infrastructure, $1 per 100K messages. Inngest wins for complex multi-step workflows that need to survive failures, sleep between steps, or fan out to hundreds of sub-tasks. AWS SQS wins when you are already on AWS, need FIFO guarantees, or require deeply integrated dead-letter queues feeding other AWS services. For most serverless applications in 2026, QStash or Inngest is the right call — SQS requires polling, which does not map cleanly onto serverless functions.

Key Takeaways

  • QStash requires no long-running process: you POST a message, QStash delivers it to your URL with retries
  • Inngest adds step functions — your job can sleep, wait for events, and fan out, with each step retried independently
  • AWS SQS requires your code to poll for messages, which works poorly in pure serverless environments without a worker process
  • QStash free tier: 500 messages/day (15K/month); Inngest free tier: 50K function runs/month; SQS free tier: 1M requests/month
  • Cron scheduling is native in QStash and Inngest; SQS does not support cron natively (requires EventBridge)
  • Local development experience: Inngest Dev Server wins with its built-in UI; QStash has a tunnel option; SQS requires LocalStack
  • All three support message deduplication; only QStash and SQS support message delays natively without step-function overhead

The Core Problem: Serverless Background Jobs

Traditional job queues assume persistent workers. Redis-based queues like BullMQ work by running a process that subscribes to a Redis stream and processes jobs as they arrive. On Vercel, Cloudflare Workers, or AWS Lambda, there is no persistent process — functions spin up per-request and shut down immediately after.

The three solutions here take different approaches to this constraint:

QStashInngestAWS SQS
Delivery modelHTTP push to URLHTTP push via Inngest platformPoll-based (pull)
Infrastructure neededNoneNone (hosted)Needs poller or Lambda trigger
Step functionsNoYesWith Step Functions (separate service)
Cron built-inYesYesNo (needs EventBridge)
Self-hostableNoYesN/A (AWS managed)
Best forSimple delayed HTTP callsMulti-step durable workflowsAWS-native queuing
Free tier500 msg/day50K runs/month1M requests/month
Paid$1/100K messages$0.40/1K runs (from $25/mo)$0.40/1M requests

Upstash QStash: HTTP-First Message Queue

QStash from Upstash is the simplest possible serverless message queue. The entire API is one HTTP POST. You send a message with a destination URL, and QStash delivers it — with automatic retries on failure, configurable delays, deduplication, and cron scheduling.

How It Works

// Publishing a message — no SDK needed, plain fetch works
const response = await fetch("https://qstash.upstash.io/v2/publish/https://your-app.com/api/process", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.QSTASH_TOKEN}`,
    "Content-Type": "application/json",
    "Upstash-Delay": "30s",         // Deliver in 30 seconds
    "Upstash-Retries": "3",          // Retry 3 times on failure
    "Upstash-Deduplication-Id": "order-123",  // Dedup key
  },
  body: JSON.stringify({ orderId: "123", action: "send_confirmation" }),
});

The Receiver

Your endpoint receives the HTTP POST and processes it. The signature verification middleware prevents spoofed deliveries:

import { verifySignatureAppRouter } from "@upstash/qstash/nextjs";

async function handler(req: Request) {
  const body = await req.json();
  // Process the job
  await sendConfirmationEmail(body.orderId);
  return new Response("OK");
}

export const POST = verifySignatureAppRouter(handler);

QStash Pricing

TierPriceIncluded
Free$0500 messages/day
Pay-as-you-go$1/100K messagesNo monthly fee
Pro 10M$40/month10M messages/month
Pro 50M$160/month50M messages/month

At 1M messages/month on pay-as-you-go, you pay $10/month. This makes QStash the lowest-cost option at small to mid scale.

QStash Limitations

QStash is intentionally simple. It does not support complex multi-step workflows. Each message is a single HTTP delivery — there is no concept of "step 2 depends on step 1" or "sleep for 24 hours then continue." For anything more complex than "deliver this HTTP request later with retries," you need Inngest.

Inngest: Durable Step Functions for Serverless

Inngest is the most developer-friendly background job platform for TypeScript in 2026. It takes a fundamentally different approach: instead of queueing messages, you write functions that Inngest orchestrates. Each step in a function runs independently, persists its result, and retries only itself on failure.

The Step Function Model

import { inngest } from "@/inngest/client";

export const processOrder = inngest.createFunction(
  { id: "process-order" },
  { event: "order.created" },
  async ({ event, step }) => {
    // Step 1: Charge payment
    const payment = await step.run("charge-payment", async () => {
      return await stripe.paymentIntents.confirm(event.data.paymentIntentId);
    });

    // Step 2: Sleep until next business day if created on weekend
    await step.sleepUntil("wait-for-business-hours", getNextBusinessDay());

    // Step 3: Notify warehouse — only runs after step 1 and 2 succeed
    await step.run("notify-warehouse", async () => {
      await warehouseApi.createPickOrder({
        orderId: event.data.orderId,
        paymentId: payment.id,
      });
    });

    // Step 4: Send confirmation email
    await step.run("send-confirmation", async () => {
      await resend.emails.send({
        to: event.data.customerEmail,
        subject: "Order confirmed",
        react: <OrderConfirmation orderId={event.data.orderId} />,
      });
    });
  }
);

If "notify-warehouse" fails at 2am, Inngest retries only that step — not the payment charge or the sleep. Your workflow resumes exactly where it failed.

Inngest Pricing

TierPriceIncluded
Free$050K function runs/month
Pro$25/month + $0.40/1K runsConcurrency controls, longer history
EnterpriseCustomSLAs, SSO, HIPAA

Vercel marketplace integration includes 100K function runs/month on the Pro plan. For a typical SaaS processing 500K runs/month, you'd pay $25 + (450K × $0.40/1K) = $25 + $180 = $205/month.

Inngest Dev Server

Inngest ships a local development server that gives you a real-time UI showing every function run, step output, event payload, and retry history. This is one of its strongest differentiators:

npx inngest-cli@latest dev
# Open http://localhost:8288 to see all function runs

Inngest Limitations

Inngest is a hosted platform. While you can self-host the Inngest server, the hosted version is what most teams use, creating vendor dependency. Function run history retention is limited by plan. And if you need extremely high throughput (millions of simple tasks per minute), the step function overhead may be unnecessary.

AWS SQS: Enterprise-Grade Queue

AWS Simple Queue Service is the oldest of the three, launched in 2004. It is battle-tested at enormous scale, deeply integrated with the AWS ecosystem, and priced extremely competitively for high volume. But its pull-based model requires rethinking your architecture on serverless.

Standard vs FIFO Queues

SQS offers two queue types:

Standard Queue: At-least-once delivery, best-effort ordering, nearly unlimited throughput. Messages can arrive out of order and may be delivered more than once — design your consumers to be idempotent.

FIFO Queue: Exactly-once processing, strict ordering, up to 3,000 messages/second with batching. Higher cost, lower throughput, but essential for operations that must not repeat.

Triggering Lambda from SQS

The most common serverless pattern with SQS is using a Lambda trigger:

// Lambda handler triggered by SQS
import type { SQSHandler, SQSEvent } from "aws-lambda";

export const handler: SQSHandler = async (event: SQSEvent) => {
  for (const record of event.Records) {
    const body = JSON.parse(record.body);

    try {
      await processJob(body);
    } catch (error) {
      // Throw to send message to DLQ after max receive count
      throw error;
    }
  }
};

SQS Pricing

Queue TypePrice
Standard queue$0.40/1M requests
FIFO queue$0.50/1M requests
First 1M requests/monthFree
Data transferStandard AWS rates

SQS is the cheapest option at high volume — $0.40 per million messages vs. $10 per million for QStash. But factor in Lambda execution costs, and the total cost converges.

SQS for Serverless: The Polling Problem

SQS is pull-based. Consumers poll for messages. On serverless:

  • Lambda SQS triggers work well but add Lambda costs
  • Vercel/Cloudflare Worker consumers need a separate polling service
  • Cold starts mean latency spikes when a queue is idle then bursts

For pure serverless (Vercel, Cloudflare), SQS is a worse fit than QStash or Inngest unless you have a dedicated Lambda consumer layer.

Retry and Dead Letter Queue Behavior

QStashInngestAWS SQS
Default retries34 (per step)Based on consumer
Retry backoffExponentialExponential with jitterConsumer-defined
DLQ supportYes (failed events)Yes (function failures)Yes (native)
Max delivery attemptsConfigurableConfigurable1-1000
Visibility timeoutN/A (push)N/A (push)0s - 12 hours

Cron Scheduling

All three handle scheduled jobs, but with different native experiences:

// QStash cron — HTTP call on schedule
// Set up via API or dashboard
// POST to /v2/schedules
// Cron: "0 9 * * 1-5"  (weekdays at 9am)

// Inngest cron
export const dailyDigest = inngest.createFunction(
  { id: "daily-digest" },
  { cron: "0 9 * * *" },
  async () => {
    await sendDailyEmailDigest();
  }
);

// SQS cron — requires EventBridge rule
// EventBridge → Lambda → SQS → Consumer
// (two additional services, more complexity)

When to Choose Each

Choose QStash if:

  • You need simple "run this HTTP endpoint later" with retries
  • You're on Vercel, Cloudflare Workers, or Netlify
  • You want the lowest cost at small-to-mid scale
  • Your jobs are single HTTP calls, not multi-step workflows
  • You want zero infrastructure management

Choose Inngest if:

  • You need multi-step workflows where each step retries independently
  • You need to sleep jobs for hours, days, or weeks
  • You want fan-out (one event triggers hundreds of parallel steps)
  • You build event-driven systems and want a real event bus
  • The local dev UI is important to your team's workflow

Choose AWS SQS if:

  • You're already on AWS and want billing in one place
  • You need FIFO guarantees for financial or ordering operations
  • You're processing millions of messages at the lowest possible per-message cost
  • You have a dedicated Lambda consumer and don't need serverless HTTP delivery
  • You need deep integration with SNS, EventBridge, or other AWS services

For more on background job patterns, see our best background job APIs roundup, Inngest vs Temporal vs Trigger.dev comparison, and event-driven API patterns guide.

Methodology

This comparison uses official pricing pages for Upstash QStash, Inngest, and AWS SQS as of March 2026. Throughput benchmarks are from the buildmvpfast.com alternative comparisons and official documentation. Code examples use official SDK versions current as of the publication date.

Comments