Laravel exception notifications on your phone
Laravel already reports exceptions: to your log and, with a log channel like Slack, to a chat. The trouble starts when the same exception fires 300 times in a minute. You want to hear about it once, with enough detail to act, and then be left alone until something new breaks.
This guide sets that up with the honk-me/honk-me package and Laravel 13. Exceptions become pushes, repeats of the same failure fold into one group, and scheduled tasks tell you when they fail and when they work again.
Before you start
- Laravel 13 on PHP 8.3 or later.
- A Honk account (invite-only for now: request access) and a project with an ingestion key: in the web app, open the project, then Keys.
1. Install the package
composer require honk-me/honk-me
# publishes config/honk.php and adds HONK_URL and HONK_KEY to .env.example
php artisan honk:installPut the server URL and the key in .env. Never commit the key; .env.example only gets empty placeholders.
HONK_URL=https://honk-me.app
HONK_KEY=honk_xxxxxxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxCheck the connection. honk:test sends a Light honk and prints the message ID:
php artisan honk:test # sends a light honk and prints the message id
php artisan about --only=honk # URL, key prefix (never the secret), queue, Context metadata2. Report exceptions
One line in bootstrap/app.php:
// bootstrap/app.php
use HonkMe\Laravel\Facades\Honk;
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->report(Honk::reportable());
})From now on, every reported exception becomes a Long honk, sent as a problem. Three details keep things calm:
- One group per place in the code. The group key is
exceptions/<Class>@<file>:<line>, so 300 identical exceptions are one incident with a count, and a different exception is a different incident. - At most one honk per group every 5 minutes. The throttle lives in your cache, so the 299 repeats don’t even leave your server.
- Sent after the response. The default mode,
defer, sends once the HTTP response has gone out, so a slow network never slows down a page.
It does nothing while HONK_URL or HONK_KEY is missing, which keeps local development and CI quiet, and it never throws.
You can pick a different level or throttle window per app, for example a Blast with a one-minute throttle for a payment service:
$exceptions->report(Honk::reportable(severity: 'blast', throttleSeconds: 60));Everything else lives in config/honk.php. Skip exceptions you expect, like a declined payment the UI already explains, and choose how they’re sent:
// config/honk.php (published by php artisan honk:install)
'exceptions' => [
'enabled' => (bool) env('HONK_EXCEPTIONS', true),
'severity' => 'long',
'channel' => 'exceptions',
'throttle' => 300, // seconds per group
'mode' => 'defer', // defer (after the response), sync or queue
'ignore' => [
\App\Exceptions\PaymentDeclined::class, // expected, already handled in the UI
],
],Exception messages are sent as is, truncated to fit. Keep personal data out of them, or it ends up in a push.
3. Now, after the response, or from the queue
The same choice exists for everything you send yourself:
use HonkMe\Laravel\Facades\Honk;
use HonkMe\Message;
Honk::defer()->beep('New order', "{$order->email} paid {$order->total} €"); // after the response
Honk::queue(Message::make('Imported 1 204 rows')->beep()->channel('imports')); // a queued job
Honk::loud('Disk 91%', '/var on app-01', ['groupKey' => 'disk/app-01/var']); // right nowHonk::defer()runs in the same process after the response, so you don’t need a worker. If the process dies before it runs, that honk is lost; for most alerts, that’s fine.Honk::queue()runs in a queue worker with retries and backoff, and survives restarts. Use it for anything that must arrive.- The plain helpers send right away, with retries, which suits commands, jobs and scheduled tasks.
Both defer() and queue() validate the message immediately, so a bad field fails in your code, not later in a worker.
4. Scheduled tasks
// routes/console.php
Schedule::command('backup:run')->daily()->honkOnFailure(); // problem on failure, recovery once it works again
Schedule::command('reports:send')->hourly()->honkOnSuccess(); // a beep after every successful runhonkOnFailure() sends a problem with the exit code and the end of the output, grouped per task. The next time the task succeeds, it sends a recovery, but only after a failure, so a healthy task never pings you. honkOnSuccess() sends a Beep-beep after every successful run, for the reports you actually want to hear about.
5. Test it
Honk::fake() records what would be sent or queued and never touches the network. It validates like the real client, so an invalid message still fails the test:
use HonkMe\Laravel\Facades\Honk;
use HonkMe\Message;
use HonkMe\Severity;
Honk::fake();
$this->post(route('quote.store'), $data);
Honk::assertSent(fn (Message $m) => $m->groupKey === 'requests/1' && $m->severity === Severity::Light);
Honk::assertSentTimes(1);
Honk::assertNotSent(fn (Message $m) => $m->severity === Severity::Blast);
Honk::assertQueued(fn (Message $m, string $idempotencyKey) => $m->channel === 'imports');
Honk::assertNothingSent(); // also assertNothingQueued(), assertNothingOutgoing()What Honk adds to Laravel’s reporting
Your log still gets every exception. Honk adds one push per new failure on your iPhone, Apple Watch or browser, folds the repeats into a count, and keeps them in one inbox with your cron jobs and servers. To handle some of them differently without touching code, add a rule to the project: for example, send exceptions/ from a noisy integration to the digest.
Honk is invite-only for now: request access. Everything in this guide works on the Free plan.