tailthemesbuy this theme · $39
Skip to content
halyard
Sections menu
Guides menu

Guides

Deliver your first message

This walkthrough takes an empty account to a consumer that acknowledges work, in about ten minutes.

Updated 13 August 20268 minute readAPI v2.4

Before you start

You need an API key with the queues:admin and queues:write scopes. Keys are scoped to one environment, and the sandbox key is free: it holds 50,000 messages and drops them after seven days.

terminal

export HALYARD_KEY="hk_sandbox_2f9c…"
export API="https://api.halyard.dev/v1"

curl "$API/queues" -H "Authorization: Bearer $HALYARD_KEY"
# {"queues": [], "has_more": false}

Every example below is a plain curl. Halyard publishes client libraries for TypeScript, Python and Go, and none of this walkthrough needs one.

Publish a message

A queue is created by name. The visibility timeout you set here is the default for every lease taken against it, and a consumer can shorten it per request.

POST/queues201

curl "$API/queues" \
  -H "Authorization: Bearer $HALYARD_KEY" \
  -d '{
    "name": "orders",
    "visibility_timeout": "30s",
    "max_deliveries": 5
  }'

# 201 Created
# {"name":"orders","region":"eu-west","created_at":"2026-08-13T09:14:22Z"}

Publishing stores the body verbatim, up to 256 KB. Halyard answers with 202 once the write is durable in three availability zones, so a successful response means the message survives a node loss.

POST/queues/orders/messages202

curl "$API/queues/orders/messages" \
  -H "Authorization: Bearer $HALYARD_KEY" \
  -H "Idempotency-Key: 8f14e45f" \
  -d '{
    "body": {"order_id": "ord_10427", "total_cents": 4990},
    "ordering_key": "ord_10427"
  }'

# 202 Accepted
# {"id":"msg_01HQ8Z3F","queue":"orders","state":"queued","delivery_count":0}

Tip

Idempotency keys are remembered for 24 hours. A repeat inside that window returns the original message id instead of enqueueing a second copy, which makes a publish safe to retry after a timeout.

Lease and acknowledge

A lease is a claim. While it holds, the message is invisible to every other consumer, so two workers cannot pick up the same order. Ask for up to 25 messages at once and set wait to hold the connection open until one arrives.

POST/queues/orders/leases200

curl "$API/queues/orders/leases" \
  -H "Authorization: Bearer $HALYARD_KEY" \
  -d '{"max": 25, "wait": "20s"}'

# 200 OK
# {
#   "lease": "lse_01HQ8Z",
#   "expires_at": "2026-08-13T09:15:31Z",
#   "messages": [
#     {"id":"msg_01HQ8Z3F","body":{"order_id":"ord_10427"},"delivery_count":1}
#   ]
# }

Acknowledging deletes the message and closes the lease. Acks are idempotent, so a retried ack after a network failure returns 204 rather than an error.

POST/queues/orders/leases/lse_01HQ8Z/ack204

curl "$API/queues/orders/leases/lse_01HQ8Z/ack" \
  -X POST -H "Authorization: Bearer $HALYARD_KEY"

# 204 No Content

Work that outlives the timeout should extend the lease rather than race it. One PATCH per half-timeout is the pattern the SDKs use, and it is documented at extend a lease.

How a message moves

Five states, one loop. Everything Halyard guarantees comes from this path, so it is worth reading once before you tune anything.

  • trigger

    POST /messages

    The producer sends a body and an idempotency key. Halyard answers 202 once the write is durable.

    • step

      Durable log

      The message is written to three availability zones and ordered inside its ordering key.

      • step

        Lease held

        A consumer takes the message for visibility_timeout seconds. No one else can see it in that window.

        • check

          Acked before the timeout?

          • yes

            • end · success

              Deleted

              The message leaves the queue and the lease closes. Acks are idempotent.

          • no

            • step

              Redelivered

              delivery_count increments and the backoff doubles, up to five minutes. The message becomes visible again.

              returns to Lease held · the next consumer leases it

              • end · warning

                Dead letters

                After max_deliveries attempts the message moves to the dead-letter queue with its full history.

The path of a single Halyard message from publish to acknowledgement, including the redelivery loop and the dead-letter exit.

Handle failure

A consumer that crashes without acking is not an error condition. The lease expires, the message becomes visible again, and delivery_count increments. Nack when you already know the work failed: it returns the message immediately instead of paying for the whole timeout.

POST/queues/orders/leases/lse_01HQ8Z/nack204

curl "$API/queues/orders/leases/lse_01HQ8Z/nack" \
  -X POST -H "Authorization: Bearer $HALYARD_KEY" \
  -d '{"retry_after": "60s", "reason": "payment_provider_timeout"}'

# 204 No Content

Backoff doubles from one second and stops at five minutes. After max_deliveries attempts the message moves to the queue’s dead-letter queue with its full delivery history, where you can inspect it and replay it. See dead letters for both endpoints.

Careful

A dead-letter queue with no alarm on it is a silent data loss policy. Alert on halyard.dead_letters.depth above zero, not on a threshold.

Go to production

Four settings separate a working consumer from a production one. The loop below is the shape the TypeScript SDK ships, written out so nothing is hidden.

consumer.ts

import { Halyard } from "@halyard/node";

const halyard = new Halyard({ key: process.env.HALYARD_KEY });

for await (const lease of halyard.queue("orders").leases({
  max: 25,          // batch: one round trip per 25 orders
  wait: "20s",      // long poll: no busy loop, no idle cost
  region: "eu-west" // read from the region that wrote
})) {
  for (const message of lease.messages) {
    await fulfil(message.body);
  }
  await lease.ack();
}

Ordering keys hold order inside a key and allow parallelism across keys, so one slow customer never blocks another. Retention runs to 14 days on paid plans, and a queue can be paused without losing what is already in it.