| name | wasp-jobs |
| description | Background jobs with PgBoss for Wasp applications. Use when implementing async tasks, scheduled jobs, email queues, or background processing. Requires PostgreSQL database. |
| triggers | background job, scheduled task, cron, job, email queue, async task, PgBoss |
| version | 1 |
| last_updated | Sat Oct 18 2025 00:00:00 GMT+0000 (Coordinated Universal Time) |
| allowed_tools | Edit, Bash, Read |
Wasp Background Jobs Skill
Quick Reference
When to use this skill:
- Sending emails asynchronously
- Processing data in background
- Scheduled/recurring tasks
- Long-running operations
- Queue-based processing
Critical Requirements
MUST use PostgreSQL - PgBoss requires PostgreSQL (SQLite not supported)
// schema.prisma
datasource db {
provider = "postgresql" // ✅ Required for jobs
url = env("DATABASE_URL")
}
Complete Job Setup Workflow
1. Define Job in main.wasp
job emailSender {
executor: PgBoss, // Requires PostgreSQL
perform: {
fn: import { sendEmail } from "@src/jobs/emailSender.js"
},
entities: [User, EmailQueue] // Entities needed in job
}
Job options:
job myJob {
executor: PgBoss,
perform: {
fn: import { myJobFunction } from "@src/jobs/myJob.js"
},
entities: [User, Task],
schedule: {
cron: "0 0 * * *", // Daily at midnight
args: {=json { "foo": "bar" } json=} // Optional default args
}
}
2. Implement Job Function
File: src/jobs/emailSender.js
import type { EmailSender } from "wasp/server/jobs";
type EmailArgs = {
to: string;
subject: string;
body: string;
};
export const sendEmail: EmailSender<EmailArgs> = async (args, context) => {
// Access entities via context
const user = await context.entities.User.findUnique({
where: { email: args.to },
});
if (!user) {
console.error("User not found:", args.to);
return { success: false, error: "User not found" };
}
try {
// Send email logic here
console.log(`Sending email to ${args.to}`);
console.log(`Subject: ${args.subject}`);
console.log(`Body: ${args.body}`);
// Actual email sending would go here
// await emailService.send(args)
return { success: true };
} catch (error) {
console.error("Email send failed:", error);
throw error; // PgBoss will retry
}
};
3. Trigger Job Programmatically
From an action:
import { emailSender } from "wasp/server/jobs";
import type { SendWelcomeEmail } from "wasp/server/operations";
export const sendWelcomeEmail: SendWelcomeEmail = async (args, context) => {
if (!context.user) throw new HttpError(401);
// Trigger job
await emailSender.submit({
to: context.user.email,
subject: "Welcome!",
body: "Thanks for signing up!",
});
return { message: "Email queued" };
};
With delay:
// Send email in 1 hour
await emailSender.submit(
{ to: "user@example.com", subject: "Reminder", body: "Don't forget!" },
{ startAfter: new Date(Date.now() + 3600000) }, // 1 hour delay
);
With retry configuration:
await emailSender.submit(emailArgs, {
retryLimit: 5, // Retry up to 5 times
retryDelay: 60, // 60 seconds between retries
retryBackoff: true, // Exponential backoff
});
Scheduling Patterns
Cron-Based Scheduling
job dailyReport {
executor: PgBoss,
perform: {
fn: import { generateDailyReport } from "@src/jobs/reports.js"
},
schedule: {
cron: "0 9 * * 1-5", // 9 AM on weekdays
args: {=json { "reportType": "daily" } json=}
}
}
Common cron patterns:
"0 * * * *"- Every hour"0 0 * * *"- Daily at midnight"0 9 * * 1-5"- 9 AM on weekdays"0 0 1 * *"- First day of month"*/15 * * * *"- Every 15 minutes
Programmatic Scheduling
import { myJob } from "wasp/server/jobs";
// Schedule one-time job
await myJob.submit(args, {
startAfter: new Date("2025-12-01T10:00:00Z"),
});
// Schedule recurring job (every 6 hours)
await myJob.submit(args, {
retryLimit: 3,
retryDelay: 3600, // 1 hour retry delay
expireInHours: 24, // Job expires after 24 hours
});
Job Patterns
Email Queue Pattern
// Queue email for sending
export const queueEmail = async (args, context) => {
if (!context.user) throw new HttpError(401);
// Create email queue record
const emailRecord = await context.entities.EmailQueue.create({
data: {
to: args.to,
subject: args.subject,
body: args.body,
status: "PENDING",
},
});
// Trigger job
await emailSender.submit({
emailId: emailRecord.id,
});
return emailRecord;
};
// Job processes queued email
export const emailSender = async (args, context) => {
const email = await context.entities.EmailQueue.findUnique({
where: { id: args.emailId },
});
if (!email) return { success: false };
try {
// Send email
await sendEmailViaService(email);
// Update status
await context.entities.EmailQueue.update({
where: { id: args.emailId },
data: { status: "SENT", sentAt: new Date() },
});
return { success: true };
} catch (error) {
// Update status to failed
await context.entities.EmailQueue.update({
where: { id: args.emailId },
data: { status: "FAILED", error: error.message },
});
throw error;
}
};
Batch Processing Pattern
job processBatchUsers {
executor: PgBoss,
perform: {
fn: import { processBatch } from "@src/jobs/batchProcessor.js"
},
entities: [User],
schedule: {
cron: "0 2 * * *" // 2 AM daily
}
}
export const processBatch = async (args, context) => {
const batchSize = 100
let offset = 0
let processedCount = 0
while (true) {
const users = await context.entities.User.findMany({
skip: offset,
take: batchSize,
where: { needsProcessing: true }
})
if (users.length === 0) break
for (const user of users) {
await processUser(user, context)
processedCount++
}
offset += batchSize
// Log progress
console.log(`Processed ${processedCount} users`)
}
return { processedCount }
}
Error Handling
Job-Level Error Handling
export const myJob = async (args, context) => {
try {
// Job logic
await doWork(args, context);
return { success: true };
} catch (error) {
console.error("Job failed:", error);
// Log to database
await context.entities.JobLog.create({
data: {
jobName: "myJob",
args: JSON.stringify(args),
error: error.message,
stack: error.stack,
},
});
// Rethrow to trigger PgBoss retry
throw error;
}
};
Dead Letter Queue
// Handle jobs that failed all retries
export const processDeadLetterQueue = async (args, context) => {
// Find failed jobs
const failedJobs = await context.entities.JobLog.findMany({
where: {
status: "FAILED",
retries: { gte: 5 },
},
});
// Alert admin or take remedial action
for (const job of failedJobs) {
await notifyAdmin({
subject: "Job permanently failed",
job: job.jobName,
args: job.args,
error: job.error,
});
}
};
PostgreSQL Setup
Local Development
# macOS
brew install postgresql
brew services start postgresql
createdb myapp_dev
# Linux
sudo apt-get install postgresql
sudo systemctl start postgresql
sudo -u postgres createdb myapp_dev
.env.server
DATABASE_URL="postgresql://username:password@localhost:5432/myapp_dev"
schema.prisma
datasource db {
provider = "postgresql" // Required for PgBoss
url = env("DATABASE_URL")
}
Common Job Errors
Error: PgBoss requires PostgreSQL
Cause: Using SQLite as database provider
Fix:
// Change in schema.prisma
datasource db {
provider = "postgresql" // Not "sqlite"
url = env("DATABASE_URL")
}
Error: Job not defined
Cause: Forgot to restart wasp after adding job to main.wasp
Fix:
# Ctrl+C to stop, then safe-start (multi-worktree safe)
../scripts/safe-start.sh
Error: Cannot submit job
Cause: Job not imported correctly
Fix:
// ✅ CORRECT
import { myJob } from "wasp/server/jobs";
// ❌ WRONG
import { myJob } from "@wasp/jobs";
Best Practices
✅ DO:
- Use jobs for long-running operations
- Handle errors and log failures
- Set appropriate retry limits
- Use PostgreSQL (required)
- Test jobs locally before scheduling
- Monitor job execution
- Implement dead letter queue
❌ NEVER:
- Use jobs for real-time operations
- Forget error handling
- Use SQLite (PgBoss requires PostgreSQL)
- Set infinite retries
- Skip job logging
Quick Setup Checklist
- Switch to PostgreSQL (if using SQLite)
- Define job in main.wasp
- Implement job function
- Restart
../scripts/safe-start.sh(multi-worktree safe) - Test job submission
- Add error handling
- Set up monitoring/logging
Critical Rules
Database: MUST use PostgreSQL (PgBoss requirement) Restart: ALWAYS restart wasp after adding jobs to main.wasp Error handling: ALWAYS handle errors in job functions Monitoring: LOG job execution and failures
References
- Wasp jobs docs: https://wasp.sh/docs/language/features#jobs
- PgBoss docs: https://github.com/timgit/pg-boss