Symfony Notifier: Slack, Teams, SMS with One Interface
AI generated
SF
{ }
Symfony · Notifier · Slack · Teams · SMS
Symfony Notifier: Slack, Teams
and SMS with One Interface

Most applications end up with three or four separate libraries for notifications: one for email, one for Slack, one for SMS, one for push notifications. Symfony Notifier unifies every channel behind a single interface and a single dispatch method, with configurable routing, channel-specific messages and the ability to write your own bridges for internal systems.

16 min read Channels · Chatter · Texter · Bridges · Routing Symfony 7.x · PHP 8.3+ · Slack · Teams · Twilio

1. Why Symfony Notifier instead of individual libraries?

Symfony Notifier solves an organizational and maintainability problem: every notification integration that gets implemented separately brings its own configuration, its own error handling and its own abstraction. That means four different API clients, four different configuration blocks and four different test strategies for Slack, Teams, SMS and email. With Symfony Notifier there is a single NotifierInterface that accepts a Notification and forwards it, based on configuration, to one or more channels. That unifies not just the code but also testing and monitoring.

The second benefit is declarative routing. Instead of deciding in code whether a message goes to Slack or Teams, you define it in configuration: "Critical errors go to the chat channel, SMS channel and email channel simultaneously. Marketing events go to Slack only." The notification class carries the urgency, routing determines the channels. That makes it possible to change routing decisions without touching code. Symfony Notifier supports over 80 official bridges for external services, from Twilio and Vonage for SMS to PagerDuty and OpsGenie for incident management.

2. Architecture: Channels, Chatter, Texter and Browser

The Symfony Notifier architecture is split into channels, each encapsulating a particular kind of communication. The chat channel handles messaging services such as Slack, Microsoft Teams, Telegram and Discord, essentially anything that is an asynchronous team communication platform. The SMS channel handles text messages via telecom providers such as Twilio, Vonage and Sinch. The email channel integrates Symfony Mailer, so a notification using the email channel is sent as an email through the configured mailer transport. The browser channel creates flash messages in the Symfony session for instant user feedback in the browser.

The ChatterInterface and TexterInterface classes are specialized versions for chat and SMS when you want to address a specific transport directly. The NotifierInterface is the overarching interface that coordinates every channel: it receives a Notification, checks its urgency level (Urgency), and routes it to all relevant channels. This routing happens either automatically based on configuration or explicitly by setting the recipient (Recipient) on the notification. A recipient has an email address and optionally a phone number, and Symfony Notifier uses these to decide which channels make sense.


<?php

declare(strict_types=1);

namespace App\Notification;

use Symfony\Component\Notifier\Notification\Notification;
use Symfony\Component\Notifier\Recipient\Recipient;

// Basic notification: subject, content, importance level, channels
final class OrderFailedNotification extends Notification
{
    public function __construct(
        private readonly int $orderId,
        private readonly string $errorMessage,
    ) {
        parent::__construct(
            subject: sprintf('Order #%d processing failed', $this->orderId),
        );
    }

    /**
     * Define urgency and channels: channels filter which transports handle this notification.
     */
    public function getChannels(Recipient $recipient): array
    {
        // URGENT: send via chat (Slack), SMS and email simultaneously
        if ($this->getImportance() === Notification::IMPORTANCE_URGENT) {
            return ['chat/slack', 'sms', 'email'];
        }

        // HIGH: chat and email only
        return ['chat/slack', 'email'];
    }

    /**
     * Build the Slack message: channel-specific content via ChatNotificationInterface.
     */
    public function asChatMessage(
        \Symfony\Component\Notifier\Recipient\RecipientInterface $recipient,
        string $transport = null,
    ): ?\Symfony\Component\Notifier\Message\ChatMessage {
        if ($transport !== 'slack') {
            return null; // Let the parent class handle other chat transports
        }

        $message = new \Symfony\Component\Notifier\Message\ChatMessage(
            subject: $this->getSubject(),
        );

        // Attach Slack-specific Block Kit options
        $message->options(
            (new \Symfony\Component\Notifier\Bridge\Slack\SlackOptions())
                ->iconEmoji('warning')
                ->block((new \Symfony\Component\Notifier\Bridge\Slack\Block\SlackSectionBlock())
                    ->text(sprintf("*Order #%d failed*\n%s", $this->orderId, $this->errorMessage))
                )
        );

        return $message;
    }
}

3. Installation and sending your first notification

Installing Symfony Notifier is modular: the base package symfony/notifier contains the interface and the core. For each channel you install the matching bridge package: symfony/slack-notifier for Slack, symfony/microsoft-teams-notifier for Teams, symfony/twilio-notifier for SMS via Twilio. Symfony Flex installs not only the package but also creates the matching configuration in config/packages/notifier.yaml and adds the DSN environment variables as a comment in .env.

