Skip to content

Queues, workers, schedules and dead letter

Queues, workers, schedules and dead letter

The work your web request should not do.

The work your web request should not do.

The work your web request should not do.

Gantry runs background jobs on durable queues. Retries you can read, schedules that hold through a deploy, and a trace for every attempt. Four SDKs, one binary to run locally.

Gantry runs background jobs on durable queues. Retries you can read, schedules that hold through a deploy, and a trace for every attempt. Four SDKs, one binary to run locally.

100,000 runs a month on the free plan. No card, no sales call.

runs — last 60 seconds

live

id

queue

state

try

took

8f2a1c

emails

done

1

412 ms

8f2a1d

webhooks

retry 2

2

1.9 s

8f2a1e

exports

running

1

6.4 s

8f2a1f

emails

done

1

388 ms

8f2a20

billing

dead letter

5

—

8f2a21

webhooks

done

3

2.2 s

queue depth 1,284

oldest 3.1 s

2.1B

2.1B

runs a month

14 ms

14 ms

enqueue, p95

99.98%

99.98%

uptime, last 90 days

38

38

regions

Running in production at

Northwind Freight

Kettle & Co

Blume Health

Ardwick Radio

Vesper Labs

Parcelgrid

Two Rivers Post

Halden Foods

Four primitives. Nothing else to learn.

Four primitives. Nothing else to learn.

Four primitives. Nothing else to learn.

A queue holds work. A worker takes it. A schedule puts it there on time. Dead letter keeps what failed, with the payload, so you can look at it on Monday.

A queue holds work. A worker takes it. A schedule puts it there on time. Dead letter keeps what failed, with the payload, so you can look at it on Monday.

Queues

Durable, ordered per key, and idempotent on the id you give them.

gantry.enqueue(“emails”, { to })

Workers

Long polling, graceful shutdown, and a concurrency limit per queue.

gantry worker --concurrency 32

Schedules

Cron with a timezone, and a window so a missed run still fires.

every(“0 6 * * 1-5”, tz)

Dead letter

Five attempts, then parked with the payload and the last error.

gantry dlq replay --queue billing

One run, from your request to the answer.

One run, from your request to the answer.

One run, from your request to the answer.

Every step is timed and every step is visible. The numbers below are the median of a small payload on the shared cluster.

Every step is timed and every step is visible. The numbers below are the median of a small payload on the shared cluster.

enqueue

14 ms

Your request writes the job and returns.

lease

40 ms

A worker takes it with a lock and a deadline.

attempt

412 ms

Your handler runs. Logs and timing are kept.

retry

if it fails

Failure backs off: 1 s, 4 s, 16 s, 1 m, 5 m.

done

Result stored for 30 days. Or parked in dead letter.

A retry you can read.

A retry you can read.

A retry you can read.

Every attempt keeps the error, the response code and the wait before the next try. No guessing from a log line that scrolled past. Backoff is 1 s, 4 s, 16 s, 1 m, 5 m, and you can set your own per queue.

Idempotency key per job, so a double enqueue is one run

Payload kept with the failure for 30 days

Replay from the dashboard or the CLI

try

at

result

next

1

attempt 1

09:41:02

at: 09:41:02

timeout after 30 s

result: timeout after 30 s

retry in 1 s

next: retry in 1 s

2

attempt 2

09:41:03

at: 09:41:03

502 from upstream

result: 502 from upstream

retry in 4 s

next: retry in 4 s

3

attempt 3

09:41:07

at: 09:41:07

502 from upstream

result: 502 from upstream

retry in 16 s

next: retry in 16 s

4

attempt 4

09:41:23

at: 09:41:23

429, retry-after 60

result: 429, retry-after 60

retry in 1 m

next: retry in 1 m

5

attempt 5

09:42:23

at: 09:42:23

200 in 380 ms

result: 200 in 380 ms

done

next: done

What changes the week after you ship it.

What changes the week after you ship it.

What changes the week after you ship it.

The 3 a.m. page stopped

“We were retrying in a cron that ran every five minutes and nobody knew which jobs were stuck. Now the queue depth is on the wall and the dead letter is empty by lunch.”

Platform lead, logistics, 40 engineers

Deploys stopped losing work

“Workers finish what they leased before they exit. We deploy eleven times a day and the last dropped job was in March.”

Staff engineer, health, 120 engineers

The bill got smaller

“We were paying per connection on the old queue. Per run turned out to be a third of it, and the free tier covers staging.”

CTO, marketplace, 12 engineers

Who this is for, and who it is not.

Who this is for, and who it is not.

Who this is for, and who it is not.

We would rather lose the sale than have you fight the tool for a quarter. If the right column sounds like you, the honest answer is somewhere else.

