https://github.com/skaisser/howl
Multi-driver Laravel notifier with Discord-first rich embeds, mentions, and bot-parseable metadata.
https://github.com/skaisser/howl
discord embeds events laravel logging notifications observability webhook
Last synced: 10 days ago
JSON representation
Multi-driver Laravel notifier with Discord-first rich embeds, mentions, and bot-parseable metadata.
- Host: GitHub
- URL: https://github.com/skaisser/howl
- Owner: skaisser
- License: mit
- Created: 2026-05-11T13:58:43.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-05-12T06:52:54.000Z (about 1 month ago)
- Last Synced: 2026-05-12T07:04:44.765Z (about 1 month ago)
- Topics: discord, embeds, events, laravel, logging, notifications, observability, webhook
- Language: PHP
- Homepage: https://packagist.org/packages/skaisser/howl
- Size: 261 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README

**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 โ](https://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
```bash
composer require skaisser/howl
php artisan vendor:publish --tag=howl-config
```
Add your driver credentials to `.env`:
```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
```php
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
```php
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:
```php
// 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
```php
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
```env
HOWL_QUEUE=true
HOWL_QUEUE_CONNECTION=redis
HOWL_QUEUE_NAME=default
HOWL_RATE_LIMITER_KEY=howl-discord # opt-in Redis rate limiter
```
```php
// 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](https://howl.skaisser.dev)** covers everything in depth:
- ๐ฆ [Installation & Quick Start](https://howl.skaisser.dev/v1.0.0/guide/installation)
- ๐ค Drivers: [Discord](https://howl.skaisser.dev/v1.0.0/drivers/discord) ยท [Slack](https://howl.skaisser.dev/v1.0.0/drivers/slack) ยท [Telegram](https://howl.skaisser.dev/v1.0.0/drivers/telegram)
- ๐๏ธ [Configuration reference](https://howl.skaisser.dev/v1.0.0/configuration/reference)
- ๐งช [HowlFake testing guide](https://howl.skaisser.dev/v1.0.0/testing/howl-fake)
- ๐ [API reference](https://howl.skaisser.dev/v1.0.0/reference/api)
- โฌ๏ธ [Upgrade guide v0.x โ v1.0](https://howl.skaisser.dev/v1.0.0/upgrade)
- ๐ [Release notes](https://howl.skaisser.dev/v1.0.0/releases)
**For AI agents:** [llms.txt](https://howl.skaisser.dev/llms.txt) (index) ยท [llms-full.txt](https://howl.skaisser.dev/llms-full.txt) (inline)
---
## ๐ค Contributing
Issues and pull requests welcome at [github.com/skaisser/howl](https://github.com/skaisser/howl).
Before opening a PR, run the full suite locally:
```bash
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](LICENSE). Copyright ยฉ Shirleyson Kaisser.
// worktree test commit Wed May 13 03:12:54 -03 2026