NotCheapMAKE EVERY CREDIT COUNT

Cloudflare Queues vs AWS SQS vs Upstash QStash: Real Queue Cost per 100 Million Messages in 2026

The short answer: AWS SQS's real cost depends on both message size and whether an application batches its API calls, and the two interact differently across a message's three lifecycle steps. For genuinely small messages (around 6 KB, small enough that ten of them fit in one batch under SQS's 64 KB billing-chunk rule), fully batched SQS calls cost about $11.60 for 100 million messages, while the same volume sent, received, and deleted one at a time costs $119.60 — roughly 10 times more. At a more realistic ~127 KB average size, SQS's maximum send/receive batch shrinks to 8 messages (limited by the 1 MiB total-batch-payload cap), and because the send and receive steps carry the full message body, batching those two steps saves nothing — the billed chunk count tracks total payload bytes, not request count. The delete step is different: DeleteMessageBatch carries only small receipt handles, not the original message body, so batching ten deletes per call still fits comfortably under the 64 KB threshold even at 127 KB message sizes, producing a real, if partial, saving. Combining these effects, 100 million 127 KB messages cost about $199.60 fully unbatched and about $163.60 with batched sends, receives, and deletes — roughly an 18% saving, not the 10x seen at small message sizes, and not zero either. Cloudflare Queues' separate 64 KB-per-message chunking mechanism produces $239.60 at this message size for its own standard write-read-delete lifecycle, confirmed directly against Cloudflare's own published worked example — a different mechanism from SQS's, landing at a different number. Upstash QStash charges a flat $1,000 for 100 million messages regardless of size or batching, trading a materially higher absolute cost for a simpler push-based delivery model that doesn't require the application to manage separate read and delete operations at all.

The billing-unit mismatch, up front

None of these three charge per "message" in the same sense. Cloudflare Queues bills per operation, and a single message's full lifecycle (write, read, delete) is three separate billable operations — more if it's retried, and more again if the message exceeds 64 KB and gets split into multiple billing chunks. AWS SQS bills per API request, and because SQS supports batching up to 10 messages into a single request, the actual number of billable requests for a given message volume depends entirely on whether the calling application uses that batching capability — a message is not automatically one request. Upstash QStash bills per message as a single flat unit, covering the full push-based HTTP delivery in one charge, with no separate read or delete operation for the caller to manage or pay for distinctly.

What each vendor bills

Cloudflare Queues (OFFICIAL, developers.cloudflare.com/queues/pricing). Standard operations: 1,000,000 operations/month included free (10,000/day), then $0.40 per million operations. A typical message lifecycle — one write, one read, one delete — consumes 3 operations. Messages larger than 64 KB are billed as multiple operations per step: a 65–128 KB message counts as 2 chunks, so its full lifecycle costs 6 operations instead of 3. A message retried 3 times (the default) before failing and landing in a Dead Letter Queue consumes 5 additional read operations on top of its base lifecycle. Message retention on the Paid plan defaults to 4 days and is configurable from 60 seconds up to 14 days; the Free plan caps retention at 24 hours, a materially shorter and non-configurable window than Paid.

AWS SQS (OFFICIAL, AWS SQS pricing and AWS API documentation). $0.40 per million requests (Standard queues; FIFO queues run $0.50/million). The current maximum message size is 1 MiB (1,048,576 bytes) — not the older, smaller 256 KB limit some guides still cite — and SendMessageBatch supports up to 10 messages per call, with the combined total payload across all 10 messages also capped at 1 MiB. Billing is based on 64 KB chunks of payload, applied to the total batch's combined payload, not per individual message: a batch of several small messages that together stay under 64 KB bills as a single request, while a single oversized message or a large combined batch bills as multiple request-units. A message's full lifecycle — send, receive, delete — is a minimum of 3 request-units if sent and processed individually, and batching's effect on cost differs by step: SendMessageBatch and batched ReceiveMessage calls carry the full message body, so batching only reduces billed chunks when the combined batch payload stays efficient relative to the 64 KB chunk size — genuinely large for small messages, absent for messages already over 64 KB each. DeleteMessageBatch is different: it carries only receipt handles (small, fixed-size tokens identifying which message to delete), not the original message body, so batching up to 10 deletes per call stays well under the 64 KB chunk threshold regardless of how large the original messages were — a real saving on the delete step specifically, independent of message size. SQS message visibility timers cap at 15 minutes; a 1-million-request monthly free tier applies, shared across the account.

Upstash QStash (OFFICIAL, Upstash's pricing documentation). $1 per 100,000 messages ($10 per million), a flat rate regardless of message size or delivery outcome, covering QStash's push-over-HTTP delivery model where QStash itself calls the destination endpoint rather than requiring the application to poll for messages. A free tier covers a modest daily allowance, and pay-as-you-go accounts can delay message delivery up to a year (unlimited on Fixed-price plans). Default retry count is 3, configurable; each retry is billed as an additional message, the same retry-amplification pattern Cloudflare Queues has for its read operations.

Cost at 100 million messages/month, by message size and batching pattern

All ILLUSTRATIVE for the exact message-size assumptions; the chunk and request-unit arithmetic itself is verified directly against AWS's and Cloudflare's own published formulas, and against AWS's confirmed receipt-handle-based DeleteMessageBatch mechanic. The small-message scenario uses 6 KB messages, batched 10 per call on every lifecycle step (60 KB combined payload, under the 64 KB chunk threshold). The large-message scenario uses ~127 KB messages: send and receive batching is capped at 8 messages per call by SQS's 1 MiB total-batch-payload limit, but delete batching uses receipt handles (an ILLUSTRATIVE ~200 bytes each) rather than the original message body, so up to 10 deletes per call still fit in a single 64 KB chunk regardless of how large the original messages were.

ScenarioCloudflare QueuesAWS SQS, fully batchedAWS SQS, unbatched (1/request)Upstash QStash
Small messages (6 KB, 10/batch)$119.60 (300M ops − 1M free, ÷1M × $0.40)$11.60 (30M request-units − 1M free, ÷1M × $0.40)$119.60 (300M request-units − 1M free, ÷1M × $0.40)$1,000 (flat, size-independent)
~127 KB messages (send/receive batched at 8/call, delete batched at 10/call via receipt handles)$239.60 (600M ops − 1M free, ÷1M × $0.40 — matches Cloudflare's own published example exactly)$163.60 (410M request-units − 1M free, ÷1M × $0.40)$199.60 (500M request-units − 1M free, ÷1M × $0.40)$1,000 (unchanged)

The 10x gap between SQS's batched and unbatched figures at the small-message size is real and is the single largest lever in this comparison for payloads that stay under the 64 KB chunk threshold. At ~127 KB, that large a gap doesn't survive, but it doesn't vanish either: batching still saves about 18% ($199.60 unbatched versus $163.60 batched), and that entire saving comes from the delete step specifically — DeleteMessageBatch's use of small receipt handles rather than the original message body means it keeps benefiting from batching even when send and receive batching stops helping at all.

Why SQS's real cost depends on your code, your message size, and which lifecycle step you're looking at

Formula: billable request-units per batch = ceiling(combined batch payload bytes ÷ 65,536), applied separately to each of the three lifecycle steps, since each carries a different payload. Send and receive both carry the full message body, so their batch size is capped by the 1 MiB total-payload limit — for small messages, batching ten at a time into one 60 KB payload means ten messages share a single 64 KB chunk, a genuine 10x reduction; for messages already at or above 64 KB each, every message already consumes at least one full chunk on its own, so combining several into one batch call just moves the same chunk count into fewer API calls, with no change to the bill. Delete is structurally different: DeleteMessageBatch carries receipt handles, not the original message body, so its effective payload per batched call stays small (a few hundred bytes per handle, typically) regardless of how large the source messages were — meaning delete batching keeps delivering close to its full 10x reduction even when send and receive batching has stopped helping entirely. This is precisely why modeling SQS by actual billable chunks on each lifecycle step separately, rather than assuming one logical message equals one billed request across the board or that batching always saves (or never saves) a fixed percentage, is necessary to get an honest number.

Why Cloudflare's chunking penalty rewards smaller messages

Formula: Cloudflare operations = messages × ceiling(message size ÷ 64 KB) × 3 lifecycle steps. Keeping messages under the 64 KB threshold avoids the chunking multiplier entirely; a workload that naturally produces larger payloads (serialized job data, batch records) can cut its Cloudflare Queues bill roughly in half simply by compressing or restructuring messages to fit under that line, independent of total message volume.

Sensitivity

  1. Batching architecture on SQS, combined with message size and which lifecycle step is batched. A 10x cost swing for small (sub-64 KB) messages across the whole lifecycle; for large messages, send/receive batching stops helping at all, but delete batching (via receipt handles) keeps delivering close to its full discount, producing an overall ~18% saving rather than 10x or zero.
  2. Message size relative to Cloudflare's 64 KB chunking threshold. Directly doubles (or more) the operation count for payloads that cross each 64 KB boundary.
  3. Retry rate. Both Cloudflare and QStash bill retries as additional operations/messages; a noisy or error-prone consumer multiplies cost beyond the base lifecycle on either platform.
  4. Whether push-based delivery (QStash) or pull-based consumption (SQS, Cloudflare Queues) better fits the application architecture. This is a structural fit question as much as a cost one — QStash's flat, size-independent rate buys architectural simplicity the other two don't offer at any price.

Budgeting traps

  • Assuming batching always delivers a 10x SQS discount regardless of message size, or alternatively assuming it delivers none once messages exceed 64 KB. Neither is quite right: for large messages, send and receive batching stop reducing the bill, but delete batching (via receipt handles rather than the full message body) keeps working, producing a real but partial saving.
  • Ignoring Cloudflare's 64 KB chunking rule when estimating cost from average message size. A workload with payloads just over a chunking boundary pays for a full extra chunk per message.
  • Treating QStash's flat per-message rate as directly comparable to SQS's or Cloudflare's per-operation rate without accounting for what each unit includes. QStash's single charge covers full delivery; the other two require summing multiple operations per message lifecycle.
  • Forgetting retry amplification on Cloudflare Queues and QStash. A consumer with a high failure rate multiplies the effective per-message cost well beyond the baseline lifecycle figure.

What to ask before you buy

Measure your typical message payload size against the 64 KB chunking threshold on both SQS and Cloudflare Queues before estimating how much batching will reduce your bill — the discount is large for small messages, partial (driven by the delete step alone) for large ones, and never exactly zero on SQS as long as DeleteMessageBatch is in use. Confirm your current message-size distribution and whether your actual (or planned) SQS integration batches all three lifecycle steps or just some of them, since that combination determines the real-world cost more precisely than a single batched-versus-unbatched assumption.