ServicesMessage Queue

Receiving, Visibility, and the DLQ

The consumer half of the pipeline: pull messages, hold them invisibly while you work, ack the ones you finish, and let the retry budget move the rest to the dead letter queue. The examples here use a short visibility timeout and --max-receives 2.

Receiving

snug --output json mq receive -q order-processing -m 10 -w 20 --visibility 60
{
  "messages": [
    {
      "message_id": "msg_UFRSuwjFZsLv",
      "receipt_handle": "rh_PnsQcyJTLYSs",
      "body": { "order_id": 123, "total": 49.99 },
      "attributes": { "type": "created" },
      "system_attributes": {
        "sender_id": "docs-wave",
        "sent_timestamp": "2026-08-28T05:01:36.587054Z",
        "approximate_receive_count": 1,
        "approximate_first_receive_timestamp": "2026-08-28T05:01:41.841845Z",
        "sequence_number": "00000000000000000001",
        "message_group_id": null
      }
    }
  ]
}
  • -m caps the batch at 1-10 messages (default 1).
  • -w long-polls: on an empty queue, -w 3 blocked for 3.1 seconds before returning {"messages": []} (verified with time); 0 returns immediately. Maximum 20 seconds.
  • --visibility overrides the queue's default timeout for just this receive.

The visibility loop

Receiving does not remove a message - it hides it for the visibility timeout. Verified sequence on a queue with a 2-second timeout:

  1. Receive returns the message with approximate_receive_count: 1.
  2. A receive during the window returns {"messages": []}.
  3. After the window lapses un-acked, the same message_id comes back with a new receipt_handle and approximate_receive_count: 2.

Deleting with the receipt handle is the acknowledgment. While holding a message you can also move the deadline:

snug mq visibility -q order-processing -r rh_rtgPXjcuLAtw -t 120  # need more time
snug mq visibility -q order-processing -r rh_rtgPXjcuLAtw -t 0   # give it back now

After -t 120 the message stayed invisible to an immediate receive; after -t 0 it was immediately receivable again (with its receive count incremented). Acks batch too:

snug mq delete-batch -q order-processing -r rh_YufpsDCuRCeP,rh_wSJspNhwWEeB

The response lists successful and failed receipt handles; the batch is capped at 10.

Receipt handle lifetimes

The rules differ by operation:

  • delete is lenient. A receipt handle from an earlier receive of the same message still acks it, even after the visibility window lapsed and a newer handle was issued. Deleting late is safe - but if another consumer re-received the message in between, your late ack deletes their in-flight copy.
  • visibility is strict. Changing visibility with a lapsed handle fails with 400 invalid_receipt_handle - you can only move the deadline of a delivery you still hold.
  • A handle the server never issued fails with 400 receipt_handle_expired.

The DLQ loop

A queue created with --dlq and --max-receives N retries each message N times; the move to the DLQ happens during receive processing. Verified with --max-receives 2: two un-acked receives, then the third receive returned {"messages": []} and the message appeared in the DLQ.

snug --output json mq dlq get -q order-processing   # SOURCE queue name, not the DLQ's
{
  "source_queue": "order-processing",
  "dlq_name": "failed-orders",
  "approximate_message_count": 1,
  "oldest_message_age_seconds": 0,
  "sample_messages": [
    {
      "message_id": "msg_xwdTdpyWajHw",
      "original_queue": "order-processing",
      "receive_count": 2,
      "moved_to_dlq_at": "2026-08-28T05:02:33.636927Z",
      "body": { "order_id": 666, "poison": true },
      "reason": "max_receive_count_exceeded"
    }
  ]
}

dlq get on a queue with no DLQ configured fails with 400 no_dlq_configured - including the DLQ queue itself, so pointing the command at failed-orders is the mistake to avoid.

After fixing the consumer, push everything back to the source queue:

snug --output json mq dlq redrive -q order-processing -m 100
{ "messages_redriven": 1, "destination_queue": "order-processing" }

A redriven message starts over: it comes back with a fresh message_id, approximate_receive_count: 1, and a full retry budget (verified - the poison message returned to the queue and was received normally). A message that exceeds its budget on a queue with no DLQ configured is dropped instead of moved.

Errors you will meet

  • 404 queue_not_found - receive/delete against a queue that does not exist (or belongs to another user).
  • 404 topic_not_found - publish to a missing topic.
  • 400 receipt_handle_expired / 400 invalid_receipt_handle - see receipt handle lifetimes.
  • 400 duplicate_message - --dedup-id reused within the 5-minute window.
  • 400 no_dlq_configured - DLQ get/redrive on a queue without one.

The exact request and response schemas are in the Receiving and DLQ API references.

On this page