Jobs, cron and the dead-letter queue
How to run work outside a request: what a job is, how it is retried, how cron creates jobs, and what to do with the jobs that end up dead.
1. Enqueue a job
A job is one background call to a function. Enqueue it from a server with a service_role key.
| Option | Default | Meaning |
|---|---|---|
payload | null | JSON given to the function as the request body |
delaySecs | 0 | Seconds to wait before the first attempt. The job stays pending until then. |
maxAttempts | 3 | Total number of attempts, the first included |
You read a job back with getJob(id) or listJobs({ status, limit }) (limit is between 1 and 500, default 50). The job has a status, attempts, max_attempts, error and the dates scheduled_at, started_at, completed_at.
enqueueJob, getJob and listJobs need a service_role key. A public key receives 403.
2. What your function receives
A job calls your function like any other request, with three differences you must handle:
| Part | Value |
|---|---|
| Method | JOB (not POST) |
| Path | /jobs/<job id> |
| Body | The job's payload, as JSON |
| Identity headers | None: x-aura-role and x-aura-user-id are absent |
A real call logged {"method":"JOB","path":"/jobs/d443d541-…","role":null,"userId":null,"body":"{\"items\":[\"a\",\"b\"],\"order\":42}"}.
A job runs without a user: do not rely on x-aura-user-id. If the work belongs to a user, put the user's id in the payload.
3. Lifecycle and retries
- A job runs at least once. It can run twice if a worker stops while it is running, so make the function idempotent.
- A failed attempt is retried after a fixed delay of 5 seconds, until
maxAttemptsis reached. The job is thendead. The delay and the default attempt count are service settings (JOB_RETRY_DELAY_SECS,JOB_MAX_RETRIES). - A job that is still
runningafter its lease (450 seconds by default) is reclaimed by the service. - Finished jobs are kept for 14 days by default.
A job is completed as soon as your function answers, whatever the status code. In a real run, a function that returned 500 produced a completed job after one attempt and was never retried.
A job fails and is retried only when the call itself fails: the function does not exist or is inactive, the runtime is unreachable, or the call times out. If you want a retry when your own code detects an error, enqueue a new job from the handler (with delaySecs for a back-off) instead of relying on the status code.
In the same real run, a job for a function that does not exist went through this sequence: attempt 1 failed at 3 s with Fonction 'ghost' introuvable, attempt 2 failed at about 9 s, and the job became dead with attempts equal to max_attempts.
4. The dead-letter queue
Dead jobs stay in the queue and can be listed and retried. These routes have no SDK method yet: use HTTP with a service_role key.
A retry puts the job back to pending without resetting its attempt counter, so it gets exactly one more attempt and returns to dead if it fails again. Fix the cause (deploy the missing function, for example) before you retry.
5. Schedule with cron
A cron entry does not call the function itself: each time it fires, it enqueues a job.
- The schedule has 5 fields (
min hour day month weekday) or 6 fields (sec min hour day month weekday). A 5-field schedule gets0seconds in front:*/1 * * * *is stored as0 */1 * * * *. - Aliases such as
@dailyare refused with a400that states the expected format. - Times are in UTC.
last_runandnext_runarenulluntil the first firing. In a real run, a schedule created at 15:40 fired at 15:41:00 and then showednext_run15:42:00.- The payload is frozen when you create the entry.
- Deleting an unknown name returns
404. Deleting a cron entry also stops it from firing. - The SDK has no
getCronJoborupdateCronJob: delete the entry and create it again.
For purely database work, the Postgres pg_cron extension avoids the round trip to a function: see the Cron page.