We would rather lose the sale than have you fight the tool for a quarter. If the right column sounds like you, the honest answer is somewhere else.

A good fit

You send email, build exports or call an API that is slower than your request

You deploy more than once a day and something is always mid-flight

You have a cron that checks whether the other cron ran

Somebody on the team can name the job that silently fails

Not a good fit

You need a workflow engine with branching, fan-out and human approval steps

Your jobs run for hours and need checkpointing inside the run

You are happy with one cron and a lock, which is a real answer

You cannot run a worker anywhere, including a container in your own cloud

Against what you are probably running today.

Against what you are probably running today.

Against what you are probably running today.

Cron plus a lock gets a long way and costs nothing. A Redis queue with your own worker is fine until the retries and the dead letter become a project. This table is where each one stops.

Cron plus a lock gets a long way and costs nothing. A Redis queue with your own worker is fine until the retries and the dead letter become a project. This table is where each one stops.

Cron and a lock

Redis and a worker

Cloud queue

Gantry

Survives a deploy

Survives a deploy

No

Cron and a lock: No

Depends on your code

Redis and a worker: Depends on your code

Yes

Cloud queue: Yes

Yes

Gantry: Yes

Retry you can read

Retry you can read

No

Cron and a lock: No

You write it

Redis and a worker: You write it

Partly

Cloud queue: Partly

Yes

Gantry: Yes

Dead letter with payload

Dead letter with payload

No

Cron and a lock: No

You write it

Redis and a worker: You write it

Yes

Cloud queue: Yes

Yes

Gantry: Yes

Schedules with a timezone

Schedules with a timezone

Crontab only

Cron and a lock: Crontab only

You write it

Redis and a worker: You write it

Extra service

Cloud queue: Extra service

Yes

Gantry: Yes

Ordering per key

Ordering per key

No

Cron and a lock: No

You write it

Redis and a worker: You write it

FIFO queues only

Cloud queue: FIFO queues only

Yes

Gantry: Yes

Trace per attempt

Trace per attempt

Logs, if you kept them

Cron and a lock: Logs, if you kept them

No

Redis and a worker: No

Metrics only

Cloud queue: Metrics only

Yes

Gantry: Yes

Local development

Local development

Yes

Cron and a lock: Yes

Yes

Redis and a worker: Yes

Emulator

Cloud queue: Emulator

Same binary

Gantry: Same binary

Priced on

Priced on

Your server

Cron and a lock: Your server

Your Redis

Redis and a worker: Your Redis

Requests and storage

Cloud queue: Requests and storage

Runs

Gantry: Runs

A trace for every attempt, not a log line for some of them.

A trace for every attempt, not a log line for some of them.

A trace for every attempt, not a log line for some of them.

Each attempt records the wait, the lease, and whatever your handler chose to mark. Below is one run of a 1.4 second invoice job, exactly as the dashboard draws it.

Each attempt records the wait, the lease, and whatever your handler chose to mark. Below is one run of a 1.4 second invoice job, exactly as the dashboard draws it.

run 8f2a20 — invoices — attempt 1 of 1

run 8f2a20 — invoices — attempt 1 of 1

1.38 s total

1.38 s total

enqueue

enqueue

14 ms

queued

queued

1.2 s

lease

lease

40 ms

handler: fetch invoice

handler: fetch invoice

310 ms

handler: render pdf

handler: render pdf

820 ms

handler: upload

handler: upload

190 ms

ack

ack

8 ms

Marks from your code

job.mark(“fetch invoice”) is one line and shows up as a span.

Kept for your window

7, 30 or 90 days by plan, and exported as JSON lines whenever you want.

Shipped out

Send the same spans to Datadog or any OTLP endpoint, one config block.

Thirty-eight regions, and the one next to your database is the one that matters.

Thirty-eight regions, and the one next to your database is the one that matters.

Thirty-eight regions, and the one next to your database is the one that matters.

Workers run wherever you run. The control plane lives in Frankfurt and Virginia, and enqueue goes to the nearest of the three edges. If your database is in Sydney, put the worker in Sydney.

Workers run wherever you run. The control plane lives in Frankfurt and Virginia, and enqueue goes to the nearest of the three edges. If your database is in Sydney, put the worker in Sydney.

ams

fra

lhr

cdg

arn

waw

mad

mil

dub

zrh

iad

ord

dfw

sea

sjc

lax

yyz

yul

gru

scl

bog

mex

nrt

hnd

icn

sin

hkg

bom

del

syd

mel

akl

jnb

cpt

dxb

bah

tlv

ist

It plugs into what you already run.

It plugs into what you already run.

It plugs into what you already run.

Bring your own Postgres or use ours. Ship traces to Datadog, errors to Sentry, pages to PagerDuty. The worker is one binary, so it runs wherever your app already runs.