Sending a notification with Symfony Notifier is a matter of three lines: create a notification, create a recipient with an email address and phone number, call NotifierInterface::send(). The notifier checks the configured channels, determines from the recipient data and the notification's channels where the message should go, and delegates to the matching transports. Sending is synchronous by default; for asynchronous sending via Symfony Messenger you configure the messenger transport in the Notifier configuration, and notifications are automatically placed on a queue.

4. Slack messages with Block Kit and attachments

Slack messages in Symfony Notifier can be formatted through the SlackOptions block builder using the full Slack Block Kit. Block Kit allows structured messages with sections, buttons, dropdown menus, images and dividers, well beyond plain text. A deployment notification can include a header with the deploy status, a section with branch and commit hash, a context block with timestamp and deployer, and an action block with a "Rollback" button. All of that is built through the PHP builder API of SlackOptions without writing any JSON by hand.

For incident management scenarios, combining Symfony Notifier with Slack is especially valuable: a critical error in the application dispatches a Symfony Messenger message that is processed asynchronously and sends a formatted alert through the Slack channel. The alert includes the error context, a link to the stack trace, and a button that opens the ticket directly in the issue tracker. This automation replaces manual Slack messages to the on-call team and significantly shortens the time from error to first response. Symfony Notifier makes this possible without running a separate alerting service.


<?php

declare(strict_types=1);

namespace App\Notification;

use Symfony\Component\Notifier\Bridge\Slack\Block\SlackActionsBlock;
use Symfony\Component\Notifier\Bridge\Slack\Block\SlackContextBlock;
use Symfony\Component\Notifier\Bridge\Slack\Block\SlackDividerBlock;
use Symfony\Component\Notifier\Bridge\Slack\Block\SlackHeaderBlock;
use Symfony\Component\Notifier\Bridge\Slack\Block\SlackSectionBlock;
use Symfony\Component\Notifier\Bridge\Slack\SlackOptions;
use Symfony\Component\Notifier\Message\ChatMessage;
use Symfony\Component\Notifier\Recipient\RecipientInterface;

/**
 * Sends a richly formatted Slack deployment notification with Block Kit.
 */
final class DeploymentSlackNotification extends \Symfony\Component\Notifier\Notification\Notification
{
    public function __construct(
        private readonly string $environment,
        private readonly string $version,
        private readonly string $deployer,
        private readonly bool $success,
    ) {
        parent::__construct(subject: sprintf('Deployment %s: %s v%s', $success ? 'successful' : 'FAILED', $environment, $version));
    }

    /**
     * Build a rich Slack Block Kit message for the deployment notification.
     */
    public function asChatMessage(RecipientInterface $recipient, string $transport = null): ?ChatMessage
    {
        $statusEmoji = $this->success ? ':white_check_mark:' : ':rotating_light:';
        $statusText  = $this->success ? 'Deployment successful' : 'Deployment FAILED';

        $options = (new SlackOptions())
            ->iconEmoji($this->success ? 'rocket' : 'fire')
            ->block(new SlackHeaderBlock(sprintf('%s %s', $statusEmoji, $statusText)))
            ->block(
                (new SlackSectionBlock())
                    ->field('*Environment*', $this->environment)
                    ->field('*Version*', $this->version)
            )
            ->block(new SlackDividerBlock())
            ->block(
                (new SlackContextBlock())
                    ->add(sprintf('Deployed by *%s* at %s', $this->deployer, (new \DateTimeImmutable())->format('H:i d.m.Y')))
            );

        // Add action button only for failed deployments
        if (!$this->success) {
            $options->block(
                (new SlackActionsBlock())
                    ->button('Open Runbook', 'https://wiki.example.com/runbook/deploy-failure', 'danger')
            );
        }

        $message = new ChatMessage(subject: $this->getSubject());
        $message->options($options);

        return $message;
    }
}

5. Microsoft Teams: sending Adaptive Cards

Microsoft Teams notifications through Symfony Notifier use Adaptive Cards, the official JSON format for interactive Teams messages. The MicrosoftTeamsOptions builder makes it possible to build Adaptive Cards declaratively in PHP: text, tables, fact lists and action buttons are assembled as PHP objects and automatically converted by the bridge into the correct Adaptive Card JSON format. Teams notifications via incoming webhooks are sufficient for simple scenarios; for more complex interactions with the Bot Framework and personal messages to individual users you need the Microsoft Graph API, which the standard Notifier does not cover.

