Skip to content

Sending

use Anvil\Notifications\Notification;
service('notifications')->send($userId, Notification::make('Export ready')
->body('deals.xlsx, 120 rows')
->url('/admin/deals')
->type(Notification::SUCCESS));
service('notifications')->send([$userA, $userB], Notification::make('Maintenance tonight'));
Method Rule Default
Notification::make($title) title of 1 to 200 characters
body($text) up to 2000 characters; longer text is cut none
url($path) a path of the application (/admin/…), up to 500 characters no link
type($type) Notification::INFO, SUCCESS, WARNING or DANGER INFO

Invalid values throw an InvalidArgumentException: an empty or too long title, an unknown type, or a link that is not a path of the application. Title and body are plain text; they are escaped when shown.

Links must be paths of the application. Other hosts, //… and javascript: are rejected, so a notification can never send a user to a foreign site. Build them with the URL helpers of resources and panels:

$resource = service('panels')->get('admin')->resourceByClass(OrderResource::class);
Notification::make('Order approved')->url($resource->path('view', $orderUuid));

Use path() (not url()), because a link is stored as a path. The page checks the permissions of the user when it is opened; a notification never grants access.

send() takes one internal user id or a list; duplicates are sent once. Typical ways to find the users:

// The users of the current tenant with a permission (through their roles)
$approvers = service('roleRepository')->usersWith('orders.approve');
// The users of a role
$team = service('roleRepository')->usersOf($roleId);
// The user who is logged in
$me = (int) auth()->id();

usersWith() also finds users with orders.* and with a role of full access. Never take user ids from the request.

Notifications belong to the current tenant: users see them only while they work in that tenant. In panels the tenant is set by the request; in queue jobs, commands and anvil:tick set it yourself:

service('tenantContext')->runAs($tenantId, function () use ($userId): void {
service('notifications')->send($userId, Notification::make('Import finished'));
});

Without a tenant in a multi-tenant application the model fails closed and nothing is stored.

send() writes through an Anvil model. Inside an action handler or afterSave() it is part of the transaction: if the change rolls back, the notification is not sent either.

  • Title: what happened, in a few words (“Task overdue: Laptop for Ada”).
  • Body: the detail the user needs to decide whether to open it.
  • Link: the page where the user can act.
  • Type: WARNING/DANGER only for things that need attention.
  • Use the language of the recipient where it differs from the sender’s – texts are stored as sent.