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 Notifier Laravel Package

symfony/slack-notifier

Symfony Slack Notifier integrates Slack messaging into Symfony's Notifier component. Send chat messages via a simple DSN with your bot token and channel, and add interactive Block Kit elements like buttons, sections, images, and dividers using SlackOptions.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the bridge alongside the Notifier core — the bridge does not pull in a transport implementation on its own:

composer require symfony/slack-notifier symfony/notifier symfony/http-client

Check your PHP version first. symfony/slack-notifier v8.x requires PHP >= 8.4; if you're on Laravel 11/12 with PHP 8.2 or 8.3, pin ^7.4 instead. Also watch the transitive symfony/* constraints — the bridge shares components with the framework, so a stray ^8.0 will fight Laravel's own Symfony pins.

Next, create a Slack app at api.slack.com, add the chat:write (and chat:write.public if you want to post without joining) bot scope, install it to the workspace, and copy the Bot User OAuth Token (starts with xoxb-). Invite the app into the target channel — the API will silently 403 otherwise.

The DSN is the whole configuration surface:

SLACK_DSN=slack://xoxb-...@default?channel=my-channel-name

The first real use case is almost always operational alerting: a queued job failure, a deploy notice, an exception in a scheduled task. Because this is Symfony Notifier, you build it directly rather than using Laravel's Notification facade:

use Symfony\Component\Notifier\Chatter;
use Symfony\Component\Notifier\Transport;
use Symfony\Component\Notifier\Message\ChatMessage;

$chatter = new Chatter(Transport::fromDsn(config('services.slack.dsn')));
$chatter->send(new ChatMessage('Deploy finished: ' . $sha));

Register that Chatter as a singleton in a service provider so you're not re-parsing the DSN per call — see the next section.

Implementation Patterns

Bind a singleton in a service provider. The DSN parse is cheap but the transport builds an HTTP client and a stack of factories, so do it once:

// AppServiceProvider::register()
$this->app->singleton(Chatter::class, fn () => new Chatter(
    Transport::fromDsn(config('services.slack.dsn'))
));

Then inject Chatter anywhere — controllers, jobs, exception handlers.

Rich messages go through SlackOptions, not the constructor. The ChatMessage text is the fallback/plain notification; everything visual lives on the options object:

$options = (new SlackOptions())
    ->block(new SlackHeaderBlock(':rocket: Deploy'))
    ->block((new SlackSectionBlock())
        ->field('*Env*')->field($env)
        ->field('*SHA*')->field(substr($sha, 0, 7)))
    ->block(new SlackDividerBlock())
    ->block((new SlackActionsBlock())
        ->button('Rollback', $rollbackUrl, 'danger')
        ->button('Logs', $logsUrl, 'primary'));

$chatter->send(new ChatMessage('Deploy', $options));

Thread related notifications with threadTs() so a multi-step pipeline doesn't spam the channel — capture the ts from the first response and pass it down:

$sent = $chatter->send(new ChatMessage('Step 1', $options));
if ($sent instanceof SlackSentMessage) {
    $ts = $sent->getMessageId();
    $channel = $sent->getChannelId();
}

Editing a message (progress bars, "started → done" states) requires both IDs from the original send:

use Symfony\Component\Notifier\Bridge\Slack\UpdateMessageSlackOptions;

$chatter->send(new ChatMessage(
    'Deploy complete',
    new UpdateMessageSlackOptions($channelId, $messageId)
));

Schedule with postAt() rather than sleeping a job — Slack holds the message:

(new SlackOptions())->postAt(new \DateTimeImmutable('+1 day'));

Always dispatch through a queued job. Slack rate-limits per channel (~1 msg/sec, and one message per channel per second during bursts), so a web request that sends three notifications will throw. Wrap sends in a job with $tries = 3 and a backoff.

Centralize the block-building. Put SlackOptions construction in a small SlackMessageBuilder class or a set of value objects per event type — otherwise block arrays leak all over the codebase and every visual tweak touches ten files.

For testability, type-hint Chatter and swap it for a fake/spy in a container binding rather than making real HTTP calls.

Gotchas and Tips

  • The channel DSN parameter is strict. #my-channel and bare fabien are invalid. Use the plain channel name (my-channel) or @fabien for a DM. IDs like C0123ABCD also work and are far more stable across renames.
  • The DSN token is a secret. config/services.php + env() is fine, but remember the whole DSN (token included) goes into the container's parameter bag and can leak into config:cache output and exception traces. Never log the DSN.
  • Channel collisions. Laravel's own Slack notification channel uses services.slack.* with a webhook URL. This package uses a bot token. Don't reuse the same config key — name yours services.slack_notifier.dsn.
  • Boolean false options were dropped on older releases (fixed in 5.4.40 / 6.4.8 / 7.0.8). If you're pinning something ancient and a markdown: false or emoji: false field quietly renders wrong, that's why. Bump to a patched 7.x.
  • markdown and emoji are mutually exclusive on text objects — verbatim only applies to markdown text, emoji only to plain text. Slack silently ignores the wrong combination, so you get no error, just an unstyled field.
  • Slack's block limits are hard: 50 blocks per message, 3000 chars per section text, 10 fields per section. Exceed them and you get a 400 invalid_blocks with an unhelpful message. Truncate long stack traces before building blocks.
  • SlackSentMessage casting is required to get IDs back. Chatter::send() returns the transport-agnostic SentMessage interface; getMessageId() doesn't exist on it. Always instanceof check.
  • Laravel's Http::fake() won't intercept these calls. The notifier uses Symfony's HTTP client, not Laravel's, so your fake is bypassed and tests hit the network. Either bind a mock Chatter, or swap the transport for a NullTransport in the test environment.
  • postAt() needs the epoch conversion to land in the future. Slack rejects timestamps in the past; a +1 day calculated against a stale cached value will fail. Also confirm the workspace's timezone assumptions — the object accepts a DateTimeInterface, so pass one with an explicit timezone.
  • Button confirm dialogs only exist from v8.0 (feature added in 8.0.0-BETA1). If you're on 7.x and copy that snippet from the README, it'll be ignored.
  • Extension point: the block/options classes are plain objects implementing MessageOptionsInterface. If you need a block type the bridge doesn't ship (e.g. rich_text or table), implement it yourself and append via ->block() — the options class just serializes whatever blocks you give it. No subclassing of the bridge needed.
  • Pin the major version in composer.json and run composer why symfony/slack-notifier before bumping Laravel; a Laravel minor upgrade that raises the Symfony constraint to 8.x will silently require PHP 8.4.
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.
andydefer/laravel-notification
adachsoft/cli-process-tester
jhaskell13/laraflow
sebastienheyd/boilerplate-media-manager
ccast/tagixo-primix
pretendtrue/laravel-builder
sawmainek/laravel-scout-tntsearch-driver
tomshaw/laravel-calendar-table
dibakar/laravel-ownership
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo
stevenay/myanfont
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc