skaisser/howl

Multi-driver Laravel notifier with Discord-first rich embeds, mentions, and bot-parseable metadata.

Maintainers

Package info

github.com/skaisser/howl

Homepage

pkg:composer/skaisser/howl

Statistics

Installs: 17

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

v1.0.1 2026-05-12 09:24 UTC

This package is auto-updated.

Last update: 2026-05-12 09:26:25 UTC


README

Howl โ€” Multi-driver Laravel notifier

Multi-driver Laravel notifier โ€” Discord, Slack, and Telegram with rich embeds.

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

Latest Version Total Downloads License

Tests 487 Tests Passing 100% Coverage

PHP 8.3 | 8.4 Laravel 12 | 13 Pest 3 | 4

Discord Slack Telegram

๐Ÿ“– 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.