A typical use case for Teams notifications in enterprise environments is monitoring business processes: an order above a certain cart value, a new enterprise registration, or a resolved critical support ticket triggers a Teams notification to the responsible account manager. Symfony Notifier automatically routes these notifications into the configured Teams channel based on urgency and recipient. Compared to Slack, Teams is the preferred platform primarily in Microsoft-oriented companies, and with the Teams bridge of Symfony Notifier, switching from Slack to Teams or running both platforms in parallel is a configuration change with no code adjustment.

6. SMS with Twilio, Vonage and other bridges

SMS notifications in Symfony Notifier go through the SMS channel and use bridge packages for external telecom providers. Twilio is the most commonly used provider and offers reliable worldwide delivery through the TwilioTransport. Vonage (formerly Nexmo) is an alternative with strong presence in Europe. Amazon SNS, Sinch, MessageBird and Infobip are further available bridges. Configuration of every SMS bridge runs through a DSN environment variable: TWILIO_DSN=twilio://SID:TOKEN@default?from=+4912345678. Switching SMS providers is therefore a one-line configuration change.

A common question about Symfony Notifier and SMS concerns handling delivery reports. Twilio and Vonage allow configuring webhook callbacks for when an SMS has been delivered or has failed. Symfony Notifier itself does not manage delivery reports; these arrive as incoming webhooks and are processed by Symfony controllers that update the delivery status in the application database. The Notifier package takes care of sending, processing the callbacks is application-specific logic. For critical SMS notifications, such as two-factor authentication and security alerts, the symfony/messenger transport is recommended for asynchronous sending with automatic retry on failure.

7. Routing: which channel for which notification?

Routing in Symfony Notifier determines which notification is sent over which channels. There are two routing levels. The first is the configuration in notifier.yaml, which maps importance levels to channels. Notifications with IMPORTANCE_URGENT go to chat and SMS, notifications with IMPORTANCE_LOW go to email only. The second level is the getChannels() method in the notification class, which can specify per notification which channels are used, independently of the global configuration.

For production setups a clear channel hierarchy is recommended: monitoring alerts and critical errors go to Slack or Teams (fast visibility), direct user messages go as SMS (immediate attention), summaries and reports go as email (persistent record). Symfony Notifier supports sending to multiple channels in parallel within a single dispatch operation; the notification is sent to all configured channels simultaneously, not sequentially. When sending asynchronously via Symfony Messenger, each channel is placed on the queue as a separate message, which lets individual channels be retried separately on failure.


# config/packages/notifier.yaml
# Symfony Notifier: channel routing and transport configuration

framework:
  notifier:
    # Chat transports: Slack and Teams with Incoming Webhook URLs
    chatter_transports:
      slack:  '%env(SLACK_DSN)%'        # slack://xoxb-TOKEN@default?channel=alerts
      teams:  '%env(TEAMS_DSN)%'        # microsoftteams://default/WEBHOOK_URL

    # SMS transports: Twilio as primary, Vonage as fallback
    texter_transports:
      twilio: '%env(TWILIO_DSN)%'       # twilio://SID:TOKEN@default?from=+49...
      vonage: '%env(VONAGE_DSN)%'       # vonage://KEY:SECRET@default?from=Mironsoft

    # Email channel uses Symfony Mailer, no separate DSN needed
    # Browser channel adds Flash messages to the session

    # Global routing by urgency level
    channel_policy:
      urgent: ['chat/slack', 'sms/twilio', 'email']
      high:   ['chat/slack', 'email']
      medium: ['email']
      low:    ['email']

    # Use Symfony Messenger for async sending: prevents slow HTTP calls in requests
    # messenger_bus: messenger.default_bus

8. Writing a custom Notifier bridge for internal systems

When an internal monitoring system, an issue-tracking tool, or a self-hosted chat server does not have an official Symfony Notifier bridge, you write your own. A Notifier bridge consists of three classes: the Transport, which sends the HTTP request to the external service; the TransportFactory, which builds the transport object from the DSN string; and optionally an Options object for channel-specific message formats. The transport implements TransportInterface with a send() method and a supports() predicate that determines which message types the transport handles.

The DSN pattern for custom bridges is the same as for official ones: my-system://TOKEN@host/channel. The transport factory parses this string and builds the transport object with the extracted credentials. The bridge class registers itself in the DI container as a known transport through tagged service configuration. Once registered, the custom bridge works identically to an official one: it appears in the notifier.yaml configuration, is addressed through the NotifierInterface, and supports retry on failure via the Messenger transport. Writing custom bridges is an excellent way to integrate internal tools into the Symfony Notifier ecosystem without waiting for official support.

9. Notifier channels compared

Every Symfony Notifier channel has its own strength, and choosing the right channel for the right use case is decisive for how well notifications are received and acted upon.

