Configuration
SCHEDULER_CONFIG_DIR must be an existing directory. Every *.yml and *.yaml file in it is loaded in sorted filename order. Each file is one project document.
- Invalid YAML or schema → that file is logged and skipped; startup continues.
- Duplicate queue name or cron
namevs an earlier accepted file → the later file is skipped entirely. - No valid files → the process still starts with no queues.
- Values support
${ENV_VAR}/$ENV_VAR. Missing vars become an empty string (easy way to ship empty secrets by mistake).
Worked example (one project file)
A publishing product might ship cms.yml like this: one queue for delayed publish, one for digests, plus a static morning cron.
global:
default_queue_concurrency: 1
retryLimit: 3
retryDelay: 60
retryBackoff: true
queues:
POST_PUBLISHING:
endpoint: 'https://cms.internal/hooks/publish'
method: 'POST'
signature_secret: ${CMS_PUBLISH_SIGNING_SECRET}
max_concurrent_jobs: 5
retryLimit: 4
retryDelay: 30
description: 'Publish scheduled posts'
REPORT_DIGEST:
endpoint: 'https://cms.internal/hooks/digest'
method: 'POST'
signature_secret: ${CMS_DIGEST_SIGNING_SECRET}
max_concurrent_jobs: 2
cron_jobs:
- name: 'cms-daily-digest'
queue: 'REPORT_DIGEST'
schedule: '0 8 * * *'
timezone: 'UTC'
payload:
type: 'daily_digest'
cleanup:
retention_days: 7
delete_after_days: 7Apps enqueue publish jobs against POST_PUBLISHING via the API. Digests fire from YAML without any client call. A second product gets its own file (billing.yml, …) with its own queue names and secrets. Do not reuse queue names across files.
Queues
Only names listed under queues are accepted by the job APIs.
queues:
WEBHOOK_DELIVERY:
endpoint: 'https://api.example.com/internal/webhooks/deliver'
method: 'POST'
signature_secret: ${WEBHOOK_SIGNING_SECRET}
max_concurrent_jobs: 20
retryLimit: 4
retryDelay: 30
expireInSeconds: 3600
description: 'Outbound webhook delivery'
REPORT_DIGEST:
endpoint: 'https://api.example.com/internal/reports/digest'
method: 'POST'
signature_secret: ${REPORT_DIGEST_SIGNING_SECRET}
max_concurrent_jobs: 3
wait_interval_after_success_seconds: 120| Field | Required | Notes |
|---|---|---|
endpoint | Yes | Worker delivery URL |
signature_secret | Yes | Signs outbound requests (not the API secret) |
method | No | Default POST |
max_concurrent_jobs / concurrency | No | Falls back to global.default_queue_concurrency |
retryLimit, retryDelay, retryBackoff | No | Queue overrides global |
expireInSeconds | No | Must be ≥ 1 if set |
wait_interval_after_success_seconds | No | Pause after a successful delivery before taking the next job |
Static cron jobs
Recurring work belongs in YAML. The schedule API only enqueues the next occurrence of a cron expression.
cron_jobs:
- name: 'daily-report-digest'
queue: 'REPORT_DIGEST'
schedule: '0 8 * * *'
timezone: 'UTC'
payload:
type: 'daily_digest'| Field | Required | Notes |
|---|---|---|
name | Yes | Unique across loaded files |
queue | Yes | Must exist in the same file’s queues |
schedule | Yes | Cron expression |
timezone | No | Default UTC |
payload | No | Object delivered to the handler |
Cleanup
Optional cleanup block. Boss-wide keys (maintenance_interval_seconds, warning_retention_days) come from the first loaded file that defines them. Per-file retention applies to that file’s queues.
cleanup:
maintenance_interval_seconds: 60
retention_days: 7
delete_after_days: 7
warning_retention_days: 30archive_completed_after_seconds / archive_failed_after_seconds are accepted for compatibility but are no-ops on pg-boss v12.
Exposing the service
Job APIs authenticate with REQUEST_SIGNING_SECRET only. Treat that secret like a root API key.
- Terminate TLS at your reverse proxy or mesh.
- Keep Postgres off the public internet.
- Use different secrets for the API and each queue’s
signature_secret. - Prefer long random values; never commit real secrets in YAML.
GET /api/healthis unauthenticated (fine for probes).- If you run the dashboard, protect it separately (auth / network policy).
POSTGRES_SSL=true connects with rejectUnauthorized: false. Prefer private network Postgres or proper cert verification outside that flag when you can.