Use when creating background job classes in app/Jobs/ - job structure, the handle method, job configuration (queue, tries, backoff, timeout, rate), dispatching patterns (dispatch, dispatchIf, dispatchAfter, dispatchNow), or the Every schedule constants. For the queue system internals (workers, batching, events, drivers, testing), see stacks-queue. Covers app/Jobs, the native Job class and typed by-name dispatch.
Install
npx skillscat add stacksjs/stacks/stacks-jobs Install via the SkillsCat registry.
Stacks Jobs
Background job classes defined in app/Jobs/.
Key Paths
- Application jobs:
app/Jobs/ - Queue config:
config/queue.ts
Creating a Job
// app/Jobs/SendWelcomeEmail.ts
import { Job } from '@stacksjs/queue'
export default new Job({
name: 'SendWelcomeEmail',
description: 'Send welcome email to new user',
queue: 'emails', // queue name (default: 'default')
tries: 3, // max attempts
backoff: 10, // seconds between retries
timeout: 30, // max execution time (seconds)
enabled: true, // can be disabled
async handle(payload: { email: string; name: string }) {
console.log(`Sending to ${payload.email}`)
// Do work here
return { sent: true }
}
})Job Configuration Options
{
name: string // job identifier
description?: string // human-readable description
queue?: string // queue name (default: 'default')
tries?: number // max retry attempts
backoff?: number | number[] // seconds between retries
backoffConfig?: { // advanced backoff
strategy: 'fixed' | 'exponential' | 'linear'
initialDelay: number
factor: number
maxDelay: number
jitter?: { enabled: boolean, factor: number }
}
timeout?: number // max seconds per attempt
rate?: string // schedule, e.g. Every.Hour; use an explicit schedule for custom policy
enabled?: boolean // enable/disable job
handle: (payload?) => any // job logic
}Dispatching Jobs
// Simple dispatch
await SendWelcomeEmail.dispatch({ email: 'user@example.com', name: 'John' })
// Conditional dispatch
await SendWelcomeEmail.dispatchIf(isNewUser, { email, name })
await SendWelcomeEmail.dispatchUnless(isExistingUser, { email, name })
// Delayed dispatch (60 second delay)
await SendWelcomeEmail.dispatchAfter(60, { email, name })
// Immediate execution (bypasses queue)
await SendWelcomeEmail.dispatchNow({ email, name })Fluent Job Builder
import { job } from '@stacksjs/queue'
// Checked against `app/Jobs/` AND the framework defaults - the same three
// directories `resolveJobFile` searches, so the jobs Stacks ships are
// dispatchable and schedulable by name too.
await job('SendWelcomeEmail', { email, name })
.onQueue('emails')
.delay(60)
.tries(5)
.timeout(30)
.backoff([10, 30, 60])
.dispatch()Scheduled Jobs
Use the rate property for automatic scheduling:
import { Every } from '@stacksjs/types'
export default new Job({
name: 'CleanupExpiredTokens',
rate: Every.Hour, // runs every hour
// rate: Every.Day, // runs daily
// rate: '*/5 * * * *', // custom cron: every 5 minutes
handle() {
// cleanup logic
}
})Register in app/Scheduler.ts:
import { schedule } from '@stacksjs/scheduler'
export default function() {
schedule.job('CleanupExpiredTokens').hourly().setTimeZone('America/New_York')
}CLI Commands
buddy make:job [name] # scaffold a new job
buddy queue # queue managementGotchas
- Prefer
default new Job({...})for dispatch methods. Discovery also supports default objects with handle and class exports with a handle method - The
handle()method receives the payload passed todispatch() dispatchNow()runs immediately in the current process - no queue involved- Default queue driver is
sync- jobs run immediately unless changed todatabaseorredis - Jobs with
rateare auto-discovered by the scheduler - Backoff array
[10, 30, 60]means: retry after 10s, then 30s, then 60s - Jobs should be idempotent - safe to retry on failure
- For queue workers, batching, events, and testing, see the
stacks-queueskill
Durable dispatch and context
The fluent job(name, payload) builder checks job names and payloads against
the generated registry. .withContext(context) passes a second argument to
the handler; JSON serialization applies to both payload and context on a durable
driver. .withIdempotencyKey(key) claims a persistent dispatch key.
Inside db.transaction() dispatch defers until commit by default;.afterCommit() states that intent, and .withoutCommit() explicitly opts
out. Outside a transaction, afterCommit warns and dispatches immediately.
See stacks-queue for the complete envelope and batch lifecycle.
Scheduling a named job through schedule.job() runs it in the scheduler by
default. Chain .onQueue(name) to dispatch it through the configured queue;
durable drivers require a worker. The explicit Scheduler entry wins over a
job's rate in runScheduler, so keep the policy in that explicit registration.
Source: core/queue/src/job.ts,action.ts, discovery.ts and core/scheduler/src/schedule.ts.
Tests: core/queue/tests/job-context.test.ts,job-declared-retries.test.ts and job-payload.test-d.ts.