NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Alerts for Laravel

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.

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:

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:

// 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']):

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

The reason is bounded like the stored message (1000 characters). For the full 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']:

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:

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

By donating, you agree to our donation terms.

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.