01
Separate message identity from its current receipt
The message ID identifies the message, while receive and update operations produce pop receipts used for subsequent operations. Retaining the ID does not make an older receipt valid forever.
In our local sequence, the worker received a message and stored its original receipt. Updating the contents and visibility returned a new receipt. The old pair of message ID and receipt was no longer valid for the attempted delete.
02
Capture the update result explicitly
Use the value returned by update_message rather than assuming an earlier object or cached receipt still describes the current operation state. Keep that result associated with the correct message when workers handle more than one item.
This example updates a message through an existing authorized queue client. The visibility interval is a demonstration value, not a recommended processing timeout for every workload.
message = next(queue.receive_messages(visibility_timeout=30))
updated = queue.update_message(
message.id, message.pop_receipt,
content="updated payload", visibility_timeout=30,
)
current_receipt = updated["pop_receipt"]03
Delete with the current valid receipt
Once the intended processing and update workflow is complete, delete using the current receipt. Do not issue a blind delete retry with the same stale token and interpret every rejection as a storage outage.
Our emulator test confirmed that the update receipt differed from the original. The stale delete was rejected; using the returned current receipt removed the message. A queue property check then reported zero remaining messages in the isolated test queue.
queue.delete_message(message.id, current_receipt)04
Investigate visibility and competing workers separately
In a real workload, another receive or update can occur after visibility changes. Inspect the timeline of operations and their results without logging full receipts or sensitive message bodies. A receipt valid at one point may no longer be the current one later.
Keep processing idempotent and choose a visibility-renewal strategy consistent with expected work. Correctly tracking a receipt does not establish exactly-once processing or prevent a message from being delivered again after a worker fails.
05
Keep retries tied to the actual operation state
After a failed delete, determine whether the message was updated, received again, expired, or already deleted. The HTTP status and service code are useful alongside that sequence; the local stale-receipt demonstration should not become a generic explanation for every missing message.
A small regression test can deliberately receive, update, attempt the stale token, and finally use the current token. Preserve the result of each operation so a later refactor cannot accidentally reuse the original receipt.
Know the limits
Validated with Azurite and azure-storage-queue 12.18.0 using API version 2023-11-03. Updating a received message returned a different pop receipt; deletion with the old value failed and deletion with the new value succeeded. No real multi-worker race or Functions trigger lifecycle was tested.
Sources & further reading
A technical publication by CoreTrailKit.
Browse all articles