docs/jobs-queue/quick-start-example.mdx
Let's walk through a practical example of setting up a simple job queue. We'll create a task that sends a welcome email when a user signs up.
You might wonder: "Why not just send the email directly in the afterChange hook?"
Now let's build this example step by step.
First, create a task in your payload.config.ts:
import { buildConfig } from 'payload'
export default buildConfig({
// ... other config
jobs: {
tasks: [
{
slug: 'sendWelcomeEmail',
retries: 3,
inputSchema: [
{
name: 'userEmail',
type: 'email',
required: true,
},
{
name: 'userName',
type: 'text',
required: true,
},
],
handler: async ({ input, req }) => {
// Send email using your email service
await req.payload.sendEmail({
to: input.userEmail,
subject: 'Welcome!',
text: `Hi ${input.userName}, welcome to our platform!`,
})
return {
output: {
emailSent: true,
},
}
},
},
],
},
})
This defines a reusable task with a unique slug, an inputSchema that validates and types the input data, and a handler function containing the work to be performed. The retries option ensures the task will automatically retry up to 3 times if it fails. Learn more about Tasks.
{
slug: 'users',
hooks: {
afterChange: [
async ({ req, doc, operation }) => {
// Only send welcome email for new users
if (operation === 'create') {
await req.payload.jobs.queue({
task: 'sendWelcomeEmail',
input: {
userEmail: doc.email,
userName: doc.name,
},
})
}
},
],
},
// ... fields
}
This uses payload.jobs.queue() to create a job instance from the task definition. The job is added to the queue immediately but runs asynchronously, so the API response returns right away without waiting for the email to send. Jobs are stored in the database as documents in the payload-jobs collection.
export default buildConfig({
// ... other config
jobs: {
tasks: [
/* ... */
],
autoRun: [
{
cron: '*/5 * * * *', // Run every 5 minutes
},
],
},
})
The autoRun configuration automatically processes queued jobs on a schedule using cron syntax. In this example, Payload checks for pending jobs every 5 minutes and executes them. Alternatively, you can manually trigger job processing with payload.jobs.run() or use the API endpoint for serverless platforms.
That's it! Now when users sign up, a job is queued and will be processed within 5 minutes without blocking the API response.
The previous example showed manual job queuing (jobs triggered by user actions). Now let's look at a job that runs automatically on a schedule without any user action.
We'll create a task that generates a daily analytics report every morning at 8 AM.
import { buildConfig } from 'payload'
export default buildConfig({
// ... other config
jobs: {
tasks: [
{
slug: 'generateDailyReport',
// This automatically queues a job every day at 8 AM
schedule: [
{
cron: '0 8 * * *', // 8:00 AM daily
queue: 'reports', // Put it in the 'reports' queue
},
],
inputSchema: [],
outputSchema: [
{
name: 'reportId',
type: 'text',
},
],
handler: async ({ req }) => {
// Generate the report
const yesterday = new Date()
yesterday.setDate(yesterday.getDate() - 1)
const analytics = await req.payload.find({
collection: 'analytics',
where: {
createdAt: {
greater_than_equal: yesterday.toISOString(),
},
},
})
// Save the report
const report = await req.payload.create({
collection: 'reports',
data: {
date: new Date().toISOString(),
totalEvents: analytics.totalDocs,
summary: `Generated report for ${yesterday.toDateString()}`,
},
})
return {
output: {
reportId: report.id,
},
}
},
},
],
},
})
The schedule property defines when this job should run (every day at 8 AM).
To actually queue and execute scheduled jobs, you need to configure the autoRun property. This handles both queuing jobs based on their schedule and running them:
export default buildConfig({
// ... other config
jobs: {
tasks: [
/* task from step 1 */
],
// This processes jobs from the 'reports' queue
autoRun: [
{
cron: '* * * * *', // Check every minute
queue: 'reports', // Process 'reports' queue
limit: 10,
},
],
},
})
Here's the complete flow:
schedule configuration automatically queues a new job in the 'reports' queueautoRun cron checks the 'reports' queue and finds the jobHere's the full config with both the task and runner:
import { buildConfig } from 'payload'
export default buildConfig({
// ... other config
jobs: {
tasks: [
{
slug: 'generateDailyReport',
schedule: [
{
cron: '0 8 * * *',
queue: 'reports',
},
],
handler: async ({ req }) => {
// Report generation logic
const report = await generateReport()
return { output: { reportId: report.id } }
},
},
],
autoRun: [
{
cron: '* * * * *',
queue: 'reports',
limit: 10,
},
],
},
})
| Approach | When to Use | Example |
|---|---|---|
| Manual Queuing | Jobs triggered by user actions or data changes | Welcome emails, payment processing, notifications |
| Scheduled Jobs | Jobs that run automatically at regular intervals | Daily reports, weekly cleanups, nightly syncs |
| Scheduled with Future | One-time job in the future | Publish post at 3pm tomorrow, trial expiry reminders |
For scheduled jobs with waitUntil:
// Queue a one-time job for the future
await payload.jobs.queue({
task: 'publishPost',
input: { postId: '123' },
waitUntil: new Date('2024-12-25T15:00:00Z'), // Runs once at this time
})
This is different from the schedule property, which repeats automatically.
See Job Schedules for more details on scheduling options and advanced patterns.