Cookbook

Notification Channels & Templates

Channel internals — email providers, multi-channel selection, custom channels, and the template system.

This page covers the notification system's channel and template internals: email provider configuration, multi-channel selection, writing custom channels, and the template system.

Email Notification Channel

The email notification channel provides comprehensive email delivery capabilities.

Email Providers

Multiple email providers are supported:

SMTP Configuration

// config/mail.php
return [
    'providers' => [
        'smtp' => [
            'host' => 'smtp.example.com',
            'port' => 587,
            'encryption' => 'tls',
            'username' => 'your-username',
            'password' => 'your-password',
            'timeout' => 30,
        ],
    ],
    'default' => 'smtp',
];

Sendgrid API

'sendgrid' => [
    'api_key' => 'your-sendgrid-api-key',
    'endpoint' => 'https://api.sendgrid.com/v3/mail/send',
    'from' => [
        'address' => 'noreply@example.com',
        'name' => 'Your App'
    ],
],

Mailgun API

'mailgun' => [
    'api_key' => 'your-mailgun-api-key',
    'domain' => 'mail.yourdomain.com',
    'endpoint' => 'https://api.mailgun.net/v3/',
],

Sending Email Notifications

Email is delivered through the standard notification flow — resolve NotificationService and call send(). Email-specific fields (template, variables, cc/bcc, attachments, provider) travel in the notification data array and are read by the email channel; the provider settings above (SMTP/Mailgun/SendGrid) configure how it delivers. See Features → Notifications for the canonical send() signature and a Notifiable example.

Multi-Channel Support

Channel Priority and Selection

The notification system supports intelligent channel selection:

// Channel selection priority:
// 1. Explicit channels in options
// 2. User preferences
// 3. Default configuration

$notificationService->send(
    'order_shipped',
    $user,
    'Your order has shipped',
    ['tracking_number' => 'ABC123'],
    [
        'channels' => ['email', 'database'], // Explicit channels
        'priority' => 'high'                 // High priority notifications
    ]
);

Channel Configuration

// config/notifications.php
return [
    'channels' => [
        'email' => [
            'enabled' => true,
            'driver' => 'smtp',
            'queue' => 'email-notifications',
            'retry_attempts' => 3,
            'retry_delay' => 300, // seconds
        ],
        'database' => [
            'enabled' => true,
            'table' => 'notifications',
        ],
        'slack' => [
            'enabled' => false,
            'webhook_url' => 'https://hooks.slack.com/...',
        ],
    ],
    'default_channels' => ['database'],
    'user_preferences' => [
        'enabled' => true,
        'table' => 'user_notification_preferences',
    ],
];

Custom Channels

Create custom notification channels:

use Glueful\Notifications\Contracts\NotificationChannel;
use Glueful\Notifications\Contracts\Notifiable;

class SlackChannel implements NotificationChannel
{
    public function getChannelName(): string { return 'slack'; }
    public function isAvailable(): bool { return (bool) config($context, 'notifications.channels.slack.enabled'); }
    public function getConfig(): array { return config($context, 'notifications.channels.slack') ?? []; }

    public function format(array $data, Notifiable $notifiable): array
    {
        return [
            'text' => $data['subject'] ?? '',
            'attachments' => [
                [
                    'color' => 'good',
                    'fields' => [
                        [
                            'title' => 'Message',
                            'value' => $data['content'] ?? '',
                            'short' => false
                        ]
                    ]
                ]
            ]
        ];
    }

    public function send(Notifiable $notifiable, array $data): bool
    {
        $webhookUrl = config($context, 'notifications.channels.slack.webhook_url');
        $response = http_client()->post($webhookUrl, ['json' => $data]);
        return $response->getStatusCode() === 200;
    }
}

Registering a Custom Channel

Register the channel from your extension's ServiceProvider::boot() using the framework helper. This resolves the shared ChannelManager and is the supported wiring path — since the framework no longer hardcodes channel providers, a channel that isn't registered here won't be reachable by the async dispatcher used for queued/retried notifications:

use Glueful\Bootstrap\ApplicationContext;
use Glueful\Extensions\ServiceProvider;

final class SlackServiceProvider extends ServiceProvider
{
    public function boot(ApplicationContext $context): void
    {
        // Idempotent for the same class; throws ChannelAlreadyRegisteredException
        // if a different class tries to claim an already-registered name.
        $this->registerNotificationChannel($this->app->get(SlackChannel::class));
    }
}

