skie/notification-slack

Slack notification channel for CakePHP Notification plugin

Installs: 1

Dependents: 0

Suggesters: 1

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

Type:cakephp-plugin

pkg:composer/skie/notification-slack

dev-main 2025-10-19 19:31 UTC

This package is auto-updated.

Last update: 2025-10-19 19:31:56 UTC


README

Send notifications to Slack using both the modern Block Kit API and legacy attachments via webhooks using the CakePHP Notification plugin.

Features:

  • ✅ Modern Block Kit API with rich interactive elements
  • ✅ Legacy attachment API for backwards compatibility
  • ✅ Fluent API for building messages
  • ✅ Full type safety (PHPStan Level 8)
  • ✅ Comprehensive test coverage
  • ✅ CakePHP HTTP Client (no external dependencies)

Requirements

  • PHP 8.1+
  • CakePHP 5.0+
  • CakePHP Notification Plugin
  • Slack Incoming Webhook URL

Installation

composer require cakephp/notification-slack

Configuration

Two Channel Types

The plugin supports two Slack integration methods:

  1. Incoming Webhooks (SlackWebhookChannel)

    • Simple webhook URLs
    • No OAuth required
    • One-way communication
    • Perfect for notifications
  2. Web API (SlackWebApiChannel)

    • Full Slack API access
    • Requires OAuth bot token
    • Two-way communication
    • Advanced features (threads, metadata, etc.)

Setup for Webhooks

  1. Go to your Slack workspace
  2. Navigate to Apps → Manage Apps
  3. Search for "Incoming Webhooks"
  4. Click "Add Configuration"
  5. Select a channel and click "Add Incoming WebHooks Integration"
  6. Copy the Webhook URL

Setup for Web API

  1. Go to https://api.slack.com/apps
  2. Create or select your Slack App
  3. Navigate to "OAuth & Permissions"
  4. Add chat:write bot token scope
  5. Install app to workspace
  6. Copy the "Bot User OAuth Token" (starts with xoxb-)

Load the Plugin

The plugin is loaded via config/plugins.php:

'Cake/SlackNotification' => [],

Configure Channels

For Webhooks (config/app_local.php):

return [
    'Notification' => [
        'channels' => [
            'slack' => [
                'className' => 'Cake\SlackNotification\Channel\SlackWebhookChannel',
                'webhook' => 'https://hooks.slack.com/services/YOUR/WEBHOOK/URL',
            ],
        ],
    ],
];

For Web API (config/app_local.php):

return [
    'Notification' => [
        'channels' => [
            'slack' => [
                'className' => 'Cake\SlackNotification\Channel\SlackWebApiChannel',
                'bot_user_oauth_token' => 'xoxb-your-bot-token',
                'channel' => '#general', // Default channel
            ],
        ],
    ],
];

Or use environment variables (.env):

# For Webhooks
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/YOUR/WEBHOOK/URL

# For Web API
SLACK_BOT_TOKEN=xoxb-your-bot-token
SLACK_CHANNEL=#general

Usage

The plugin supports two message formats:

  1. Modern Block Kit - Rich interactive messages with blocks, buttons, and more
  2. Legacy Attachments - Backwards compatible attachment-based messages

Modern Block Kit API

Use the modern BlockKitMessage for rich interactive messages:

<?php
namespace App\Notification;

use Cake\Datasource\EntityInterface;
use Cake\Notification\AnonymousNotifiable;
use Cake\Notification\Notification;
use Cake\SlackNotification\BlockKit\BlockKitMessage;

class OrderCompletedNotification extends Notification
{
    public function __construct(
        protected string $orderNumber,
        protected float $total,
    ) {}

    public function via(EntityInterface|AnonymousNotifiable $notifiable): array
    {
        return ['database', 'slack'];
    }

    public function toSlack(EntityInterface|AnonymousNotifiable $notifiable): BlockKitMessage
    {
        return (new BlockKitMessage())
            ->text("Order {$this->orderNumber} completed")
            ->headerBlock('🎉 New Order Completed!')
            ->dividerBlock()
            ->sectionBlock(function ($section) {
                $section->text("Order *{$this->orderNumber}* has been completed")
                    ->markdown();
                $section->field('Total')->markdown()->text("*$" . number_format($this->total, 2) . "*");
                $section->field('Status')->text('✅ Completed');
            })
            ->actionsBlock(function ($actions) {
                $actions->button('View Order')
                    ->url("https://example.com/orders/{$this->orderNumber}")
                    ->primary();
                $actions->button('Download Invoice')
                    ->url("https://example.com/orders/{$this->orderNumber}/invoice");
            });
    }
}

Block Kit Features

Header Block:

$message->headerBlock('Welcome to our Service!');

Section Block with Fields:

$message->sectionBlock(function ($section) {
    $section->text('Main section text')->markdown();
    $section->field('Field 1')->text('Value 1');
    $section->field('Field 2')->text('Value 2');
});

Actions Block with Buttons:

