Definition
A notification is a file, and the file names it
OrderPaid.notification.ts is OrderPaid — the same rule job files already follow, so there is no registry to keep in step with the filesystem.
export default defineNotification({
via: (p) => p.total > 100 ? ['inApp', 'email'] : ['inApp'],
format: async (p) => ({ title: `Order ${p.id} paid` }),
})
Replaces
NovuCouriera hand-rolled fan-out
Fan-out
app.notify → in-app record, WebSocket event, email
One send reaches every transport the definition asked for, and the in-app record is a row like any other — queryable, gated, and readable back.
Replaces
three separate send calls
The type is persisted
type: holds the name still
The type is a column the browser reads to pick a renderer, so renaming the file orphans every existing row. State it and the file is free to move; a divergence is warned about.
Replaces
Nothing — there is no common equivalent to swap out.
Recipient
The unit of address is a Recipient, not a User
A notification can reach an address that was never an account — an invitee, a webhook, a shared inbox — without inventing a user row to hold it.
Replaces
a nullable user on every notification
Transports
inApp and email, plus drivers of your own
A transport is a delivery medium, kept separate from the broadcast set a WebSocket subscriber joins — two things one word usually covers.
Replaces
per-medium send helpers
Introspection
app.notifications answers what this app can send
Formatters may be async and take the payload rather than closing over it, so the catalog can be listed with nothing actually sent.
Replaces
reading the source to find out