Agent skill
wasp-jobs
Background jobs with PgBoss for Wasp applications. Use when implementing async tasks, scheduled jobs, email queues, or background processing. Requires PostgreSQL database.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/wasp-jobs
SKILL.md
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
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?