---
title: Queues quickstart
description: Move one message through a queue with one key - create the queue, send, lease, ack, and replay a message that failed.
type: tutorial
product: queues
summary: A queue, a message sent, leased and acked, and a failure replayed
updated: 2026-10-07
order: 1
---

This takes you from nothing to a message that was sent, leased by a worker and
acked, and then a failing message brought back from the dead-letter set. It
uses only a key and `curl`; nothing is deployed.

<Prerequisites
	items={[
		'An account, and a project with an environment',
		'A secret key holding the events:write and events:publish scopes, as SYLPHX_API_KEY',
		'curl and jq',
	]}
/>

The examples use the environment `orgs/acme/projects/shop/envs/production`;
use your own. `sylphx access whoami` prints the environment your key belongs
to, and a key only reaches its own environment.

```bash
export ENV=orgs/acme/projects/shop/envs/production
```

## 1. Create the queue

The queue id is the last segment of its name, and every producer and worker
names it, so choose it. Here a lease hides a message for a minute, and a
message dead-letters after three deliveries.

**cURL**

```bash
curl -sS -X POST "https://api.sylphx.com/v1/$ENV/queues?queue_id=emails" \
  -H "Authorization: Bearer $SYLPHX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"spec": {"visibility_timeout": "60s", "max_deliveries": 3}}'
```

**TypeScript**

```ts
import { Sylphx } from '@sylphx/sdk'

const sylphx = new Sylphx()
const { env } = await sylphx.access.whoami({})
await sylphx.events.queues.create({
  parent: env,
  queueId: 'emails',
  queue: { spec: { maxDeliveries: 3 } },
})
```

## 2. Send a message

A message is a CloudEvent. `id` and `source` identify it, `type` says what it
is, and `data` is the payload.

**cURL**

```bash
curl -sS -X POST "https://api.sylphx.com/v1/$ENV/queues/emails:send" \
  -H "Authorization: Bearer $SYLPHX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"events": [{"id": "welcome-42", "source": "https://shop.example.com/signup", "type": "email.welcome", "data": {"user": "42"}}]}'
```

**TypeScript**

```ts
await sylphx.events.queues.send({
  name: `${env}/queues/emails`,
  events: [{ id: 'welcome-42', source: 'https://shop.example.com/signup', type: 'email.welcome', data: { user: '42' } }],
})
```

The answer is the message ids, in the order you sent them.

## 3. Lease it

This is the worker's call. `wait` keeps the call open up to 20 seconds when
the queue is empty, so a worker loop does not spin.

```bash
curl -sS -X POST "https://api.sylphx.com/v1/$ENV/queues/emails:lease" \
  -H "Authorization: Bearer $SYLPHX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"max_messages": 10, "wait": "20s"}' > leased.json
jq '.messages[] | {message_id, delivery_count, type: .event.type}' leased.json
```

Each leased message carries the `event` you sent, its `delivery_count` (1 the
first time) and a `lease`. For the next minute no other worker can lease it.

## 4. Ack it

When the work is done, hand the leases back:

```bash
jq '{leases: [.messages[].lease]}' leased.json |
  curl -sS -X POST "https://api.sylphx.com/v1/$ENV/queues/emails:ack" \
    -H "Authorization: Bearer $SYLPHX_API_KEY" \
    -H "Content-Type: application/json" \
    -d @-
```

An empty `stale_leases` means every ack was applied and the message is gone.
A lease that ran out before the ack, and was taken by another worker, comes
back in `stale_leases` instead: that worker now owns the message.

## 5. Fail it, and replay it

Send a second message (step 2 with another `id`), then three times over lease
it (step 3) and **nack** it, as a worker would when the work keeps failing:

```bash
jq '{leases: [.messages[].lease]}' leased.json |
  curl -sS -X POST "https://api.sylphx.com/v1/$ENV/queues/emails:nack" \
    -H "Authorization: Bearer $SYLPHX_API_KEY" \
    -H "Content-Type: application/json" \
    -d @-
```

After the third delivery the message is dead-lettered, and the queue's counts
show it:

```bash
curl -sS "https://api.sylphx.com/v1/$ENV/queues/emails" \
  -H "Authorization: Bearer $SYLPHX_API_KEY" | jq .status
```

`dead_letter_count` is 1 and `ready_count` is 0. Once the worker is fixed,
return it to the queue:

```bash
curl -sS -X POST "https://api.sylphx.com/v1/$ENV/queues/emails:replay" \
  -H "Authorization: Bearer $SYLPHX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
```

`replayed_count` is 1, and the next lease delivers the message again with
`delivery_count` back at 1.

<Callout tone="note" title="At least once">
A worker can finish the work and crash before its ack, so the same message can
be delivered twice. Make the effect idempotent on the message id, which stays
the same across deliveries.
</Callout>

## Next

<RelatedDocs
	links={[
		{
			href: '/docs/queues',
			label: 'Queues',
			description: 'Leases, fencing, dead-letters and replay.',
		},
		{
			href: '/docs/api/queues',
			label: 'The queues collection',
			description: 'Every method, its scope and its examples.',
		},
		{
			href: '/docs/api/subscriptions',
			label: 'The subscriptions collection',
			description: "Route an environment's events into a queue.",
		},
	]}
/>
