Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Slack Laravel Package

nexylan/slack

A maintained PHP package for sending messages to Slack via incoming webhooks, with a fluent API for rich messages and attachments. Laravel-friendly and ideal for notifications, alerts, and simple integrations.

View on GitHub
Deep Wiki
Context7

Getting Started

Install it straight from Composer — the package is framework-agnostic but trivially consumable inside Laravel:

composer require nexylan/slack

The only hard requirement is a Slack incoming webhook URL (create one at api.slack.com/apps → Incoming Webhooks). Everything happens through Nexy\Slack\Client:

use Nexy\Slack\Client;

$client = new Client('https://hooks.slack.com/services/T000/B000/XXXX');
$client->send('Deploy finished'); // first use case: fire a one-liner

Where to look first

  1. Nexy\Slack\Client — the constructor signature new Client(string $endpoint, array $attributes = [], ?GuzzleHttp\ClientInterface $http = null). That third argument is what makes it testable; the second argument is where you set default channel, username, icon, link_names, unfurl_links, unfurl_media, allow_markdown, markdown_in_attachments.
  2. Nexy\Slack\Message — created via $client->createMessage(), exposes setText(), setChannel(), setUsername(), setIcon(), attach(), send().
  3. Nexy\Slack\Attachment / AttachmentField — for rich, structured notifications.

First real use case: a Laravel controller or console command that pings a channel.

// app/Http/Controllers/DeployController.php
app(\Nexy\Slack\Client::class)->send('*Deploy* to :rocket: `production` finished.');

Do not instantiate new Client(...) inline in controllers. Register it once (see below) so config, channel defaults, and the Guzzle client are centralized.

Implementation Patterns

Singleton binding with config. Keep the webhook out of source and expose your defaults:

// config/services.php
'slack' => [
    'webhook'  => env('SLACK_WEBHOOK_URL'),
    'channel'  => env('SLACK_CHANNEL', '#general'),
    'username' => env('SLACK_USERNAME', config('app.name')),
],

// app/Providers/AppServiceProvider.php
use Nexy\Slack\Client;

public function register(): void
{
    $this->app->singleton(Client::class, fn () => new Client(
        config('services.slack.webhook'),
        [
            'channel'                     => config('services.slack.channel'),
            'username'                    => config('services.slack.username'),
            'link_names'                  => true,
            'allow_markdown'              => true,
            'markdown_in_attachments'     => ['text', 'pretext', 'title', 'fields'],
        ],
    ));
}

Message object for per-send overrides. Anything set on the Message wins over constructor defaults — useful when one job needs a different channel:

$message = $client->createMessage()
    ->setChannel('#alerts')
    ->setUsername('Deploy Bot')
    ->setIcon(':rocket:')
    ->setText('Build #' . $build->id . ' failed');

$client->sendMessage($message);

Attachments for structured alerts. Slack payloads with attachments render much better than free text, and fields are the idiomatic shape for CI/CD, monitoring, and error reports:

$attachment = $client->createAttachment()
    ->setColor($status === 'failed' ? 'danger' : 'good')
    ->setFallback("Build {$build->id} {$status}")   // required for push notifications
    ->setTitle("Build {$build->id} {$status}")
    ->setTitleLink(route('builds.show', $build))
    ->addField('Branch', $build->branch, true)
    ->addField('Duration', $build->duration, true)
    ->addField('Commit', $build->commit, false);

$client->sendMessage(
    $client->createMessage()
        ->setText('')                       // text can be empty when attachments exist
        ->attach($attachment)
);

Queue it. Sends are synchronous Guzzle calls, so wrap in a job. This also gives you retries/backoff, which is the right place to handle Slack outages:

class SendSlackAlert implements ShouldQueue
{
    public int $tries = 3;
    public int $backoff = 5;

    public function __construct(public string $text) {}

    public function handle(Client $client): void
    {
        $client->send($this->text);
    }
}

Testing. Inject a Guzzle client with a MockHandler — no HTTP is performed and you can assert on the payload:

use GuzzleHttp\Client as Guzzle;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;

$mock = new MockHandler([new Response(200, [], 'ok')]);
$client = new Client('https://hooks.slack.com/services/fake', [], new Guzzle(['handler' => HandlerStack::create($mock)]));

$this->app->instance(Client::class, $client);

You can also drop Http::fake() entirely — Slack webhooks aren't driven by the Laravel HTTP client here.

Integrations. Wire it into Laravel’s exception handler (report()), event listeners (Illuminate\Notifications is a heavier alternative if you need per-user routing), or scheduler hooks. For anything that might be user-facing rather than ops-facing, prefer Laravel's built-in slack notification channel — this package shines for broadcast to a channel alerts.

Gotchas and Tips

  • The webhook URL is a secret. Never ship it to Git, never log it. Rotate it if a CI log leaks it — it’s effectively a bearer token for the channel.
  • Constructor defaults are not #channel-safe. If SLACK_CHANNEL is empty, you’ll get a payload Slack silently accepts but routes to the app’s default channel. Validate the env var in a config check.
  • allow_markdown and markdown_in_attachments are separate. allow_markdown => true does not enable mrkdwn inside attachments. You must list text, pretext, title, fields explicitly, otherwise *bold* renders literally.
  • fallback is required in practice. Attachments without a fallback won’t trigger desktop/mobile notifications, which defeats the point of alerting.
  • Colors are limited. Use good, warning, danger, or a hex string like #439FE0. Arbitrary names are ignored by Slack.
  • Rate limits are per channel, ~1 message/second. Bursty loops will drop silently or get 429s. Batch multiple items into a single message with several attachments, and throttle queued jobs (Redis::throttle() or backoff).
  • Errors surface as HTTP exceptions. The client does not retry; a 400 from Slack (bad payload, e.g. missing fallback, invalid field shape) or a 404 (revoked webhook) will throw. Catch GuzzleHttp\Exception\* at the job boundary and treat Slack failures as non-blocking.
  • The API uses setters, not fluent to() / from(). Old blog posts (and the upstream maknz/slack README) show $client->to('#x')->from('Bot')->send(...). In this fork you get createMessage()->setChannel()->setUsername()->setText() and then sendMessage(). If a snippet doesn't work, this is almost always why.
  • icon vs icon_url. setIcon(':ghost:') sends an emoji; setIconUrl('https://...') sends an image. Setting both produces unpredictable results — pick one.
  • Channel must exist and the app must be a member of a private channel before messages land. Sending to a private channel you haven’t invited the bot to throws a 404-ish payload error, not a permission error.
  • Empty text + attachments is fine, but both empty is not. Slack responds with invalid_payload; add a guard if you build messages dynamically.
  • Version pinning. Last release is from early 2021 and the repo is effectively in maintenance mode. Pin nexylan/slack in composer.json and keep Guzzle aligned with the version your composer.lock resolved, or you’ll hit type errors in the injected ClientInterface.
  • Extension point: if you need Slack Block Kit (this package predates it and only speaks the legacy attachment format), subclass Message — the Client calls $message->toArray() before POSTing, so overriding that method lets you inject blocks without forking.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons