# Stacks Jobs

> 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.

- Skill: `stacksjs/stacks-jobs` (Agent Skill)
- Install (CLI): `npx skillmds@latest add stacksjs/stacks-jobs`
- Raw SKILL.md: https://api.skillmd.com/api/skills/stacksjs/stacks-jobs/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: stacksjs (https://skillmd.com/u/stacksjs)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/stacksjs/stacks-jobs

---


# Stacks Jobs

Background job classes defined in `app/Jobs/`.

## Key Paths
- Application jobs: `app/Jobs/`
- Queue config: `config/queue.ts`

## Creating a Job

```typescript
// 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

```typescript
{
  name: string               // job identifier
  description?: string       // human-readable description
  queue?: string             // queue name (default: 'default')
  tries?: number             // max retry attempts
  backoff?: 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              // cron schedule (e.g., Every.Hour)
  enabled?: boolean          // enable/disable job
  handle: (payload?) => any  // job logic
}
```

## Dispatching Jobs

```typescript
// 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

```typescript
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:

```typescript
import { Every } from '@stacksjs/enums'

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`:
```typescript
import { schedule } from '@stacksjs/scheduler'

export default function() {
  schedule.job('CleanupExpiredTokens').hourly().setTimeZone('America/New_York')
}
```

## CLI Commands
```bash
buddy make:job [name]       # scaffold a new job
buddy queue                  # queue management
```

## Gotchas
- Jobs must export `default new Job({...})` — not a plain object
- The `handle()` method receives the payload passed to `dispatch()`
- `dispatchNow()` runs immediately in the current process — no queue involved
- Default queue driver is `sync` — jobs run immediately unless changed to `database` or `redis`
- Jobs with `rate` are 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-queue` skill

