Skip to main content
BunShip uses BullMQ backed by Redis for asynchronous task processing. Background jobs handle email delivery, webhook dispatch, and periodic cleanup tasks in a separate process from the API server.

Architecture

The API server enqueues jobs. A separate worker process consumes them. This separation means slow or failing background tasks never block API responses.

Starting the worker

Run the worker as a standalone process:
On startup, the worker:
  1. Verifies the Redis connection is healthy
  2. Starts all three worker instances
  3. Schedules recurring cleanup jobs
  4. Listens for SIGTERM and SIGINT for graceful shutdown

Job Queues

BunShip defines three queues, each with its own configuration: All queues share a common Redis connection:

Job retention

Completed and failed jobs are pruned automatically:

Built-in Workers

Email Worker

Sends transactional emails through Resend. Supports HTML content, plain text, templates, CC/BCC, and reply-to addresses.
The worker enforces rate limits matching Resend’s tier:
Email job data:

Webhook Worker

Delivers webhook events to external endpoints with HMAC-SHA256 signing and automatic retries.
Each delivery includes these headers: Requests time out after 30 seconds. Non-2xx responses trigger a retry with exponential backoff. After all retries are exhausted, the delivery is marked as permanently failed in the database.

Cleanup Worker

Runs maintenance tasks one at a time (concurrency: 1). Four built-in tasks are supported:

Creating Custom Workers

Add a new worker by following the existing pattern.
1

Define the job data interface and queue

2

Create the worker

3

Register the worker in the startup function

4

Enqueue jobs from your API routes

Job Scheduling

BunShip schedules recurring jobs using BullMQ’s cron repeat feature. These are configured in setupRecurringJobs:
The jobId field prevents duplicate recurring jobs when the worker restarts. BullMQ deduplicates by ID, so only one instance of each recurring job exists at a time.

Adding a custom recurring job

Graceful Shutdown

The worker process listens for termination signals and waits for in-progress jobs to finish before exiting:
worker.close() stops accepting new jobs and waits for the currently running job to complete. closeQueues() closes all queue connections and the Redis client.
In Docker or Kubernetes, set the terminationGracePeriodSeconds high enough for your longest-running job to complete. The default 30 seconds may not be sufficient for large cleanup tasks.

Monitoring and Debugging

Redis health check

The worker verifies the Redis connection on startup:

Worker event logging

Each worker emits events for completed, failed, and error states. These are logged to stdout/stderr by default:

BullMQ dashboard

For a visual queue dashboard, consider adding Bull Board:
Protect the dashboard route with admin-only middleware in production.

Environment Variables