Skip to content
BoringStack
GitHub

Background work

3 min read

Background jobs

Products need work off the HTTP critical path: email, notifications, retries. BoringStack puts that work in the API with BullMQ on Valkey, shared queue patterns, and dispatch helpers. You ship events and mail without standing up a separate job platform first.

BullMQ

queue engine

Valkey

queue backend

Inline

fallback when off

Queues

BullMQ + QueueManager

Named queues and workers; centralized lifecycle, retries, and shutdown. See Queues.

Email

Pluggable providers

Precompiled templates, queue-aware sendTemplate(). Cloudflare, Resend, SendGrid, SMTP, or noop.

Notifications

Event registry

Typed events, dispatch job, channels, dedup, preferences, and SSE pub/sub via Valkey.

flowchart LR
  app[Application code] --> reg[eventRegistry]
  reg --> dispatch[notification-dispatch queue]
  dispatch --> ch{channels}
  ch --> inApp[in-app]
  ch --> email[email]
  ch --> sse[SSE via Valkey pub/sub]

Notification flow: application code emits typed events through the eventRegistry; the notification-dispatch queue resolves channels; dispatched events fan out to three sinks: an in-app row in Postgres, an email template through the email provider, and a live SSE push via Valkey pub/sub.

  1. Define an event. defineNotificationEvent registers type, payload shape, and which channels apply.
  2. Emit. Application code triggers the event; preferences and dedup run before enqueue.
  3. Dispatch job. The notification-dispatch worker resolves channels and delivers (in-app row, email template, live SSE to connected clients).
  4. Maintenance. A separate maintenance queue handles retention and housekeeping.

Extension lives under src/lib/notifications/ (events/, dispatch/, channels/, preferences/, pubsub/, dedup.service.ts). Add an event with the scaffold script, register channels, enqueue through the dispatcher. Mail follows the same pattern via email-delivery.

Email uses the same queue-or-inline pattern: precompiled Handlebars JSON, then the configured provider (Cloudflare, Resend, SendGrid, SMTP, noop).

Audit log appends security-relevant events to a dedicated Postgres schema (audit namespace). Fire-and-forget from the API; query and retention are yours to extend.

In-process is the default. Move work to a dedicated worker or service when:

  • Dispatch or send rate needs isolation from API request latency.
  • Another group owns delivery infrastructure.
  • Background work needs its own scaling, deploy, or failure domain.
  • Data or processing must run in a segregated environment.

Keep job shapes and contracts stable (payload types, idempotency rules) so API producers change little. BullMQ job names, channel interfaces, and OpenAPI-facing behavior stay the same; only where the worker runs changes.