Queue & async
Slow channels (SMTP, HTTP) shouldn’t block a request. Configure a queue and
.queue() publishes a rendered job to herald; a worker delivers it.
Wire it
Section titled “Wire it”import { type NotificationConfig, heraldQueue, inApp, mailChannel } from "@warlock.js/notifications";import { Notification } from "app/notifications/notification.model";
const config: NotificationConfig = { channels: { mail: mailChannel(), database: inApp.configure({ model: Notification }) }, queue: heraldQueue(), // backs `.queue()`};
export default config; // the notifications connector registers it at bootheraldQueue({ channel?, broker? }) defaults to the "notifications.dispatch"
channel. It lazy-imports @warlock.js/herald — install that package to use the
queue; without it the first .queue() throws a clear install message. Without
queue configured at all, .queue() rejects NoQueueDispatcherError.
Run the worker
Section titled “Run the worker”import { startNotificationsWorker } from "@warlock.js/notifications";
// after the notifications config is registered + the broker is connectedawait startNotificationsWorker();The worker subscribes to the channel, looks each job’s channel up by name, and
runs channel.send. Because defineNotification renders the payload and
resolves the route before enqueuing, the job is fully serializable —
the worker needs no recipient model.
await orderShipped.queue(user, { order });await orderShipped.queue(buyers, { order }, { delay: "10m", idempotencyKey: `ship:${order.id}` });idempotencyKey makes a redelivered job a no-op instead of a duplicate row.
Current limits
Section titled “Current limits”delayis carried on the job but not yet honored — delivery is immediate (delay-aware delivery is a follow-up).- A failed
channel.sendis logged and acked (no retry/dead-letter yet). - For queued sends, the rate-limit budget is consumed at enqueue time.
See also
Section titled “See also”- Define a notification —
.queue()semantics. - Observability —
sentfires when a job is enqueued.