awssat/discord-notification-channel
Send Laravel notifications directly to Discord with a dedicated notification channel. Configure a webhook, define notification classes, and deliver messages or embeds through Laravel’s familiar notification system. Lightweight integration for Laravel/PHP apps.
Install
composer require awssat/discord-notification-channel
No service provider or config publish is needed for the basic flow — the package registers itself via Laravel's package discovery.
Where to look first
DiscordChannel class (Awssat\DiscordNotificationChannel\DiscordChannel) — this is what you add to via().DiscordMessage builder — this is the fluent object you return from toDiscord().First use case
A low-volume operational notification (e.g., "new order", "deploy finished", "backup failed") sent to a single Discord webhook:
config/services.php or .env (DISCORD_WEBHOOK=...).routeNotificationForDiscord() on the notifiable (or set the webhook at send-time) so the channel knows where to POST.DiscordChannel::class in via() and a toDiscord() method returning a DiscordMessage.That's the whole loop: install → webhook → route → notify. Resist the urge to build a full "notification bus" on day one; start with one webhook to one channel and iterate.
Basic notification
use Awssat\DiscordNotificationChannel\DiscordChannel;
use Awssat\DiscordNotificationChannel\DiscordMessage;
public function via($notifiable): array
{
return [DiscordChannel::class];
}
public function toDiscord($notifiable): DiscordMessage
{
return DiscordMessage::create()
->content("Order #{$notifiable->id} was paid");
}
Embeds for structured output
Discord reads best as embeds rather than raw content. Use embeds when you have a title, fields, and a link (e.g., "Payment failed" + amount + customer + link back to your admin panel). Keep content for @here-style mentions or short summaries, and use the embed for the payload.
Routing
Two common routing strategies:
routeNotificationForDiscord() on the notifiable (User, Team, etc.) returning the webhook URL.config/services.php (discord.webhooks.orders, discord.webhooks.alerts) and pick one inside the notification.The second scales better once you have "orders" vs "errors" vs "deploys" channels, because it keeps the webhook choice next to the notification intent instead of scattered across models.
On-demand notifications
For one-off messages from a controller or artisan command, use Notification::route('discord', $webhookUrl)->notify(new SomethingHappened()). This is the fastest way to add Discord pings to existing code without wiring a notifiable.
Queue it
Implement ShouldQueue on the notification. The channel does a blocking HTTP call; queueing turns a 100–400ms webhook round-trip into a background job and lets Laravel's retry policy handle transient Discord 5xx / 429s. Pair with backoff() and a $tries cap.
Fan-out to multiple channels
Discord is a side channel, not a primary one. The idiomatic pattern is via() returning [MailChannel::class, DiscordChannel::class] — email/DB is the source of truth, Discord is the "look at this now" signal. Don't put payloads in Discord that exist nowhere else.
Testing
Use Notification::fake() and Notification::assertSentTo($user, OrderPaid::class) — you're asserting the notification was dispatched, not the HTTP call. If you want to assert the webhook body itself, either Queue::fake() and inspect the job, or wrap the channel in a mock. Avoid hitting Discord in the test suite entirely.
Integration tip
Wrap the channel in a small domain service (Notifier::orderPaid($order)) so controllers/jobs don't sprinkle Notification::route('discord', ...) everywhere. That single indirection point is where you later add throttling, feature flags, and "don't send at 3am" logic.
Discord's rate limits are aggressive and per-webhook. A burst of 10 notifications in 2 seconds can get you 429'd, and Discord returns a retry_after hint that Laravel's HTTP client won't honor automatically. Mitigations: queue + ->delay(), coalesce multiple events into one embed per notification, and for anything bursty (e.g., error alerts), implement your own per-webhook token bucket or use Redis::throttle() in the notification's middleware().
Embed limits are hard caps, not soft warnings. Title ≤ 256, description ≤ 4096, field name ≤ 256, field value ≤ 1024, max 25 fields, total embed ≤ 6000 chars. Truncate in toDiscord() (Str::limit(...)) — Discord rejects the whole webhook, not just the field. This bites hardest on exception messages and stack traces; ship a Str::limit($e->getMessage(), 1000) helper.
Color is a decimal integer, not a hex string. ->color(0x00ff00) and ->color(65280) work; ->color('#00ff00') does not. Keep a small constants class (Discord::GREEN, Discord::RED) so callers don't guess.
Webhook URLs are credentials. Anyone with the URL can post as your bot. Keep them in .env / a secrets manager, never in config/*.php committed to git, and rotate if leaked. On Laravel Cloud/Forge, use environment variables, not config files.
Silent failures are the default failure mode. The channel throws on non-2xx, but if you're using Notification::route(...) inside a try/catch (or the notifier is inside a queued job that has $tries = 1), a dead webhook is invisible. Add a health check that POSTs a tiny payload to each webhook daily, or forward channel exceptions to your error tracker.
ShouldQueue + afterCommit — if you fire notifications inside a DB transaction, queue + afterCommit() or the webhook can fire before the row is committed and the message references a phantom record.
Notifiable routing is optional but easy to miss. If routeNotificationForDiscord() returns null, the channel has nowhere to send. The failure message ("No webhook configured") is usually fine, but if you support multiple webhooks per user, return an array and dedupe — the channel will POST to each.
Version pinning. This is a 94-star community package, not a first-party Laravel one. Pin to a caret constraint (^x.y), add it to your composer update watchlist, and keep a one-line fallback (a plain Http::post($webhook, $payload) inside a queued job) so that if the package stalls you can drop it without touching call sites.
Extension point. If you need custom behavior (signing, dedupe, retries with backoff), subclass DiscordChannel and override the send method, or wrap the channel behind your own DiscordNotifier service. Both are cheaper than forking — forking a 94-star package creates a maintenance burden when you upgrade Laravel.
How can I help you explore Laravel packages today?