---
title: "Notifications & throttling — Alerts for Laravel | Roundly"
description: "Who gets notified and when, the renotification throttle, the bundled notification, channel routing with notifyVia() and custom notifications."
url: https://roundly-consulting.com/open-source/docs/alerts-for-laravel/notifications
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Alerts for Laravel](https://roundly-consulting.com/open-source/docs/alerts-for-laravel.md)

# Notifications & throttling

Without an escalation policy, every failing run that passes the flap gate notifies the owner’s default group — each recipient that forEachNotifiableForAlerts() yields. With a policy, notifications go out only when a new level is reached (see Escalation policies). Nothing is sent while muted.

A recipient whose notification throws — a bad address, a mail server that is down — is reported to your exception handler and skipped. The recipients after it are still notified, so an escalated tier is never cut short.

## Throttle

throttle(maxAttempts, decayMinutes) caps notifications at maxAttempts per decayMinutes for each recipient of each scheduled row. The default is one per minute — so an every-minute check notifies every minute of an incident unless you raise decayMinutes:

```php
Health::for($team)->monitor(DiskUsageCheck::class)
    ->everyMinute()
    ->throttle(maxAttempts: 1, decayMinutes: 60)   // at most one notification per hour per recipient
    ->save();
```

The throttle uses Laravel’s RateLimiter and therefore your default cache store. Use a shared store such as Redis or the database when several servers run queue workers.

## The bundled notification

Built-in checks, and inline checks without notifyUsing(), notify with HealthCheckFailedNotification. It sends an error-styled mail with the subject “Health check failed: <name>” plus the failure reason — the failing result’s message — and its toArray() payload (check, name, message) feeds the database channel. Warnings use the same notification. Reword the texts after publishing the translations:

```php
// lang/vendor/alerts/en/notifications.php
return [
    'subject' => 'Health check failed: :name',
    'line' => 'The health check ":name" has failed.',
];
```

The default channels are mail and database. The database channel needs Laravel’s notifications table — create it, or route to mail only with notifyVia(\['mail'\]):

```bash
# The default channels are mail + database; the database channel needs this table
php artisan make:notifications-table
php artisan migrate
```

The reason is the stored message — bounded to 1000 characters, and for a thrown exception the line naming its class unless store\_exception\_details is on, so no exception text is mailed by default. For the stored result meta, read $alert->meta in a HealthCheckFailed listener, or send your own notification.

## Channel routing

notifyVia() sets the channels of the bundled notification; notifyVia(\[...\], level: n) overrides them for one escalation level. The effective list resolves in the order level override → global list → \['mail', 'database'\]:

```php
Health::for($team)->monitor(DatabaseCheck::class)
    ->notifyVia(['database'])                     // in-app only at first
    ->notifyVia(['mail', 'database'], level: 3)   // add mail once threshold 3 is reached
    ->escalate([1 => 'owner', 3 => 'team'])
    ->save();
```

The bundled notification implements toMail() and toArray() only. For Slack, SMS or any other channel, send your own notification.

## Custom notifications

A check’s notification() — or an inline check’s notifyUsing() — can return any Laravel notification, which keeps full control of its own via(). To still honour notifyVia(), pass $this->channels() along. During a run, $this->result() returns the failing result being notified about, so your notification can say why:

```php
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;

class DiskUsageNotification extends Notification
{
    public function __construct(
        private array $channels = ['mail'],
        private string $reason = '',
    ) {}

    public function via(object $notifiable): array
    {
        return $this->channels;
    }

    public function toMail(object $notifiable): MailMessage
    {
        return (new MailMessage)
            ->error()
            ->subject('Disk usage is critical')
            ->line('The disk usage check on the primary server has failed.')
            ->lineIf($this->reason !== '', $this->reason);
    }
}

// In DiskUsageCheck — pass along the channels resolved from notifyVia()
// and the message of the failing result being notified about:
public function notification(object $notifiable): Notification
{
    return new DiskUsageNotification($this->channels() ?? ['mail'], (string) $this->result()?->message);
}
```

## Recovery

No notification is sent when an alert recovers. The package dispatches HealthCheckRecovered — listen for it to post an all-clear (see Events).

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/alerts-for-laravel.md)