Channel Strengths Limitations Typical use
Chat (Slack/Teams) Instant visibility, rich text, buttons Only visible within the team Monitoring alerts, deployments
SMS Highest open rate, no internet needed Cost per message, no HTML 2FA, critical alerts, on-call
Email HTML, attachments, persistent record Low open rate, slower Reports, confirmations, invoices
Browser (Flash) Instantly visible to the current user Only for session users Form feedback, status messages
Push (Firebase) Delivered even with the app closed Opt-in, platform dependent Mobile apps, service updates

In practice, the combination of chat and email is sufficient for most backend applications: chat for instant team visibility on errors and deployments, email for user notifications and persistent records. SMS pays off for security-critical scenarios (2FA, security alerts) or on-call situations where chat notifications could be overlooked. Symfony Notifier makes it trivial to use all three channels in parallel without separate code for each.

Mironsoft

Symfony Notifier, alerting systems and multi-channel notifications

Building a notification system with Symfony Notifier?

We implement multi-channel notification systems with Symfony Notifier, from Slack and Teams integration through SMS sending to custom bridges for internal systems.

Channel integration

Slack Block Kit, Teams Adaptive Cards, SMS bridges and email templates for structured notifications

Custom bridges

Custom Notifier bridges for internal monitoring systems, issue trackers and self-hosted chat platforms

Async sending

Symfony Messenger integration for asynchronous sending with retry logic and a failure queue

10. Summary

Symfony Notifier unifies every notification channel behind a single NotifierInterface, eliminating the need to maintain a separate integration for each channel. Slack Block Kit, Microsoft Teams Adaptive Cards, SMS via Twilio or Vonage, and browser flash messages all run through the same dispatch mechanism. Configurative routing by urgency level determines which channels are active for which notification, with no code changes needed when routing decisions change. Custom bridges extend the system for internal tools and self-hosted services.

The biggest advantage of Symfony Notifier lies in testability: with the test transport, notifications are not actually sent but collected in an array. Integration tests check whether the right messages go to the right channels, without any Slack webhooks, Twilio account, or real email delivery. Combined with Symfony Messenger for asynchronous sending, Symfony Notifier becomes production ready: notifications never slow down HTTP requests and are automatically retried on failure.

Symfony Notifier, the essentials at a glance

One interface, every channel

NotifierInterface::send() routes notifications to chat, SMS, email and browser, based on urgency level and Notification::getChannels().

Channel-specific messages

asChatMessage(), asSmsMessage() and asEmailMessage() in the notification class deliver channel-specific formats: Block Kit for Slack, Adaptive Cards for Teams.

80+ bridges

Twilio, Vonage, Slack, Teams, Telegram, PagerDuty, Firebase, or write your own bridge for internal systems. Switching is a single configuration line.

Async with Messenger

messenger_bus in notifier.yaml. Notifications land on the queue, the HTTP response stays fast, retry on failure happens automatically.

11. FAQ: Symfony Notifier

1What is Symfony Notifier?
Symfony component for multi-channel notifications: Slack, Teams, SMS, email and browser through a unified interface. Over 80 official bridge packages available.
2How to install?
composer require symfony/notifier, then bridge-specific packages: symfony/slack-notifier, symfony/twilio-notifier etc. Symfony Flex creates configuration automatically.
3Chatter vs. Texter vs. Notifier?
Chatter: chat channels (Slack, Teams). Texter: SMS channels. Notifier: coordinates every channel and routes based on urgency and notification configuration.
4Using Slack Block Kit?
asChatMessage() in the notification class returns a ChatMessage with SlackOptions. Builder API for header, sections, dividers, context and action blocks, no JSON to write by hand.
5Async with Symfony Messenger?
messenger_bus in notifier.yaml, notifications are placed on a queue instead of being sent synchronously. Each channel as a separate message, retry on failure is automatic.
6Writing a custom bridge?
Transport (HTTP request), TransportFactory (parses the DSN), optionally an Options object. Register the factory as a tagged service, then it works like an official bridge.
7Configuring routing?
channel_policy in notifier.yaml maps urgent/high/medium/low to channel lists. getChannels() in the notification class overrides global routing per recipient.
8How to test?
Built-in test transport collects notifications instead of sending them. getSentMessages() in functional tests. No real Slack webhook or SMS account needed.
9Which SMS providers are supported?
Twilio, Vonage, Amazon SNS, Sinch, MessageBird, Infobip and more. Switching providers means one new DSN line in .env. Handler code stays identical.
10Processing delivery reports?
Symfony Notifier only sends, delivery reports arrive as webhooks from the provider. A Symfony controller receives them, verifies the signature and updates the delivery status in the DB.