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

Writing checks

Extend RoundlyConsulting\Alerts\Check and implement check() and notification():

use Illuminate\Notifications\Notification;
use RoundlyConsulting\Alerts\Check;
use RoundlyConsulting\Alerts\CheckResult;

class DiskUsageCheck extends Check
{
    public function check(): CheckResult
    {
        // $this->healthCheck holds the scheduled HealthCheck record and its meta.
        $serverId = $this->healthCheck?->meta['server_id'] ?? null;

        return CheckResult::ok(
            message: 'Disk usage within limits',
            meta: ['server_id' => $serverId],
        );

        // Return CheckResult::failed('Disk almost full', [...]) to trigger an alert.
    }

    public function notification(object $notifiable): Notification
    {
        return new DiskUsageNotification();
    }
}

$this->healthCheck is the scheduled HealthCheck row the check runs for, with its meta. It is null when the check runs without a row — for example alerts:check without --notifiable — so read it null-safely.

A fuller example

Override name(), key(), description() or tags() to change how the check presents itself. Tags declared here merge with each row’s own tags. Reuse the bundled notification and pass $this->channels() to honour notifyVia():

use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Http;
use RoundlyConsulting\Alerts\Check;
use RoundlyConsulting\Alerts\CheckResult;
use RoundlyConsulting\Alerts\Notifications\HealthCheckFailedNotification;

class PaymentGatewayCheck extends Check
{
    public function name(): string
    {
        return 'Payment gateway';
    }

    public function tags(): array
    {
        return ['critical', 'billing'];
    }

    public function check(): CheckResult
    {
        $url = $this->healthCheck?->meta['url'] ?? 'https://payments.example.com/status';

        // No try/catch needed — a thrown exception becomes a failed result.
        $response = Http::timeout(5)->get($url);

        return $response->successful()
            ? CheckResult::ok('Gateway reachable', ['url' => $url])
            : CheckResult::failed('Gateway returned '.$response->status(), ['url' => $url]);
    }

    public function notification(object $notifiable): Notification
    {
        // Reuse the bundled notification and honour notifyVia() routing.
        return new HealthCheckFailedNotification($this, channels: $this->channels());
    }
}

Because each row carries its own meta, one check class can watch several services for the same owner. Each row has its own counters, alerts and notification throttle:

// One check class, two monitors — each row carries its own target in meta.
Health::for($team)->monitor(PaymentGatewayCheck::class)
    ->everyMinute()
    ->meta(['url' => 'https://payments.example.com/status'])
    ->save();

Health::for($team)->monitor(PaymentGatewayCheck::class)
    ->everyMinute()
    ->meta(['url' => 'https://payouts.example.com/status'])
    ->save();

Checks are instantiated without constructor arguments when you register or schedule them by class name. Keep configuration in fluent setters on a registered instance, or in the row’s meta.

Results and severity

A CheckResult carries a Status — ok, warning, failed or skipped — plus a message and a meta array:

use RoundlyConsulting\Alerts\CheckResult;

CheckResult::ok();
CheckResult::warning('Disk filling up');
CheckResult::failed('Disk full');
CheckResult::skipped('Maintenance window');
StatusSeverityEffect of a run
ok0Resets the failure counter and counts towards recovery of an open alert.
skipped1Records the run and nothing else — no counters, no alert, no notification.
warning2Counts as a failure: opens an alert and notifies, just like failed.
failed3Counts as a failure: opens an alert and notifies.
use RoundlyConsulting\Alerts\CheckResult;
use RoundlyConsulting\Alerts\Enums\Status;

$result = CheckResult::failed('Disk full', ['free_bytes' => 1024]);

$result->status;   // Status::Failed
$result->message;  // 'Disk full'
$result->meta;     // ['free_bytes' => 1024]
$result->isOk;     // false — legacy flag, true only for Status::Ok

new CheckResult(true);                     // same as CheckResult::ok()
new CheckResult(false, 'Down');            // same as CheckResult::failed('Down')
new CheckResult(Status::Warning, 'Slow');  // any Status works too

You never need a try/catch in check(): a thrown exception becomes a failed result automatically (see Timeouts & error handling).

The Status enum

Status keeps its domain helpers — isAlertable() and severity() — and adopts the Helpers trait from enums-for-laravel, so it is ready for select inputs, validation rules and API payloads:

use RoundlyConsulting\Alerts\Enums\Status;

Status::Warning->isAlertable();  // true — opens an alert and notifies
Status::Failed->severity();      // 3 (ok 0, skipped 1, warning 2, failed 3)

Status::values();                // Collection: ok, warning, failed, skipped
Status::toOptions();             // Collection: 'ok' => 'Ok', 'warning' => 'Warning', …
Status::validationRule();        // 'in:ok,warning,failed,skipped'
Status::Failed->label();         // 'Failed'
Status::tryFromLabel('Warning'); // Status::Warning

Also available: names(), labels(), options(), readable(), fromName()/tryFromName(), fromLabel(), hasValue(), is()/isIn() and the when* guards.

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.