Skip to content

Pull consumers

Receive messages with a long poll, acknowledge them, and extend or release them with their receipt handles.

Updated View as Markdown

A pull consumer runs in your own code. It receives messages from https://queues-live.ezghcloud.com with an API key, handles them, and acknowledges each one it finished with its receipt handle. It needs queues.messages.receive on the queue, and outbound HTTPS only.

Receive messages

curl -sS -X POST "https://queues-live.ezghcloud.com/v1/organizations/$ORG_ID/projects/$PROJECT_ID/queues/$QUEUE_ID/messages/receive" \
  -H "Authorization: Bearer $EZGH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "maxMessages": 10, "waitSeconds": 20, "visibilityTimeoutSeconds": 60 }'

ReceiveMessages takes, all optional:

Field Default Range
maxMessages 1 1 to 10
waitSeconds The queue’s receiveWaitSeconds 0 to 20
visibilityTimeoutSeconds The queue’s visibilityTimeoutSeconds 0 to 43,200

It answers 200 with up to maxMessages messages as soon as there are any, or with {"messages": []} when the wait ends. Give your HTTP client a timeout longer than waitSeconds. Each message has:

Field Meaning
messageId The message’s ID, the same on every delivery
receiptHandle Acknowledges or changes the visibility of this delivery
body, attributes As sent. A dead-lettered message adds ezgh. attributes (Dead-letter queues)
sentAt When it was sent
firstReceivedAt When it was first received
receiveCount How many times it has been received, this time included
groupId, seq FIFO queues only: its message group, and its sequence number in the group (a string of digits)

A receive can answer fewer messages than are visible. With many messages in flight (120,000 on a standard queue, 20,000 on a FIFO queue) it answers no more until some are acknowledged or become visible again.

From the CLI, ezgh queues receive takes --max, --wait and --visibility-timeout, and prints each message with its receipt handle; --ack acknowledges them once printed. ezgh queues ack and ezgh queues change-visibility take receipt handles.

Acknowledge messages

When you’ve handled a message, acknowledge it, which deletes it:

curl -sS -X POST https://queues-live.ezghcloud.com/v1/receipts/ack \
  -H "Content-Type: application/json" \
  -d '{ "receiptHandles": ["AQAAAH8…", "AQAAAJ2…"] }'
{ "results": [ { "ok": true }, { "error": "receipt_handle_stale" } ] }
  • No credential. POST /v1/receipts/ack reads no API key or session: the receipt handle is what allows it. Browser code on any origin can call it, without credentials.
  • 1 to 10 handles a request, from any of your queues.
  • One result per handle, in order: {"ok": true}, or {"error": …}:
Error Meaning What to do
receipt_handle_stale The handle’s delivery is over: the message was received again, already acknowledged, moved to a dead-letter queue, purged, expired, or its queue was deleted. Nothing changed Don’t retry. If it was received again, that delivery will handle it
receipt_handle_invalid Not a receipt handle, or altered Check that you pass the handle exactly as received

The whole request answers 400 with 0 or more than 10 handles. A client address that sends more than 100 invalid handles a minute gets 429 with Retry-After.

A handle still works after the visibility timeout ends, until another receive takes the message. Acknowledging the same delivery twice answers receipt_handle_stale the second time.

Extend or release a message

Change how long messages stay in flight, counted from now:

curl -sS -X POST https://queues-live.ezghcloud.com/v1/receipts/visibility \
  -H "Content-Type: application/json" \
  -d '{ "entries": [ { "receiptHandle": "AQAAAH8…", "visibilityTimeoutSeconds": 120 } ] }'
  • Extend a message your consumer needs longer for, before its visibility timeout ends. A message stays in flight for at most 12 hours from its receive: a longer extension ends there.
  • Release a message your consumer won’t handle with "visibilityTimeoutSeconds": 0. It’s visible at once to the next receive.
  • visibilityTimeoutSeconds is 0 to 43,200. Requests take 1 to 10 entries and answer the same results as acknowledging, with the same errors and limits.

A consumer loop

This TypeScript loop runs with Bun, or Node.js 24 or later. Messages it fails to handle stay in flight until their visibility timeout ends, then are received again: the timeout is the delay between attempts. Give the queue a dead-letter queue so a message that always fails stops being retried.

consumer.tsts
const live = "https://queues-live.ezghcloud.com"
const { EZGH_API_KEY, ORG_ID, PROJECT_ID, QUEUE_ID } = process.env
const receiveUrl = `${live}/v1/organizations/${ORG_ID}/projects/${PROJECT_ID}/queues/${QUEUE_ID}/messages/receive`
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))

async function handle(message: { messageId: string; body: string; attributes: Record<string, string> }) {
  console.log(message.messageId, message.body)
}

while (true) {
  const res = await fetch(receiveUrl, {
    method: "POST",
    headers: { Authorization: `Bearer ${EZGH_API_KEY}`, "Content-Type": "application/json" },
    body: JSON.stringify({ maxMessages: 10, waitSeconds: 20 }),
    signal: AbortSignal.timeout(30_000),
  })
  if (res.status === 429 || res.status >= 500) {
    await sleep(Number(res.headers.get("Retry-After") ?? 1) * 1000)
    continue
  }
  if (!res.ok) throw new Error(`receive: ${res.status} ${await res.text()}`)

  const { messages } = await res.json()
  const handled: string[] = []
  for (const message of messages) {
    try {
      await handle(message)
      handled.push(message.receiptHandle)
    } catch (err) {
      console.error(`message ${message.messageId} failed (receive ${message.receiveCount})`, err)
    }
  }
  if (handled.length > 0) {
    await fetch(`${live}/v1/receipts/ack`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ receiptHandles: handled }),
    })
  }
}

Run several consumers to handle more: each receive claims different messages. On a FIFO queue, each message group goes to one consumer at a time (FIFO queues).

Errors

Status When
400 A field is out of range
401, 403 The credential is missing or invalid, or lacks queues.messages.receive on the queue
404 No such queue in the project, or it was deleted while the receive waited
429 Over the queue’s or organization’s rate limit. Wait for Retry-After
503 Try again after Retry-After

Pull consumers and subscriptions

A queue with a push subscription can still be received from. The subscription and pull consumers then compete for the queue’s messages: each message goes to whichever claims it first.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close