skaisser/howl

Multi-driver Laravel notifier with Discord-first rich embeds, mentions, and bot-parseable metadata.
1,694 1
Install
composer require skaisser/howl
Latest Version:v1.0.1
PHP:^8.3
License:MIT
Last Updated:May 23, 2026
Links: GitHub  ·  Packagist
Maintainer: skaisser

Multi-driver Laravel notifier — Discord, Slack, and Telegram with rich embeds.

When something goes wrong, your app should howl into the night.

📖 Full documentation at howl.skaisser.dev →


✨ Why Howl?

A single driver-agnostic API for Discord, Slack, and Telegram. Drop it into any Laravel 12 or 13 app and start delivering rich, structured notifications in minutes.

  • 🎯 One fluent API for all three drivers — switch per-call without touching business logic
  • 🎨 Rich, native formatting per platform — Discord embeds, Slack Block Kit, Telegram HTML — with mentions, fields, code blocks, buttons, attachments
  • 🛰️ Channel failover & fan-out — automatic backup channel dispatch on failure
  • 📦 Seven built-in event templates — exceptions, deployments, audits, cron heartbeats, job failures, manual ops, generic info
  • 🧪 HowlFake test helper — assert notifications without real HTTP calls; per-driver assertions
  • ⚡ Queue-aware with exponential backoff and opt-in Redis rate limiting
  • ✅ 100% line coverage across 487 tests, enforced by pest --coverage --min=100
  • 📚 Versioned docs at howl.skaisser.dev plus machine-readable llms.txt for AI agents

🛠️ Compatibility

PHP Laravel Pest PHPUnit Testbench Status
8.3 12.x 3.x 11.x 10.x ✅
8.3 13.x 4.x 12.x 11.x ✅
8.4 12.x 3.x 11.x 10.x ✅
8.4 13.x 4.x 12.x 11.x ✅

Composer constraints support all four combinations. CI validates the latest combo (PHP 8.4 × Laravel 13) on every push and PR targeting main; the other rows are validated locally before each release.


📦 Installation

composer require skaisser/howl
php artisan vendor:publish --tag=howl-config

Add your driver credentials to .env:

HOWL_DRIVER=discord                            # discord | slack | telegram
HOWL_DEFAULT_CHANNEL=errors                    # primary channel name

# Discord
HOWL_DISCORD_DEFAULT=https://discord.com/api/webhooks/...

# Slack (optional — only if you use the slack driver)
HOWL_SLACK_BOT_TOKEN=xoxb-...
HOWL_SLACK_DEFAULT_CHANNEL=C0XXXXXXX

# Telegram (optional — only if you use the telegram driver)
HOWL_TELEGRAM_BOT_TOKEN=123456:ABC-DEF...
HOWL_TELEGRAM_CHAT_ID=-1001234567890

🚀 Quick Start

use Skaisser\Howl\Facades\Howl;
use Skaisser\Howl\Events\GenericExceptionEvent;

// Direct severity verbs — use config('howl.driver') by default
Howl::error(new GenericExceptionEvent($exception));
Howl::info('Scheduled job completed');
Howl::audit($auditEvent);

// Channel routing — per-call override beats event default beats config
Howl::on('audits')->audit($event);

// Per-call driver override — same payload, different destination
Howl::driver('slack')->info('Deploy succeeded');
Howl::driver('telegram')->error('Database connection lost');

// Chainable: pick driver + channel + severity in one go
Howl::driver('slack')->channel('deployments')->success('v1.2.0 shipped');

📨 Built-in event templates

use Skaisser\Howl\Events\{
    GenericExceptionEvent,
    DeploymentEvent,
    AuditEvent,
    CronHeartbeatEvent,
    JobRetryExhaustedEvent,
    ManualOperationEvent,
    GenericInfoEvent,
};

Howl::error(new GenericExceptionEvent($e));
Howl::deployment(new DeploymentEvent(version: 'v1.2.0', env: 'production', commit: 'abc1234'));
Howl::audit(new AuditEvent(actor: $user->email, action: 'role.changed', target: $role));

🛰️ Channel Failover & Fan-Out

Configure a backup channel and pick the mode:

// config/howl.php
'channel' => 'errors',
'channel_backup' => 'errors-backup',
'channel_mode' => 'failover',   // try primary; on failure, backup once
// or
'channel_mode' => 'fan_out',    // dispatch to BOTH channels in parallel
  • failover (default): primary first, backup only on failure. true on first success, false if both fail.
  • fan_out: dispatch to primary AND backup sequentially. true if at least one succeeds. Doubles rate-limit consumption — size your RateLimiter::for() quota accordingly.

🧪 Testing with HowlFake

use Skaisser\Howl\Facades\Howl;

$fake = Howl::fake();

Howl::error('Something broke');
Howl::driver('slack')->info('Deploy started');

// Global assertions
$fake->assertSent(fn ($p) => $p->severity === 'error');
$fake->assertNothingSent(); // negation form
expect($fake->sent())->toHaveCount(2); // count via the sent() accessor

// Per-driver assertions (v1.0+)
$fake->assertSentVia('discord', fn ($p) => $p->severity === 'error');
$fake->assertSentVia('slack', fn ($p) => $p->severity === 'info');
$fake->assertSentViaNothing('telegram');

No real HTTP calls. No mocks of HTTP clients. Drop-in replacement.


⚡ Queue Mode + Rate Limiting

HOWL_QUEUE=true
HOWL_QUEUE_CONNECTION=redis
HOWL_QUEUE_NAME=default
HOWL_RATE_LIMITER_KEY=howl-discord   # opt-in Redis rate limiter
// AppServiceProvider::boot()
use Illuminate\Cache\RateLimiter;
use Illuminate\Cache\RateLimiting\Limit;

RateLimiter::for('howl-discord', fn () => Limit::perMinute(28));

SendHowlJob ships with 3 retries + exponential backoff. Queue-failure events always force sync to avoid recursive loops.


📖 Documentation

The full docs site at howl.skaisser.dev covers everything in depth:

For AI agents: llms.txt (index) · llms-full.txt (inline)


🤝 Contributing

Issues and pull requests welcome at github.com/skaisser/howl.

Before opening a PR, run the full suite locally:

composer install
vendor/bin/pest --parallel
vendor/bin/pest --coverage --min=100   # enforces 100% line coverage
vendor/bin/pint                         # code style

📜 License

MIT — see LICENSE. Copyright © Shirleyson Kaisser. // worktree test commit Wed May 13 03:12:54 -03 2026

Related Packages

jeffersongoncalves/laravel-discord-logger

Send Laravel logs to Discord with deduplication, configurable error grouping, ra...

3,165 3
vinkius-labs/watchdog-discord

Real-time error monitoring and alerting for Laravel apps via Discord

2,268 3
marvinlabs/laravel-discord-logger

Logging to a discord channel in Laravel

1,374,090 212
mpociot/captainhook

Add webhooks to your Laravel app.

8,910 335
daneeveritt/login-notifications

Login notifications for Laravel

13,216 4

Version History

Version Released PHP Laravel License
v1.0.1 ^8.3 ^12.0|^13.0 MIT
v1.0.0 ^8.3 ^12.0|^13.0 MIT
v0.2.1 ^8.3 ^12.0|^13.0 MIT
v0.2.0 ^8.3 ^12.0|^13.0 MIT