Bring your own Postgres or use ours. Ship traces to Datadog, errors to Sentry, pages to PagerDuty. The worker is one binary, so it runs wherever your app already runs.

Postgres

Redis

SQS

Kafka

Datadog

Sentry

Slack

PagerDuty

Vercel

Fly.io

Railway

GitHub Actions

Moving over takes a week, and most of it is waiting.

Moving over takes a week, and most of it is waiting.

Moving over takes a week, and most of it is waiting.

01

Dual write for a week

Enqueue to both your queue and Gantry. Nothing consumes from Gantry yet, so the only risk is a bigger bill for a week.

02

Move one queue

Pick the noisiest one, point its worker at Gantry, and watch both dashboards. Most teams pick the one that wakes them up.

03

Move the rest, delete the glue

The retry loop, the lock table and the cron that checks the cron all go. That is usually the part people enjoy.

If you are coming from Sidekiq, Celery, BullMQ or SQS, the migration guide has the mapping for the names you already use.

What you get to delete.

What you get to delete.

What you get to delete.

This is the part teams underestimate. The queue is not the work, the glue around it is, and the glue is what you stop maintaining.

This is the part teams underestimate. The queue is not the work, the glue around it is, and the glue is what you stop maintaining.

The retry loop

About 80 lines, plus the test nobody trusts

retry.ts

The lock table

The lock table

And the migration that added it in 2023

About 80 lines, plus the test nobody trusts: And the migration that added it in 2023

locks.sql

retry.ts: locks.sql

The cron that checks the cron

The cron that checks the cron

Every five minutes, forever

About 80 lines, plus the test nobody trusts: Every five minutes, forever

watchdog.sh

retry.ts: watchdog.sh

The dead job spreadsheet

The dead job spreadsheet

Maintained by whoever was on support

About 80 lines, plus the test nobody trusts: Maintained by whoever was on support

failed-jobs.xlsx

retry.ts: failed-jobs.xlsx

The Slack channel for stuck work

The Slack channel for stuck work

It can stay, it will just be quiet

About 80 lines, plus the test nobody trusts: It can stay, it will just be quiet

#queue-alerts

retry.ts: #queue-alerts

From install to your first run in three steps.

From install to your first run in three steps.

From install to your first run in three steps.

Node shown here. Python, Go and Ruby are the same three steps with the same names.

Node shown here. Python, Go and Ruby are the same three steps with the same names.

01

Install

terminal

npm i @gantry/node

One dependency. The CLI comes with it.

02

Enqueue

app/signup.ts

await gantry.enqueue(“emails”, {
to: user.email,
template: “welcome”
})

Returns in 14 ms. Your request is done.

03

Run a worker

terminal

gantry worker emails \
--concurrency 32

Locally or in your cluster. Same binary.

Priced on runs, not on seats.

Priced on runs, not on seats.

Priced on runs, not on seats.

A run is one attempt of one job. Retries of the same job inside a minute count once. No per connector fee, no charge for the dashboard, no minimum.

A run is one attempt of one job. Retries of the same job inside a minute count once. No per connector fee, no charge for the dashboard, no minimum.

Free

$0

$0

100k runs a month

1 queue concurrency 8

7 day history

Community support

Start free

Team

$99

$99

per month, 5M runs

Unlimited queues

30 day history and replay

Schedules with a timezone

Email support, one business day

Start free

Scale

$0.14

$0.14

per 10k runs above 5M

Dedicated cluster on request

90 day history

SSO and audit log

Shared channel, 4 hour response

Talk to us

Boring where it should be boring.

Boring where it should be boring.

Boring where it should be boring.

Payloads encrypted at rest

AES-256, keys rotated every 90 days. You can bring your own key on Scale.

Retention you choose

7, 30 or 90 days. Deleted means deleted, including from backups within 35 days.

Four subprocessors

Named on the security page with what each one sees. No advertising, ever.

SOC 2 Type II

Report from May 2026, available under NDA. Pen test twice a year.

The whole security page, including what we do not have

The whole security page, including what we do not have

What engineers ask before they switch.

What engineers ask before they switch.

What engineers ask before they switch.

What happens to a job during a deploy?

Is it exactly once?

Can I run it on my own Postgres?

How big can a payload be?

What counts as a run?

What if I need to leave?

Start free. The first 100,000 runs a month are on us.

Start free. The first 100,000 runs a month are on us.

Start free. The first 100,000 runs a month are on us.

No card, no sales call, no trial that expires on a Friday. If it does not fit, the export is one command.

No card, no sales call, no trial that expires on a Friday. If it does not fit, the export is one command.

Create a free website with Framer, the website builder loved by startups, designers and agencies.