Skip to content
Warlock.js v4.2.11

Notifications

Built on @warlock.js/core (mail) and @warlock.js/cascade (in-app store). Not standalone — it orchestrates over the transports the framework already gives you.

@warlock.js/notifications answers one question: something happened — how do I tell the right people, through the right channels, without rewriting the plumbing every time? You describe a notification once; it renders per channel and fans out to every recipient.

Define a notification, then fire it anywhere — sync or queued:

import { defineNotification } from "@warlock.js/notifications";
export const orderShipped = defineNotification<{ order: Order }>({
type: "order.shipped",
via: ["database", "mail"],
database: ({ order }) => ({ title: `Order #${order.number} shipped`, payload: { id: order.id } }),
mail: ({ order }, to) => ({
subject: `Order #${order.number} shipped`,
html: `<p>Hi ${to.get("name")}, it's on the way.</p>`,
}),
});
// anywhere — one recipient, a list, or queued
await orderShipped.send(user, { order });
await orderShipped.send([buyer, salesRep], { order });
await orderShipped.queue(user, { order }, { delay: "10m" });

Need a one-off? Skip the definition and use the per-channel proxy:

import { notify } from "@warlock.js/notifications";
await notify.mail(user, { subject: "Welcome", html: "<p>Thanks for joining</p>" });
await notify.database(user, { type: "welcome", title: "Welcome!" });
  • Define once, fire anywheredefineNotification carries the channels + a renderer per channel; .send / .queue / .only dispatch it.
  • A typed channel registrynotify.mail, notify.database, … are payload-typed; custom channels extend the registry with a 3-line declare module.
  • An in-app store with a clean read sideinApp.listUnread / countUnread / markAsRead / dismiss, recipient-scoped by construction (no IDOR).
  • Pre-send gates — plug in a PreferenceProvider (mute lists, quiet hours) and a RateLimiter (per-user budgets). Both optional.
  • IdempotencyidempotencyKey makes a retried send a no-op instead of a duplicate row.
  • Observability — subscribe to sending / sent / failed / skipped (shared dispatchId + durationMs) for metrics, logs, tracing, and drop accounting.

WhatsApp, Telegram, Slack, and push channels arrive with @warlock.js/bridges. Until then, any HTTP channel is a 30-line defineChannel — see custom channels.

Source: @warlock.js/notifications/.