Job Queue
Delayed job scheduling with webhook delivery: hand the server a JSON payload, a URL, and a time, and it POSTs the payload to that URL when the time comes - with per-queue retry policies, exponential backoff, dead letter queues, priority lanes, pause/resume, and runtime statistics.
When to reach for it: delayed webhooks (send a reminder in an hour), retrying calls to a flaky downstream with backoff, spreading bursts of work over time, priority lanes for background work the server executes for you.
When not to: this is a push model - the server calls your endpoint. If your own workers should pull messages off a queue and acknowledge them (SQS-style), that is the Message Queue; if you want to fan out ephemeral messages to whoever is listening right now, with no persistence or retries, that is Pub/Sub.
One service, three CLI commands
The HTTP API is one service, but the CLI splits it by role:
snug job-queues- queue manifests: create, configure retries and DLQ, pause/resume, redrive. Takes queue IDs (jq_...).snug jobs- individual jobs: schedule, list, inspect, cancel. Takes job IDs (job_...) and queue names.snug queues- read-only runtime stats (depth, success rate).
Concepts
- Every queue has a manifest holding its retry policy, DLQ config,
default priority, retention, and active flag. The
defaultqueue is auto-created on first use; any other queue must be created before you can schedule into it - otherwise404 queue_not_found. - Jobs fire as signed webhook POSTs. A 2xx response counts as success; anything else (or a 30-second timeout) is a failed attempt.
- Priorities -
critical,high,normal,low,bulk- are processing lanes within a queue, not scheduling times. - Retries and the DLQ: failed attempts retry with exponential backoff
until the attempt budget is spent; then the job is
failedand, if the queue has a DLQ, dead-lettered for inspection and redrive. - Pause holds delivery, not intake: a paused queue still accepts new
jobs; due jobs wait until
resume. - Jobs are retained, then pruned: terminal jobs (completed, failed, cancelled) are cleaned up after the retention window (7 days by default).
Create and configure a queue
snug job-queues create docs4-emails -d "Docs email queue" \
--enable-dlq --max-attempts 2 --base-delay-ms 500
snug job-queues list --search docs4
snug job-queues info jq_fvXKFGTtWYGa
snug job-queues update jq_fvXKFGTtWYGa --retention-days 3 --default-priority low
snug job-queues pause jq_fvXKFGTtWYGa # active: false - delivery stops
snug job-queues resume jq_fvXKFGTtWYGa # active: true - held jobs fire
snug job-queues delete jq_fvXKFGTtWYGa --forcecreate returns only {id, name, created_at} - use info for the resolved
configuration and live job counts. Names are unique per user (409 queue_already_exists on a duplicate), and deleting a queue with pending jobs
fails with 409 queue_has_pending_jobs unless you pass --force.
Schedule a job
snug jobs schedule -w https://example.com/hook -q docs4-emails -d 60 \
-p high -j '{"to":"user@example.com","template":"welcome"}'Over HTTP the same call is POST /api/v1/jobs/schedule; response captured
live:
curl -X POST -H "Authorization: Bearer $SNUG_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"queue":"docs4-emails","delay_seconds":60,"payload":{"to":"user@example.com"},
"webhook":{"url":"https://example.com/hook"}}' \
http://localhost:4000/api/v1/jobs/schedule{
"status": 201,
"msg": "Created",
"data": { "job_id": "job_MApgwfcqfspu", "scheduled_at": 1787898570,
"queue": "docs4-emails", "status": "scheduled" }
}delay_seconds must be greater than 0 (400 INVALID_REQUEST otherwise).
The API alternatively accepts an absolute scheduled_at unix timestamp, which
must be at least 5 seconds in the future; the CLI only exposes --delay.
--payload is required - pass '{}' if the job needs no body.
The webhook receives the payload JSON-serialized inside a delivery envelope
({"id": "jobqueue_...", "type": "test.webhook", "message": "<your payload as a string>", ...}), signed with the job's secret - see
delivery details.
Batch scheduling
snug jobs schedule-batch -f jobs.json takes a bare JSON array of schedule
requests (up to 1000) and returns per-job results - verified with one invalid
entry: "success_count": 2, "failure_count": 1, the bad row reported in
failed with its array index. Exception: an unknown queue name fails the
whole request with 404 queue_not_found and nothing is scheduled.
Inspect and cancel
snug jobs list --queue docs4-emails --status scheduled --limit 20
snug jobs info job_cKKaaVwTgDrM
snug jobs cancel job_cKKaaVwTgDrM
snug jobs cancel-all --queue docs4-emails --force # your scheduled jobs onlyStatuses are scheduled, processing, completed, failed, cancelled.
cancel-all is the emergency kill switch for your own scheduled jobs,
including jobs parked between retry attempts. Jobs and queues are
per-user: another user's info/cancel on your resources fails with
403 FORBIDDEN (reproduced with a second token).
Two operations require a platform admin token: snug jobs admin-cancel-all
(bulk-cancel across users, optionally scoped by --user / --queue) and
snug jobs cleanup (prune terminal jobs past retention on demand).
Monitor
snug queues stats docs4-emails
snug health job-queuestats reports queue depth (scheduled), in-flight count, one-hour
completed/failed counters, and success rate - shapes and caveats in
Reliability. The health check
(GET /health/job_queue, no auth) reports Redis connectivity plus
scheduler_running and processor_running.
Behaviors and gotchas
Each of these was reproduced live:
- Scheduling into a nonexistent custom queue:
404 queue_not_foundwith the hint "Custom queues must be created before use". Onlydefaultauto-creates. - A queue's
default_priorityapplies only to HTTP requests that omitpriority. The CLI always sends one (-pdefaults tonormal), so a manifest default never takes effect on CLI-scheduled jobs. - While paused, a due job stayed
scheduledwith 0 attempts; onresumeit was delivered within seconds. - Error-code casing is split: queue-domain failures use lowercase codes
(
queue_not_found,queue_already_exists,queue_has_pending_jobs), while the job handlers use the shared uppercase codes (INVALID_REQUEST,FORBIDDEN,NOT_FOUND).
Failure handling
Retry budgets, backoff, the dead letter queue, redrive, and stats semantics have their own page: Reliability.
Limits and configuration
Worker pool sizes per priority lane, the scheduler poll interval, retention,
cleanup cadence, and stream tuning are the JOB_QUEUE_* variables in the
Job Queue CONFIG reference.
Batches cap at 1000 jobs; DLQ listing pages cap at 500 entries.
Reference
- Job Queue API - every endpoint, callable
- Related: Message Queue for pull-based consumer queues, Pub/Sub for ephemeral fan-out, Webhooks for event-driven webhook subscriptions, Health for the health surface