Digging deeper
Notifications
A notification is a short message to one of your users, such as "your order has shipped", sent over one or more channels: as an email, as a row in your database for your app to show, or both.
Introduction#
Each notification is a class with a via() method, which names its channels, and a toXxx() method for each of
them. Oriel has two channels: mail, which sends an email, and database, which stores the notification in a
notifications table. You can add channels of your own with Notification.extend().
Generating notifications#
make:notification writes a class to app/:
node oriel make:notification InvoicePaidThe class it writes is queued (it has readonly shouldQueue = true), so run node oriel queue:work while you work
on it, or remove the field to send it right away.
Sending notifications#
Using the Notifiable mixin#
A model sends and stores notifications through the Notifiable mixin, which the User model of a new app already
has:
import { AuthenticatableModel, Authorizable, HasApiTokens, Model, Notifiable } from '@orieljs/framework'export class User extends HasApiTokens(Notifiable(Authorizable(AuthenticatableModel(Model)))) {}It gives the model notify(), notifyNow() and routeNotificationFor(), and the notifications(),
readNotifications() and unreadNotifications() relations of the database channel:
const user = await User.findOrFail(1)await user.notify(new InvoicePaid('$20.00'))Using the Notification facade#
To notify several users at once, pass them (an array or any iterable) to Notification.send():
import { Notification } from '@orieljs/framework'const users = await User.all()await Notification.send(users, new InvoicePaid('$20.00'))Notification.sendNow() sends right away, even a queued notification, and takes the channels to use as a third
argument.
Specifying delivery channels#
via() gets the notifiable, so a notification can choose its channels for each user:
class InvoicePaid extends Notification { via(notifiable: object): string[] { return notifiable instanceof User ? ['mail', 'database'] : ['mail'] }}Queueing notifications#
A notification with readonly shouldQueue = true is queued: notify() and Notification.send() push one job per
notifiable and per channel, and a queue worker sends them. The fields
connection, queue and delay choose where and when, and any of them also queues the notification, as a uniqueId()
or retryUntil() method does. notifyNow() and Notification.sendNow() send a queued notification right away.
Unique notifications#
A queued notification with a uniqueId() method isn't queued again while its job is waiting or running, as a
unique job. The lock is taken per notifiable and per channel, so the same notification to two
users, or by mail and by database, sends each of them.
On-demand notifications#
To notify someone who isn't a user of your app, give the route yourself:
await Notification.route('mail', 'taylor@example.com').notify(new InvoicePaid('$20.00'))The database channel is skipped for an on-demand notification, as it needs a model to store the row against.
Mail notifications#
Formatting mail messages#
toMail() returns a MailMessage, which Oriel renders in its own styled template, with a plain-text part beside the
HTML:
import { MailMessage, Notification } from '@orieljs/framework'export class InvoicePaid extends Notification { constructor(private readonly amount: string) { super() } via(): string[] { return ['mail'] } toMail(): MailMessage { return new MailMessage() .subject('Invoice paid') .greeting('Hello!') .line(`We received your payment of ${this.amount}.`) .action('View invoice', 'https://example.com/invoices/1') .line('Thank you for using our application!') }}greeting()is "Hello!" when you don't set one ("Whoops!" for an error message), andsalutation()is "Regards," and your app's name.line(),lines(),lineIf()andlinesIf()add paragraphs;action()adds a button.from(),to(),cc(),bcc()andreplyTo()set the addresses,attach(path, { as, mime })attaches a file andattachData(data, name, { mime })attaches content you have in memory.
Each line is plain text: Oriel escapes it, and keeps it as you wrote it.
Error messages#
error() gives the message the error level, which colours its button red; success() colours it green, and
level('info' | 'success' | 'error') sets it directly.
Customizing the subject#
When you don't call subject(), the subject is the class name in title case: InvoicePaid is sent as "Invoice Paid".
A notification written as an object literal has the name Object, so its subject is "Object": give it a subject().
Customizing the recipient#
The mail channel sends to the notifiable's email attribute. To send elsewhere, add a routeNotificationForMail()
method to the model:
class Customer extends Notifiable(Model) { declare billing_email: string routeNotificationForMail(): string { return this.billing_email }}When there is no route, as for a notifiable with no email address, nothing is sent.
Database notifications#
Prerequisites#
The database channel stores each notification in the notifications table, which a new app's migrations create.
toDatabase() (or toArray()) returns the data to store, and the notifiable must be a model:
class InvoicePaid extends Notification { constructor(readonly amount: string) { super() } via(): string[] { return ['database'] } toDatabase(): Record<string, unknown> { return { amount: this.amount } }}The row's type is the notification's class name, and its data is what toDatabase() returned.
Accessing the notifications#
notifications() is a relation, newest first; unreadNotifications() and readNotifications() filter it:
const user = await User.findOrFail(1)for (const notification of await user.unreadNotifications().get()) { console.log(notification.data)}Marking notifications as read#
const user = await User.findOrFail(1)const notification = await user.notifications().firstOrFail()await notification.markAsRead()await notification.markAsUnread()read() and unread() tell which it is, and a collection of notifications has its own markAsRead().
Custom channels#
Notification.extend(name, factory) adds a channel: an object with send(notifiable, notification). List its name in
via().