$message->actionsBlock(function ($actions) {
    $actions->button('Approve')
        ->value('approve_123')
        ->primary();

    $actions->button('Reject')
        ->value('reject_123')
        ->danger()
        ->confirm('Are you sure?');
});

Context Block:

$message->contextBlock(function ($context) {
    $context->image('https://example.com/icon.png', 'Icon');
    $context->text('Additional context information');
});

Image Block:

$message->imageBlock('https://example.com/chart.png')
    ->alt('Sales chart')
    ->title('Q4 Sales Performance');

Divider Block:

$message->dividerBlock();

Legacy Attachments API

Use the legacy SlackMessage for attachment-based messages:

Creating a Notification

<?php
namespace App\Notification;

use Cake\Datasource\EntityInterface;
use Cake\Notification\AnonymousNotifiable;
use Cake\Notification\Notification;
use Cake\SlackNotification\Message\SlackMessage;

class DeploymentNotification extends Notification
{
    public function __construct(
        protected string $version,
        protected string $environment
    ) {}

    public function via(EntityInterface|AnonymousNotifiable $notifiable): array
    {
        return ['database', 'slack'];
    }

    public function toSlack(EntityInterface|AnonymousNotifiable $notifiable): SlackMessage
    {
        return SlackMessage::create()
            ->from('Deploy Bot', ':rocket:')
            ->success()
            ->content("Deployment successful!")
            ->attachment(function ($attachment) {
                $attachment
                    ->title('Version ' . $this->version)
                    ->field('Environment', $this->environment, true)
                    ->field('Time', date('Y-m-d H:i:s'), true)
                    ->color('good');
            });
    }
}

Sending Notifications

// To a user
$user = $this->Users->get($userId);
$user->notify(new DeploymentNotification('v2.1.0', 'production'));

// On-demand to a webhook
NotificationManager::route('slack', 'https://hooks.slack.com/services/...')
    ->notify(new DeploymentNotification('v2.1.0', 'production'));

Routing

Define routing on your entities:

class User extends Entity
{
    public function routeNotificationForSlack(): ?string
    {
        return $this->slack_webhook_url ?? null;
    }
}

Message Features

Basic Message

SlackMessage::create('Hello from CakePHP!');

Custom Username and Icon

SlackMessage::create()
    ->content('Deployment complete')
    ->from('Deploy Bot', ':rocket:');

Message Levels

SlackMessage::create()
    ->content('Success!')
    ->success(); // green color

SlackMessage::create()
    ->content('Warning!')
    ->warning(); // yellow color

SlackMessage::create()
    ->content('Error!')
    ->error(); // red/danger color

With Attachments

SlackMessage::create()
    ->content('New Order Received')
    ->attachment(function ($attachment) {
        $attachment
            ->title('Order #12345', 'https://example.com/orders/12345')
            ->content('Customer ordered 3 items')
            ->color('good')
            ->field('Total', '$99.99', true)
            ->field('Items', '3', true)
            ->image('https://example.com/order-preview.png')
            ->footer('Order System')
            ->timestamp(time());
    });

Multiple Attachments

SlackMessage::create()
    ->content('Daily Report')
    ->attachment(function ($attachment) {
        $attachment->title('Sales')->field('Today', '$1,234');
    })
    ->attachment(function ($attachment) {
        $attachment->title('Orders')->field('Total', '45');
    });

With Actions (Buttons)

SlackMessage::create()
    ->content('Approval Required')
    ->attachment(function ($attachment) {
        $attachment
            ->title('New User Registration')
            ->content('John Doe wants to join the team')
            ->action('Approve', 'https://example.com/approve/123', 'primary')
            ->action('Reject', 'https://example.com/reject/123', 'danger');
    });

Advanced Options

SlackMessage::create()
    ->content('Notification with options')
    ->to('#general')              // Override channel
    ->linkNames()                 // Link @mentions and #channels
    ->unfurlLinks(true)           // Show link previews
    ->unfurlMedia(false);         // Don't show media previews

Complete Example

public function toSlack(EntityInterface|AnonymousNotifiable $notifiable): SlackMessage
{
    return SlackMessage::create()
        ->from('Orders Bot', ':shopping_cart:')
        ->success()
        ->content("New order from {$this->order->customer_name}")
        ->attachment(function ($attachment) {
            $attachment
                ->title("Order #{$this->order->id}", $this->order->url)
                ->field('Customer', $this->order->customer_name, true)
                ->field('Total', '$' . number_format($this->order->total, 2), true)
                ->field('Items', (string)$this->order->item_count, true)
                ->field('Status', $this->order->status, true)
                ->color('good')
                ->footer('Order System')
                ->footerIcon('https://example.com/icon.png')
                ->timestamp(time())
                ->action('View Order', $this->order->url, 'primary')
                ->action('Contact Customer', $this->order->customer_url);
        });
}

Testing

composer test
composer cs-check
composer stan

License

MIT License. See LICENSE file.