← Documentation
Background jobs setup
How background jobs work in the backend.
pg-boss (email jobs)
Transactional emails are sent via pg-boss, a PostgreSQL-based job queue. No extra setup is needed beyond DATABASE_URL — email jobs run automatically with the NestJS server.
- email-high (priority): password reset
- email-default: order confirmation, order status, welcome, user-created
Jobs page: View queue stats (queued, active, total) and job list with filters. From the Jobs page you can retry failed jobs or cancel queued jobs. Requires jobs:read, jobs:retry, jobs:cancel permissions (included for Admin after running npm run db:seed).
Deployment (pg-boss schema)
You do not need to run any script on deployment. When the backend starts, pg-boss creates the pgboss schema and tables automatically. If your database already existed without the schema (e.g. before a fix that strips ?schema=public from the URL), run this once only:
cd backend
PGBOSS_DATABASE_URL="postgresql://USER:PASSWORD@HOST:PORT/DATABASE" npx pg-boss create
Use your real DATABASE_URL credentials and omit ?schema=public from the URL. After that, normal deploys are enough.
Current state
-
Email jobs — Configured. Run with the backend. No cron needed.
-
Voucher expired job — A script exists that writes
EXPIREDaudit logs for vouchers past their expiry date. Run it manually:cd backend && npm run job:voucher-expired -
Archival job — Not implemented yet. Plan: delete or archive voucher audit logs older than a configurable retention period.
Option 1: NestJS Schedule (in-process cron)
Use @nestjs/schedule to run jobs inside the NestJS process.
1. Install
cd backend && npm install @nestjs/schedule
2. Register module
In app.module.ts:
import { ScheduleModule } from '@nestjs/schedule';
@Module({
imports: [
ScheduleModule.forRoot(),
// ... other imports
],
})
export class AppModule {}
3. Create a cron service
Create src/modules/jobs/jobs.service.ts:
import { Injectable } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import { VoucherExpiredJob } from '../vouchers/voucher-expired.job';
@Injectable()
export class JobsService {
constructor(private readonly voucherExpiredJob: VoucherExpiredJob) {}
@Cron('0 2 * * *') // Daily at 2:00 AM
async handleVoucherExpired() {
const { processed } = await this.voucherExpiredJob.processExpiredVouchers();
console.log(`[Jobs] Voucher expired: processed ${processed}`);
}
}
4. Register JobsService
Add JobsService as a provider in a new JobsModule or in VouchersModule.
Option 2: System cron (standalone script)
Run the script via system cron. No code changes needed.
Crontab example
# Voucher expired job - daily at 2:00 AM
0 2 * * * cd /path/to/backend && npm run job:voucher-expired >> /var/log/voucher-expired.log 2>&1
With PM2
pm2 start "npm run job:voucher-expired" --cron "0 2 * * *" --no-autorestart --name voucher-expired
Option 3: External scheduler (GitHub Actions, AWS EventBridge, etc.)
Trigger the job via HTTP or by running the script in CI.
Example: GitHub Actions
name: Voucher Expired Job
on:
schedule:
- cron: '0 2 * * *' # Daily 2 AM UTC
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm run job:voucher-expired
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
Archival job (future)
When ready to add archival for voucher audit logs:
- Add env var:
VOUCHER_AUDIT_RETENTION_DAYS=730 - Create
scripts/job-voucher-audit-archival.ts:- Query rows where
created_at < now() - retention_days - Delete in batches (e.g. 1000 at a time) to avoid long locks
- Query rows where
- Run daily via cron or
@nestjs/schedule
Summary
| Job | How it runs | Schedule |
|---|---|---|
| Email (all types) | pg-boss (in-process) | Immediate |
| Voucher expired | Standalone script | Manual or cron |
| Audit archival | (to be added) | Daily |
Email jobs: Start with the backend. Voucher expired: Run manually or via cron.