Notification Channels & Templates
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']]
);