Job types
Job is the execution product for backend work. Different job types share the same core lifecycle:
define task -> create run -> execute attempt -> succeed, retry, cancel, replay, or dead-letterThe difference is how the run is created and how much state the task needs.
One-off jobs
Section titled “One-off jobs”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:
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:
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:
const run = await jobs.runs.get(runId)Delayed jobs
Section titled “Delayed jobs”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:
jobs.task("send-reminder", { retry: { attempts: 3, },}, async (payload) => { await notifications.send({ userId: payload.userId, template: "reminder", })})Run after a relative delay:
await jobs.enqueue("send-reminder", { userId: "user_123",}, { delaySeconds: 3600,})Run at an absolute time:
await jobs.enqueue("send-reminder", { userId: "user_123",}, { runAt: "2026-06-01T09:00:00.000Z",})Retrying jobs
Section titled “Retrying jobs”Use a retrying job when external systems can fail temporarily. Common examples are provider calls, webhook delivery, email delivery, imports, and media processing.
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:
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:
jobs.task("deliver-webhook", { retry: { attempts: 8, backoff: { type: "custom", delaysSeconds: [10, 30, 120, 300, 900, 3600, 21600], }, },}, async (payload) => { await deliverWebhook(payload)})Managed runs
Section titled “Managed runs”Use run management when your app needs status, cancellation, or replay for background work.
Read one run:
const run = await jobs.runs.get("run_123")List recent runs for a task:
const recent = await jobs.runs.list({ taskName: "sync-provider", limit: 25,})Cancel a run:
await jobs.runs.cancel("run_123")Replay a run with the same task and payload:
await jobs.runs.replay("run_123", { reason: "provider recovered",})Webhook delivery jobs
Section titled “Webhook delivery jobs”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:
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:
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:
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", })}