obsidian/wiki/payloadcms/jobs-queue-quick-start.md
2026-05-15 16:14:29 +01:00

4.6 KiB

title aliases tags sources created updated
Jobs Queue — Quick Start Examples
jobs-queue-quick-start
payload-jobs-example
jobs-queue-welcome-email
payloadcms
jobs-queue
tasks
scheduling
cron
background-jobs
raw/jobs-queue__quick-start-example.md
2026-05-15 2026-05-15

Why Use a Job Queue?

Instead of running slow/risky work inline in hooks:

  • Non-blocking — API returns immediately; email/heavy work runs async
  • Resilience — automatic retries if external service is down
  • Scalability — job workers can run on separate servers
  • Monitoring — all jobs stored in DB with status + error logs

Example 1: Welcome Email on User Signup

Step 1 — Define the Task (payload.config.ts)

jobs: {
  tasks: [
    {
      slug: 'sendWelcomeEmail',
      retries: 3,
      inputSchema: [
        { name: 'userEmail', type: 'email', required: true },
        { name: 'userName',  type: 'text',  required: true },
      ],
      handler: async ({ input, req }) => {
        await req.payload.sendEmail({
          to: input.userEmail,
          subject: 'Welcome!',
          text: `Hi ${input.userName}, welcome to our platform!`,
        })
        return { output: { emailSent: true } }
      },
    },
  ],
}

Step 2 — Queue the Job (from a Collection hook)

// In users collection config
hooks: {
  afterChange: [
    async ({ req, doc, operation }) => {
      if (operation === 'create') {
        await req.payload.jobs.queue({
          task: 'sendWelcomeEmail',
          input: { userEmail: doc.email, userName: doc.name },
        })
      }
    },
  ],
}

Job is stored in payload-jobs collection immediately; runs async — no API delay.

Step 3 — Run Jobs via autoRun

jobs: {
  tasks: [ /* ... */ ],
  autoRun: [
    { cron: '*/5 * * * *' }, // check every 5 minutes
  ],
}

Example 2: Scheduled Daily Report (No User Trigger)

Task with schedule Property

{
  slug: 'generateDailyReport',
  schedule: [
    {
      cron: '0 8 * * *',  // 8:00 AM daily
      queue: 'reports',
    },
  ],
  handler: async ({ req }) => {
    const report = await req.payload.create({ collection: 'reports', data: { /* ... */ } })
    return { output: { reportId: report.id } }
  },
}

autoRun Must Match the Queue Name

autoRun: [
  {
    cron: '* * * * *',  // check every minute
    queue: 'reports',   // MUST match schedule.queue
    limit: 10,
  },
]

Critical gotcha: schedule.queue and autoRun.queue must be identical. Mismatch → jobs queued but never executed.

Execution Flow

  1. 8:00 AMschedule auto-queues a job into 'reports' queue
  2. Within 1 minautoRun cron finds the job
  3. Execution — report generated
  4. Next day — repeats automatically

One-Time Future Job (waitUntil)

await payload.jobs.queue({
  task: 'publishPost',
  input: { postId: '123' },
  waitUntil: new Date('2024-12-25T15:00:00Z'),
})

Different from schedule — runs once at specified time, not recurring.


Approach Comparison

Approach When to Use Example
Manual Queuing Triggered by user actions / data changes Welcome emails, payments, notifications
Scheduled (schedule) Automatic recurring at fixed intervals Daily reports, weekly cleanups, nightly syncs
waitUntil One-time job in the future Publish at 3pm, trial expiry reminder

Serverless Warning

autoRun does not work on Vercel/serverless. Use the wiki/payloadcms/jobs-queue-queues with a dedicated API endpoint instead.


Key Takeaways

  • Define tasks in jobs.tasks[] with slug, inputSchema, retries, and handler
  • Queue manually: req.payload.jobs.queue({ task: 'slug', input: {...} })
  • autoRun polls for pending jobs on a cron schedule
  • For scheduled recurring tasks: add schedule to the task + matching queue in autoRun
  • Queue names in schedule.queue and autoRun.queue must match exactly
  • waitUntil = one-time future job; schedule = recurring
  • autoRun does not work on serverless (Vercel) — use API endpoint trigger instead


Sources