1. First decide who starts the delivery#

The most important difference between queues is not price: it is who opens the connection. In a pull model, your workers ask for messages when they have capacity and acknowledge each one when done. In a push model, the service calls your code, usually over HTTP, and treats the message as delivered when you respond successfully.

  • AWS SQS is pull: your consumers call ReceiveMessage and then DeleteMessage. With long polling, the call waits up to 20 seconds for messages to arrive, which cuts empty responses and cost.
  • Cloudflare Queues offers both: a Worker consumer receives batches via push, or a pull consumer fetches them over HTTP from any environment outside Workers, using an API token and a visibility timeout of 30 seconds by default and up to 12 hours.
  • Upstash QStash is push: it sends an HTTP request to your endpoint with the body unmodified and a signature in the Upstash-Signature header that you must verify.

Pull fits when your workers are long-running processes, when you need to control pressure on a database, or when you want to scale consumers by backlog. Push fits serverless functions or existing HTTP endpoints that should not run a polling loop. The cost of push includes keeping an endpoint that is public, authenticated, and able to respond in time.

Documentation: AWS: short and long polling in SQS ↗ · Cloudflare: pull consumers ↗ · Upstash: receiving QStash messages ↗

2. Delivery guarantees: at-least-once, ordering, and deduplication#

All three options can deliver a message more than once. SQS Standard documents at-least-once delivery: if a server holding a copy is unavailable when you delete, that copy can come back. Cloudflare Queues also states at-least-once delivery and warns that, on rare occasions, a message may be delivered more than once. In QStash, any response without a 2XX status triggers a retry, so an endpoint that did the work but answered late or with an error will receive the message again.

Ordering is a separate decision. SQS Standard makes a best-effort attempt to keep send order, but messages may occasionally arrive out of order. SQS FIFO preserves order within each message group and does not introduce duplicates into the queue when you retry SendMessage within a 5-minute deduplication interval, using a MessageDeduplicationId or content-based deduplication with a SHA-256 hash of the body. QStash offers queues with one-by-one FIFO delivery and a 10-minute deduplication window by Upstash-Deduplication-Id or by content. Cloudflare's delivery-guarantees documentation makes no ordering promise.

None of these windows replaces consumer idempotency: they deduplicate nearby publishes, not business effects. The guide on queues, retries, and idempotency covers how to design that part; here it is enough to know you will need it with any provider.

Go deeper: Queues, Retries, and Idempotency: Stop Processing Messages Twice

Documentation: AWS: at-least-once delivery in SQS ↗ · AWS: ordering in standard queues ↗ · AWS: FIFO queues ↗ · AWS: FIFO deduplication ↗ · Cloudflare: Queues delivery guarantees ↗ · Upstash: QStash FIFO queues ↗ · Upstash: QStash deduplication ↗ · Upstash: QStash retries ↗

3. Retention, size, delay, and throughput#

Operational limits rule options out before price does. A 500 KB payload does not fit in Cloudflare Queues; a job scheduled a week out does not fit in SQS's delay; a very large ordered burst can hit FIFO throughput limits.

LimitSQS Standard / FIFOCloudflare Queues (Paid)QStash Pay as You Go
Queue retention4 days by default; 1 minute to 14 days4 days by default; configurable up to 14No published queue retention; 7-day DLQ
Max message size1 MiB; larger via Extended Client and S3128 KB per message10 MB per message
Max delay15 minutes24 hours (delaySeconds)1 year
ThroughputStandard nearly unlimited; FIFO 300 TPS per partition without high-throughput mode5,000 messages per second per queueMax parallelism of 100
RetriesVisibility timeout 30 s by default, up to 12 h3 by default, max 100; then DLQ or deletion3 by default, configurable with Upstash-Retries

In Cloudflare Queues, a message that exhausts its retries is deleted from the queue unless you configure a dead-letter queue. In QStash, the wait between retries grows exponentially up to a maximum of one day. In SQS, the message reappears when its visibility timeout expires, so that value must cover your real processing time.

Retention is your buffer for incidents: if your consumer goes down over a long weekend, the 4-day default may not be enough. Set it on purpose and alert on the age of the oldest message, not just on backlog size.

Documentation: AWS: SQS message quotas ↗ · Cloudflare: Queues limits ↗ · Cloudflare: batching and retries ↗ · Upstash: Pay as You Go plan ↗ · Upstash: QStash retries ↗

4. Understand the unit you are billed for#

