Notifications
Register NotificationServiceProvider (which registers MailServiceProvider as a dependency) and add config/notifications.ts:
export default {
table: 'notifications',
connection: 'sqlite',
queue: 'default',
queueConnection: 'database',
} as const;Run the notifications table migration:
tyravel migrateSending notifications
Use the Notifications facade:
import { Notifications } from '@tyravel/core';
// Queued when the notification implements ShouldQueue
await Notifications.send(user, new WelcomeNotification(user));
// Deliver immediately, bypassing the queue
await Notifications.sendNow(user, new WelcomeNotification(user));Notification classes
Extend Notification and implement via() to declare delivery channels:
import { Notification, type Notifiable } from '@tyravel/notifications';
import type { MailMessage } from '@tyravel/mail';
export class WelcomeNotification extends Notification {
constructor(private readonly user: { email: string; name: string }) {
super();
}
via(notifiable: Notifiable): string[] {
return ['mail', 'database'];
}
toMail(notifiable: Notifiable): MailMessage {
return {
subject: 'Welcome!',
htmlView: 'notifications::mail.html',
textView: 'notifications::mail.text',
viewData: { name: this.user.name },
};
}
toDatabase(notifiable: Notifiable): Record<string, unknown> {
return {
message: `Welcome, ${this.user.name}!`,
user_id: notifiable.getKey(),
};
}
}Channels
| Channel | Description |
|---|---|
mail | Sends via the mail system. Implement toMail() |
database | Stores in the notifications table. Implement toDatabase() |
slack | Posts to a Slack incoming webhook. Implement toSlack() |
webhook | POSTs JSON to a custom URL. Implement toWebhook() |
broadcast | Pushes over Echo/WebSocket. Implement toBroadcast(); requires BroadcastServiceProvider |
sms | Sends via SmsChannel transport stub. Implement toSms() |
Slack
override toSlack() {
return {
webhookUrl: process.env.SLACK_WEBHOOK_URL!,
text: 'Order shipped!',
};
}Webhook
override toWebhook() {
return {
url: 'https://example.com/hooks/orders',
body: { event: 'order.shipped', id: 42 },
};
}Broadcast
override toBroadcast(notifiable: Notifiable) {
return {
event: 'NotificationSent',
channels: [`App.Models.User.${notifiable.getKey()}`],
data: { type: 'OrderShipped' },
};
}SMS (Twilio-compatible stub)
Register a transport during boot (the default logs to stdout):
import { setSmsTransport } from '@tyravel/notifications';
setSmsTransport(async (message) => {
// Twilio example:
// await twilioClient.messages.create({ to: message.to, from: message.from, body: message.body });
console.log(`[sms] ${message.to}: ${message.body}`);
});Notification + notifiable:
export class LoginCodeNotification extends Notification {
override via(): Array<'sms'> {
return ['sms'];
}
override toSms(notifiable: Notifiable) {
return {
to: notifiable.routeNotificationForSms!(),
body: `Your code is ${this.code}`,
};
}
}See examples/hello-world/src/notifications/login-code-notification.ts for a working scaffold.
Batching and digests
Send multiple notifications in one pass:
import { NotificationBatch } from '@tyravel/notifications';
const batch = new NotificationBatch();
batch.add(user, new CommentNotification(post));
batch.add(user, new LikeNotification(post));
await batch.sendNow(manager); // two separate deliveries
await batch.sendDigestNow(manager); // one combined digest per notifiableNotificationDigest groups by notifiable and wraps multiple items in a DigestNotification that merges toMail(), toSlack(), toSms(), and toDatabase() payloads.
Database inbox helpers
For in-app notification bells:
import { DatabaseNotificationInbox } from '@tyravel/notifications';
const inbox = new DatabaseNotificationInbox({ connection });
const page = await inbox.paginate(user, 1, 15);
const unread = await inbox.unread(user);
const count = await inbox.unreadCount(user);
await inbox.markAsRead(notificationId);
await inbox.markAsUnread(notificationId);
await inbox.markAllAsRead(user);Queued notifications
Extend ShouldQueue or override shouldQueue() to deliver notifications via the queue:
import { Notification, ShouldQueue } from '@tyravel/notifications';
export class WelcomeNotification extends Notification implements ShouldQueue {
override shouldQueue(): boolean {
return true;
}
connection = 'database';
queue = 'notifications';
delaySeconds = 10;
}Failed notifications
Failed queued notifications land in failed_jobs. Inspect and retry them:
tyravel notification:failed
tyravel notification:retry <id>Notifiable type
The Notifiable interface requires at minimum:
interface Notifiable {
getKey(): string | number;
routeNotificationForMail?(): string | { address: string; name?: string };
routeNotificationForSms?(): string;
}Testing fakes
import { mailFake, notificationFake } from '@tyravel/testing';
const mail = mailFake(app);
const notifications = notificationFake(app);
await Notifications.send(user, new WelcomeNotification(user));
notifications.assertSent((entry) => entry.notification.id() === 'WelcomeNotification');
mail.assertNothingSent();Service provider registration
import { NotificationServiceProvider } from '@tyravel/core';
app.register(NotificationServiceProvider);
await app.boot();Then wire the facade:
import { setNotificationApplication } from '@tyravel/core';
setNotificationApplication(app);