Structured Results (RichNotificationChannel)

NotificationChannel::send() returns a bare bool. To surface richer per-send telemetry — a provider message id, error code/message, retryability, and latency — implement Glueful\Notifications\Contracts\RichNotificationChannel and return a NotificationResult from sendNotification(). The dispatcher prefers sendNotification() when a channel implements it and falls back to adapting send(): bool otherwise, so this is purely additive:

use Glueful\Notifications\Contracts\Notifiable;
use Glueful\Notifications\Contracts\RichNotificationChannel;
use Glueful\Notifications\Results\NotificationResult;

class SlackChannel implements RichNotificationChannel
{
    // ... getChannelName(), isAvailable(), getConfig(), format() as above ...

    public function send(Notifiable $notifiable, array $data): bool
    {
        // Keep the legacy contract by delegating to the structured path.
        return $this->sendNotification($notifiable, $data)->success;
    }

    public function sendNotification(Notifiable $notifiable, array $data): NotificationResult
    {
        $webhookUrl = config($context, 'notifications.channels.slack.webhook_url');
        $start = microtime(true);
        $response = http_client()->post($webhookUrl, ['json' => $this->format($data, $notifiable)]);
        $latencyMs = (int) round((microtime(true) - $start) * 1000);

        if ($response->getStatusCode() === 200) {
            return NotificationResult::success(latencyMs: $latencyMs);
        }

        return NotificationResult::failure(
            errorCode: 'http_' . $response->getStatusCode(),
            errorMessage: 'Slack webhook rejected the message.',
            retryable: $response->getStatusCode() >= 500,
            latencyMs: $latencyMs,
        );
    }
}

Template System

Email Templates

Templates support HTML and plain text versions with variable substitution:

HTML Template (welcome.html)

<!DOCTYPE html>
<html>
<head>
    <title>Welcome to {{app_name}}</title>
    <style>
        .container { max-width: 600px; margin: 0 auto; }
        .header { background: #007bff; color: white; padding: 20px; }
        .content { padding: 20px; }
        .button { 
            display: inline-block; 
            background: #28a745; 
            color: white; 
            padding: 10px 20px; 
            text-decoration: none; 
            border-radius: 5px; 
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>Welcome, {{user_name}}!</h1>
        </div>
        <div class="content">
            <p>Thank you for joining {{app_name}}. We're excited to have you on board!</p>
            <p>To get started, please verify your account:</p>
            <p><a href="{{verification_link}}" class="button">Verify Account</a></p>
            <p>If you have any questions, don't hesitate to contact our support team.</p>
            <p>Best regards,<br>The {{app_name}} Team</p>
        </div>
    </div>
</body>
</html>

Plain Text Template (welcome.txt)

Welcome to {{app_name}}, {{user_name}}!

Thank you for joining {{app_name}}. We're excited to have you on board!

To get started, please verify your account by visiting:
{{verification_link}}

If you have any questions, don't hesitate to contact our support team.

Best regards,
The {{app_name}} Team

Template Management

use Glueful\Notifications\Templates\TemplateManager;

$templateManager = container($context)->get(TemplateManager::class);

// Render template with variables
$rendered = $templateManager->renderTemplate('user_welcome', 'welcome', 'email', [
    'user_name' => 'John Doe',
    'app_name' => 'Glueful App',
    'verification_link' => 'https://example.com/verify/abc123'
]);

// Get a specific template
$template = $templateManager->getTemplate('user_welcome', 'welcome', 'email');

// List all templates
$templates = $templateManager->getAllTemplates();

Dynamic Templates

// Register templates dynamically, then send
$templateManager->createTemplate(
    id: 'dynamic_html',
    type: 'dynamic_alert',
    name: 'dynamic-template',
    channel: 'email',
    content: '<h1>{{alert_type}} Alert</h1><p>{{message}}</p>'
);
$templateManager->createTemplate(
    id: 'dynamic_text',
    type: 'dynamic_alert',
    name: 'dynamic-template',
    channel: 'email_text',
    content: '{{alert_type}} ALERT: {{message}}'
);

$notificationService->sendWithTemplate(
    'dynamic_alert',
    $user,
    'dynamic-template',
    ['alert_type' => 'security', 'message' => 'Suspicious login detected'],
    ['channels' => ['email']]
);