← 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 EXPIRED audit 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:

  1. Add env var: VOUCHER_AUDIT_RETENTION_DAYS=730
  2. 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
  3. Run daily via cron or @nestjs/schedule

Summary

JobHow it runsSchedule
Email (all types)pg-boss (in-process)Immediate
Voucher expiredStandalone scriptManual or cron
Audit archival(to be added)Daily

Email jobs: Start with the backend. Voucher expired: Run manually or via cron.