Skip to the content
OrielDocs
0.1.0-alpha.10GitHub

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/Notifications:

Terminal
node oriel make:notification InvoicePaid

The 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:

app/Models/User.ts
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:

app/Notifications/InvoicePaid.ts
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), and salutation() is "Regards," and your app's name.
  • line(), lines(), lineIf() and linesIf() add paragraphs; action() adds a button.
  • from(), to(), cc(), bcc() and replyTo() set the addresses, attach(path, { as, mime }) attaches a file and attachData(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().

Privacy policy ·

esc