Laravel and PHP
The honk-me/honk-me package works in any PHP 8.3 app and has a first-class Laravel 13 integration: a facade, a notification channel, exception reporting, scheduler hooks and a fake for tests.
- Package
-
honk-me/honk-mePublished - Source
- github.com/honk-me/honk-php · MIT License
- Requirements
- PHP 8.3 or later with ext-curl (or any PSR-18 client). The Laravel integration is for Laravel 13.
Install
Add the package, publish the config, then put the server URL and a project’s ingestion key in .env (never commit it). honk:test sends a Light honk to check the connection.
composer require honk-me/honk-me
# publishes config/honk.php and adds HONK_URL and HONK_KEY to .env.example
php artisan honk:install HONK_URL=https://honk-me.app
HONK_KEY=honk_xxxxxxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 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 metadata Anyone with an ingestion key (honk_…) can post to its project. Keep it on servers, in jobs and in CI secrets, never in a browser, mobile or desktop app.
Send an event
The facade has a helper for each level of the Honk scale. source defaults to your APP_NAME and environment to APP_ENV.
use HonkMe\Laravel\Facades\Honk;
Honk::beep('Backup finished', 'nightly pg_dump took 42 s');
Honk::loud('Disk 91%', '/var on app-01', ['groupKey' => 'disk/app-01/var']); The notification channel
Use the honk channel like mail or database. Queued notifications keep the customer’s request fast, and the idempotency key is the notification’s id, so queue retries never notify twice. Keep toHonk() deterministic: a retry must send the same payload.
// app/Notifications/CustomerRequested.php
namespace App\Notifications;
use App\Models\CustomerRequest;
use HonkMe\Laravel\Notifications\HonkMessage;
use HonkMe\Laravel\Notifications\ToHonk;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;
use Illuminate\Queue\Attributes\Backoff;
use Illuminate\Queue\Attributes\Tries;
use Illuminate\Support\Str;
#[Tries(5)]
#[Backoff(10, 60, 300, 900)]
class CustomerRequested extends Notification implements ShouldQueue, ToHonk
{
use Queueable;
public function __construct(public CustomerRequest $request)
{
$this->afterCommit();
}
public function via(object $notifiable): array
{
return ['honk']; // add 'mail', 'database', … as you like
}
public function toHonk(object $notifiable): HonkMessage
{
$r = $this->request;
return HonkMessage::create()
->title(Str::limit("New request: {$r->subject}", 150))
->line("{$r->name} ({$r->company})")
->line(Str::limit($r->body, 2000)) // message: ≤ 8192 bytes
->light() // a light honk (info)
->category('customers')
->groupKey("requests/{$r->id}") // one group per request
->occurredAt($r->created_at)
->url(route('admin.requests.show', $r)) // https only, shown as "Open link"
// buttons, up to 3 (the first is the main one): https://, mailto:, tel: or sms:
->action('Reply by email', "mailto:{$r->email}")
->action('Call', "tel:{$r->phone}")
->meta('request_id', (string) $r->id);
}
} $request = CustomerRequest::create($validated);
$admin->notify(new CustomerRequested($request)); // a User with the Notifiable trait
Notification::route('honk', null)->notify(new CustomerRequested($request)); // or without a user Now, after the response, or from the queue
Honk::defer() sends after the HTTP response, in the same process. Honk::queue() sends from a worker and survives restarts. The plain helpers send right away, retries included. Both defer() and queue() validate the message immediately, so mistakes surface in your code.
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 now Report exceptions
One line in bootstrap/app.php turns every reported exception into a Long honk. Repeats of one failure share a group (exceptions/<Class>@<file>:<line>), and each group honks at most once per 5 minutes. It does nothing while HONK_URL or HONK_KEY is missing, and it never throws.
// bootstrap/app.php
use HonkMe\Laravel\Facades\Honk;
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->report(Honk::reportable());
}) $exceptions->report(Honk::reportable(severity: 'blast', throttleSeconds: 60)); Scheduled tasks
honkOnFailure() sends a problem with the exit code and the end of the output, and a recovery the next time the task succeeds, but only after a failure, so a healthy task never pings you.
// 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 run Testing with Honk::fake()
The 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 your 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() Plain PHP
Without Laravel, create a Client and call the same helpers. problem and recovery open and close an incident for a group key.
composer require honk-me/honk-me use HonkMe\Client;
$honk = new Client(url: getenv('HONK_URL'), key: getenv('HONK_KEY'));
$honk->beep('Backup finished', 'nightly pg_dump took 42 s'); $client->problem('db/backup', 'Backup failed', 'pg_dump exited with 1'); // a long honk by default
$client->recovery('db/backup', 'Backup OK', 'pg_dump finished in 41 s'); // a beep by default Errors
Every exception extends HonkException and tells you whether a retry makes sense. A ValidationException is a bug to fix; a retryable one can be queued again with the same idempotency key.
use HonkMe\Exception\HonkException;
use HonkMe\Exception\ValidationException;
try {
$honk->send($message, "order-{$order->id}-failed");
} catch (ValidationException $e) {
report($e); // a bug: $e->fields says what to fix
} catch (HonkException $e) {
if (!$e->isRetryable()) {
throw $e;
}
// retry later with $e->idempotencyKey, after $e->retryAfter seconds if set
} Retries that never send twice
- Every send carries an
Idempotency-Key: yours, or a fresh UUIDv7. The same key is reused on every retry, and within 24 hours Honk answers a replay with the original id andduplicate: true, so a lost response never turns into a second message. - Only network errors, timeouts,
429and5xxare retried, with exponential backoff and full jitter, never sooner than the server’sRetry-After. Other4xxresponses are never retried; fix the request instead. - Each attempt times out after 5 seconds and everything stops after 30. A wait that would cross that deadline, like a daily quota that resets at midnight, fails right away and tells you when to retry.
- Fields are validated before sending, and all invalid fields are reported together.