How to Drain an SQS Queue (Without Reprocessing)
If your SQS consumer 'processes' messages but the queue never empties, you're missing one call: delete_message. SQS only removes a message when you delete it - otherwise the visibility timeout expires and it comes back. Here's the correct loop.
Why messages keep reappearing
SQS doesn't remove a message when you receive it - it just hides it for the visibility timeout. You're expected to process it and then explicitly delete it. If you don't delete, the timeout expires and SQS redelivers it, so the same messages get processed again and again and the depth never drops. That's at-least-once delivery working as designed - you just skipped the acknowledgement.
The correct receive -> process -> delete loop
import boto3
sqs = boto3.client("sqs")
url = "https://sqs.../orders"
while True:
resp = sqs.receive_message(QueueUrl=url, MaxNumberOfMessages=10, WaitTimeSeconds=20)
msgs = resp.get("Messages", [])
if not msgs:
break # queue drained
for m in msgs:
process(m) # do the work
sqs.delete_message( # ACK it - this is what removes it
QueueUrl=url, ReceiptHandle=m["ReceiptHandle"])
delete_message with the message's ReceiptHandle is the acknowledgement. No delete =
redelivery.
The details that matter
- Delete only after success. If
process()throws, don't delete - let the message reappear and retry. Pair this with a dead-letter queue so a poison message doesn't loop forever. - ReceiptHandle is per-receive, not the message id - it changes each time you receive the message; use the one from this receive.
- Visibility timeout must exceed processing time. If processing takes longer than the
timeout, SQS redelivers while you're still working - a second worker double-processes.
Raise the timeout or extend it (
change_message_visibility). - Long polling (
WaitTimeSeconds=20) avoids busy-looping on an empty queue and cuts cost. - Idempotent processing - because delivery is at-least-once, design
process()so a duplicate is harmless (dedupe on a message/business id).
Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.
Try it in a workspace →What you'll practice
- Adding delete_message after successful processing
- Tuning visibility timeout vs processing time
- Making process() idempotent for at-least-once delivery
FAQ
Why do my SQS messages keep coming back after processing?
You didn't delete them. SQS only hides a message for the visibility timeout on receive; you must call delete_message with its ReceiptHandle to remove it. Without the delete, the timeout expires and SQS redelivers it.
When should I delete an SQS message?
Only after processing succeeds. If processing fails, skip the delete so the message reappears and retries - and configure a dead-letter queue so a message that keeps failing eventually moves off the main queue.
What is the SQS visibility timeout?
The window after a message is received during which it's hidden from other consumers. It must be longer than your processing time, or SQS redelivers the message while you're still working on it (causing duplicate processing).
Keep learning
Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.
Open the workspace →