# Jobs & cron

> Run background work and scheduled tasks with the built-in queue.

_Updated: 2026-06-10_

Railbase has a built-in background job runner and scheduler — no Redis, no
separate worker process. Use it for anything that shouldn't block a request:
sending email, generating documents, sweeping expired data.

## Define a job

Register a handler under a **kind** at boot, then enqueue work against that kind at
runtime. The registry (and the store) come up during `App.Run`, so register from
inside `OnBeforeServe` — calling `app.Jobs()` directly in the `ExecuteWith`
callback returns nil (the subsystem doesn't exist yet):

```go
import "github.com/railbase/railbase/pkg/railbase/jobs"

cli.ExecuteWith(func(app *railbase.App) {
    app.OnBeforeServe(func(_ chi.Router) {
        // Register: maps a "kind" to a handler. j.Payload is the raw
        // JSON the enqueuer passed.
        app.Jobs().Register("email.welcome", func(ctx context.Context, j *jobs.Job) error {
            return sendWelcome(ctx, j.Payload)
        })
    })
})

// Enqueue (e.g. from a hook or REST handler — any time after boot):
_, err := app.JobsStore().Enqueue(ctx, "email.welcome",
    map[string]any{"user_id": id},
    jobs.EnqueueOptions{Delay: 30 * time.Second})
```

Jobs retry on failure (default 5 attempts). Return `jobs.ErrPermanent` to fail
without retrying.

## Inspect jobs from the CLI

```bash
railbase jobs list
railbase jobs show <id>
railbase jobs run-now <id>
railbase jobs cancel <id>
railbase jobs recover        # requeue jobs stuck in "running"
```

> [!NOTE]
> Like every command that opens the vault, these need exclusive access to the
> data file — stop the server first, or you'll get *"vault: file is locked by
> another process"*. To inspect and manage jobs on a **running** instance, use
> the admin console instead (see below).

## Schedule recurring work

Persisted schedules are managed with `railbase cron` (standard 5-field cron
expressions):

```bash
railbase cron upsert nightly-report "0 3 * * *" report.generate
railbase cron list
railbase cron run-now nightly-report
railbase cron disable nightly-report
```

You can also schedule from a JavaScript hook:

```js
$app.cronAdd("nightly", "0 3 * * *", () => { /* … */ });
```

> [!TIP]
> Scheduled backups are just a built-in job kind — you can manage them here or
> from the admin's Backups screen. See [Backups & restore](backups-and-restore).

The admin console shows the schedule table and lets you run, enable/disable, or
edit entries — see [Operating from the admin](operations).