Each provider bills a different unit, and none of them is exactly a message. That is why the Codifly queue comparator models a common scenario: 1 million 1 KiB messages to a single consumer, with no batching, retries, or free tiers.

  • SQS bills per request: in us-east-1, $0.40 per million for Standard and $0.50 per million for FIFO in the first tier, with 1 million free requests per month. Every action counts, including ReceiveMessage, DeleteMessage, and ChangeMessageVisibility. Each 64 KB chunk of payload counts as one request: an action with 1 MiB is billed as 16. A batch of up to 10 messages counts as a single request.
  • Cloudflare Queues charges $0.40 per million operations on Workers Paid, with 1 million included per month. One operation is counted per 64 KB written, read, or deleted, and each retry adds a read. There are no egress or bandwidth charges, but the Workers Paid plan and your Workers' execution are billed separately.
  • QStash charges $1 per 100,000 messages, where one delivery attempt to one endpoint counts as one message. A retry is another attempt, and publishing to a topic is billed once per subscribed endpoint. It includes 50 GB of bandwidth per month; overage costs $0.05 per GB.

In the comparator's reference scenario, a message on SQS or Cloudflare means three calls: send, read, and delete. That works out to $1.20 per million messages on SQS Standard and Cloudflare Queues, $1.50 on SQS FIFO, and $10 on QStash. QStash's premium buys something: you do not operate polling workers and you get long delays. None of these prices includes running the consumer.

Documentation: AWS: SQS billing rules ↗ · AWS: SQS price list for us-east-1 ↗ · Cloudflare: Queues pricing ↗ · Upstash: QStash pricing ↗

5. Worked example: how retries and batching move the bill#

Assumptions: 10 million 1 KiB messages per month, one consumer, and 5% of messages (500,000) failing once and being retried once. We subtract each provider's free allowance and leave out compute, base plans, and transfer.

text
SQS Standard, no batching
  send 10M + receive (10M + 0.5M) + delete 10M = 30.5M requests
  (30.5M − 1M free) × $0.40 per M                = $11.80

SQS FIFO, no batching
  (30.5M − 1M free) × $0.50 per M                = $14.75

SQS Standard, full batches of 10
  1M + (1M + 0.05M) + 1M = 3.05M requests
  (3.05M − 1M free) × $0.40 per M                = $0.82

Cloudflare Queues
  write 10M + read (10M + 0.5M) + delete 10M = 30.5M operations
  (30.5M − 1M included) × $0.40 per M            = $11.80

QStash
  10M attempts + 0.5M retries = 10.5M attempts
  10.5M / 100,000 × $1                           = $105.00
  with 2 endpoints subscribed to a topic: × 2    = $210.00
Editorial calculation with published first-tier rates. SQS batching assumes enough backlog to fill every batch of 10.

Three lessons come out of this math. First, on SQS batching cuts cost almost tenfold because the unit is the request, not the message. Second, Cloudflare states that operations are per message, not per batch: a batch of 10 incurs 10 writes, 10 reads, and 10 deletes. Third, on QStash every retry and every extra destination is a billed message: a downstream dependency returning 500 for hours turns straight into spend.

Size multiplies too. With 100 KB messages, every SQS action and every Cloudflare read or write counts as two 64 KB chunks. On SQS, short polling an empty queue generates billed requests with no messages; use long polling. And on SQS, each ChangeMessageVisibility call to extend a long job is another request.

Documentation: AWS: SQS billing rules ↗ · AWS: SQS price list for us-east-1 ↗ · Cloudflare: Queues pricing ↗ · Upstash: QStash pricing ↗

6. Which option fits which case#

If you needBest starting pointWhy
Your own workers on AWS at high volumeSQS StandardPull with batches of 10 and long polling; very low cost per message.
Strict ordering per customer or entitySQS FIFOOrdering per message group and 5-minute deduplication on publish.
Producers and consumers on WorkersCloudflare QueuesBuilt-in push consumption, no egress, configurable retries and DLQ.
Calling HTTP endpoints or serverless functionsQStashSigned HTTP push, retries, and delays up to 1 year without running workers.
Large payloadsStore the file in object storage and enqueue a referenceYou avoid the 128 KB or 1 MiB limits and billed 64 KB chunks.

If you already run on one cloud, integration with its identity, networking, and observability usually outweighs a few cents per million. Switching queues later is possible, but it touches producers, consumers, alerts, and failed-message handling.

7. Checklist for choosing a queue#

  • Decide whether your consumer can sustain polling or needs to receive HTTP calls.
  • Confirm your consumer is idempotent: every option can deliver duplicates.
  • Decide whether you need ordering, and at what level: global, per entity, or none.
  • Compare your largest payload with the size limit and with 64 KB billing chunks.
  • Check the maximum delay if you schedule future jobs.
  • Configure retention, a DLQ, and alerts on the age of the oldest message.
  • Price it with your real failure rate, number of destinations, and batch size.
  • Add what the queue does not charge for: consumer compute, base plan, and transfer.
  • Verify rates on the provider's official page before deciding.

The Codifly queue comparator summarizes these figures along with their assumptions; adjust the scenario to your volume and retries before comparing.

Sources and scope

Documentation checked on September 26, 2026. Examples and decision criteria are editorial proposals; adapt them to your application's contract and validate them in an authorized test environment.

From design to decision

Compare message queues

Review pricing, limits, conditions and sources for each option.

Open comparison