Skip to content

Job types

Job is the execution product for backend work. Different job types share the same core lifecycle:

Terminal window
define task
-> create run
-> execute attempt
-> succeed, retry, cancel, replay, or dead-letter

The difference is how the run is created and how much state the task needs.

Use a one-off job when something should run once after an app event. Common examples are sending notifications, processing an upload, syncing one account, or calling a provider API.

Define the task:

Terminal window
const jobs = job({
name: "background",
namespace: "app",
})
app.use(jobs)
jobs.task("notify-user", {
retry: {
attempts: 5,
backoff: "exponential",
initialDelaySeconds: 5,
maxDelaySeconds: 300,
},
}, async (payload, ctx) => {
await notifications.send({
userId: payload.userId,
template: payload.template,
data: payload.data,
})
})

Enqueue it from a route:

Terminal window
app.post("/signup", async (request) => {
const body = await request.json()
const run = await jobs.enqueue("notify-user", {
userId: body.userId,
template: "welcome",
data: {
plan: body.plan,
},
}, {
idempotencyKey: `welcome:${body.userId}`,
})
return Response.json({
queued: true,
runId: run.runId,
})
})

Read the run later:

Terminal window
const run = await jobs.runs.get(runId)

Use a delayed job when the task should run once in the future. Common examples are reminders, trial lifecycle notifications, delayed cleanup, and deferred provider sync.

Define the task:

Terminal window
jobs.task("send-reminder", {
retry: {
attempts: 3,
},
}, async (payload) => {
await notifications.send({
userId: payload.userId,
template: "reminder",
})
})

Run after a relative delay:

Terminal window
await jobs.enqueue("send-reminder", {
userId: "user_123",
}, {
delaySeconds: 3600,
})

Run at an absolute time:

Terminal window
await jobs.enqueue("send-reminder", {
userId: "user_123",
}, {
runAt: "2026-06-01T09:00:00.000Z",
})

Use a retrying job when external systems can fail temporarily. Common examples are provider calls, webhook delivery, email delivery, imports, and media processing.

Terminal window
jobs.task("sync-provider", {
retry: {
attempts: 5,
backoff: "exponential",
initialDelaySeconds: 30,
maxDelaySeconds: 900,
},
}, async (payload) => {
await provider.syncAccount(payload.accountId)
})

Use a fixed retry policy when every retry should wait the same base interval:

Terminal window
jobs.task("send-notification", {
retry: {
attempts: 4,
backoff: "fixed",
initialDelaySeconds: 60,
},
}, async (payload) => {
await notifications.send(payload)
})

Use a custom retry policy when a provider gives you a known retry cadence:

Terminal window
jobs.task("deliver-webhook", {
retry: {
attempts: 8,
backoff: {
type: "custom",
delaysSeconds: [10, 30, 120, 300, 900, 3600, 21600],
},
},
}, async (payload) => {
await deliverWebhook(payload)
})

Use run management when your app needs status, cancellation, or replay for background work.

Read one run:

Terminal window
const run = await jobs.runs.get("run_123")

List recent runs for a task:

Terminal window
const recent = await jobs.runs.list({
taskName: "sync-provider",
limit: 25,
})

Cancel a run:

Terminal window
await jobs.runs.cancel("run_123")

Replay a run with the same task and payload:

Terminal window
await jobs.runs.replay("run_123", {
reason: "provider recovered",
})

Webhook delivery is the best example of why Job exists. A webhook product receives an event, records it, then creates a delivery run.

Define the delivery task:

Terminal window
jobs.task("deliver-webhook", {
retry: {
attempts: 8,
backoff: {
type: "custom",
delaysSeconds: [10, 30, 120, 300, 900, 3600, 21600],
},
},
}, async (payload, ctx) => {
const response = await fetch(payload.url, {
method: "POST",
headers: {
"content-type": "application/json",
"x-layeron-event-id": payload.eventId,
"x-layeron-delivery-id": ctx.runId,
},
body: JSON.stringify(payload.body),
})
if (response.status === 410) {
await webhooks.disableEndpoint(payload.endpointId)
return
}
if (!response.ok) {
throw new Error(`webhook delivery failed: ${response.status}`)
}
})

Create a delivery run:

Terminal window
await jobs.enqueue("deliver-webhook", {
endpointId: "ep_123",
eventId: "evt_123",
url: "https://example.com/webhook",
body: {
type: "invoice.paid",
data: {
invoiceId: "inv_123",
},
},
}, {
idempotencyKey: "webhook:ep_123:evt_123",
})

Replay failed deliveries:

Terminal window
const failed = await jobs.runs.list({
taskName: "deliver-webhook",
status: "dead_lettered",
limit: 50,
})
for (const run of failed.runs) {
await jobs.runs.replay(run.runId, {
reason: "endpoint recovered",
})
}