Cookbook

Notification

This comprehensive guide covers Glueful's notification system, including multi-channel delivery, email notifications, template management, and advanced features for enterprise-grade notification management.

Table of Contents

  1. Overview
  2. Architecture
  3. Core Components
  4. Email Notification Channel
  5. Multi-Channel Support
  6. API Endpoints
  7. Configuration
  8. Usage Examples
  9. Template System
  10. Advanced Features
  11. Error Handling
  12. Best Practices

Overview

The Glueful Notification System, introduced in v0.19.0, provides a comprehensive solution for managing user notifications across multiple channels. The system is designed for enterprise-grade applications requiring reliable, scalable, and flexible notification delivery.

Key Features

  • Multi-Channel Delivery: In-app, email, and extensible channel architecture
  • Template-Based Notifications: Reusable templates with variable substitution
  • Email Integration: Full-featured email notification system with multiple providers
  • Delivery Tracking: Read/unread status, delivery confirmation, and retry mechanisms
  • Queue Integration: Asynchronous notification processing with queue support
  • Event-Driven Architecture: Comprehensive event system for notification lifecycle
  • Advanced Configuration: Flexible configuration for different environments
  • Performance Optimization: Caching, batching, and efficient database operations

Architecture

The notification system follows a modular, event-driven architecture:

graph TD
    A[Notification Service] --> B[Event Dispatcher]
    B --> C[Email Notification Channel]
    B --> D[Database Notification Channel]
    B --> E[Custom Channels...]
    C --> F[Email Providers]
    F --> G[SMTP]
    F --> H[Sendgrid API]
    F --> I[Mailgun API]
    F --> J[Custom Providers]
    A --> K[Notification Repository]
    A --> L[Template Manager]
    A --> M[Queue System]

Core Components

  1. NotificationService: Central service for notification management
  2. NotificationRepository: Data access layer for notifications
  3. Event Dispatcher: Handles notification events and channel routing
  4. Template Manager: Manages notification templates and rendering
  5. Channel System: Pluggable delivery channels (email, database, etc.)
  6. Queue Integration: Asynchronous processing and retry mechanisms

Core Components

Notification Service

The NotificationService is the primary interface for notification management:

use Glueful\Notifications\Services\NotificationService;

$notificationService = container()->get(NotificationService::class);

// Send notification
$result = $notificationService->send(
    'user_welcome',                    // type
    $notifiableUser,                   // recipient (implements Notifiable)
    'Welcome to our platform!',       // subject
    ['username' => $user->name],       // data
    ['channels' => ['email', 'database']] // options
);

// Template-based notifications
$result = $notificationService->sendWithTemplate(
    'password_reset',
    $notifiableUser,
    'password-reset-template',
    ['reset_url' => $resetUrl],
    ['channels' => ['email']]
);

// Scheduled notifications
$notificationService->create(
    'subscription_reminder',
    $user,
    'Your subscription expires soon',
    ['expiry_date' => $expiryDate],
    ['channels' => ['email'], 'schedule' => new DateTime('+7 days')]
);

Notification Repository

Database operations and querying:

use Glueful\Repository\NotificationRepository;

$repository = container()->get(NotificationRepository::class);

// Get user notifications
// Fetch notifications for a notifiable (type + id)
$notifications = $repository->findForNotifiable('user', $userUuid);
// Count unread notifications
$unread = $repository->countForNotifiable('user', $userUuid, true);

// Query with filters
$recent = $repository->findForNotifiable('user', $userUuid, true, 10, 0, [
    'type' => 'order_update',
]);

// Mark notifications as read
// Mark single notification as read via service (after loading model)
// $notification = $repository->findByUuid($notificationUuid);
// $notificationService->markAsRead($notification);
// Mark all as read for a notifiable using service
// $notificationService->markAllAsRead($user);

Event System

The notification system dispatches events throughout the notification lifecycle:

// Available Events
use Glueful\Notifications\Events\NotificationSent;
use Glueful\Notifications\Events\NotificationFailed;
use Glueful\Notifications\Events\NotificationRead;

// Event Listeners
class NotificationEventListener
{
    public function onNotificationSent(NotificationSent $event): void
    {
        $notification = $event->getNotification();
        // Track successful delivery
    }

    public function onNotificationFailed(NotificationFailed $event): void
    {
        $notification = $event->getNotification();
        $reason = $event->getReason();
        // Handle delivery failure
    }

    public function onNotificationRead(NotificationRead $event): void
    {
        $notification = $event->getNotification();
        // Track read status
    }
}

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' => '[email protected]',
        'name' => 'Your App'
    ],
],

Mailgun API

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

Email Features

Basic Email Sending

// Simple email notification
$emailNotification = Extensions::get('email_notification');

$emailNotification->send([
    'to' => '[email protected]',
    'subject' => 'Welcome to Our Platform',
    'template' => 'welcome',
    'variables' => [
        'user_name' => 'John Doe',
        'verification_link' => 'https://example.com/verify/token123'
    ]
]);

Advanced Email Options

// Email with attachments and advanced options
$emailNotification->send([
    'to' => [
        ['email' => '[email protected]', 'name' => 'Recipient One'],
        ['email' => '[email protected]', 'name' => 'Recipient Two']
    ],
    'cc' => ['[email protected]'],
    'bcc' => ['[email protected]'],
    'subject' => 'Your Invoice #123',
    'template' => 'invoice',
    'variables' => [
        'invoice_number' => '123',
        'amount' => '$99.99',
        'due_date' => '2025-05-15'
    ],
    'attachments' => [
        [
            'path' => '/path/to/invoice.pdf',
            'name' => 'Invoice-123.pdf',
            'mime' => 'application/pdf'
        ]
    ],
    'provider' => 'sendgrid', // Override default provider
    'priority' => 'high',
    'track_opens' => true,
    'track_clicks' => true
]);

Batch Email Processing

// Send multiple emails efficiently
$emails = [
    [
        'to' => '[email protected]',
        'template' => 'welcome',
        'variables' => ['name' => 'User 1']
    ],
    [
        'to' => '[email protected]',
        'template' => 'welcome',
        'variables' => ['name' => 'User 2']
    ]
];

// Loop or implement an app-level helper to batch
foreach ($emails as $email) {
    $emailNotification->send($email);
}

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('notifications.channels.slack.enabled'); }
    public function getConfig(): array { return config('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('notifications.channels.slack.webhook_url');
        $response = http_client()->post($webhookUrl, ['json' => $data]);
        return $response->getStatusCode() === 200;
    }
}

API Endpoints

REST API

MethodEndpointDescription
GET/api/notificationsGet all notifications for authenticated user
GET/api/notifications/unreadGet unread notifications
GET/api/notifications/{uuid}Get a specific notification
POST/api/notifications/mark-read/{uuid}Mark a notification as read
POST/api/notifications/mark-all-readMark all notifications as read
DELETE/api/notifications/{uuid}Delete a notification
GET/api/notifications/preferencesGet user notification preferences
PUT/api/notifications/preferencesUpdate user notification preferences

API Usage Examples

// Get notifications with pagination
GET /api/notifications?page=1&limit=10&type=order_update

// Response
{
    "success": true,
    "data": {
        "notifications": [...],
        "pagination": {
            "current_page": 1,
            "per_page": 10,
            "total": 45,
            "total_pages": 5
        },
        "unread_count": 12
    }
}

// Mark notification as read
POST /api/notifications/mark-read/uuid-123
{
    "success": true,
    "message": "Notification marked as read"
}

// Update preferences
PUT /api/notifications/preferences
{
    "email_notifications": true,
    "types": {
        "order_updates": ["email", "database"],
        "promotions": ["database"],
        "security_alerts": ["email"]
    }
}

Configuration

Main Configuration

// config/notifications.php
return [
    'channels' => [
        'email' => [
            'enabled' => true,
            'driver' => 'smtp',
            'from' => [
                'address' => '[email protected]',
                'name' => 'Notification System'
            ],
            'template_path' => 'storage/templates/email',
            'queue' => 'email-notifications',
            'retry_attempts' => 3,
            'retry_delay' => 300,
        ],
        'database' => [
            'enabled' => true,
            'table' => 'notifications',
            'cleanup_after' => 90, // days
        ],
    ],
    'default_channels' => ['database'],
    'templates' => [
        'cache_enabled' => true,
        'cache_ttl' => 3600,
        'fallback_language' => 'en',
    ],
    'queue' => [
        'enabled' => true,
        'connection' => 'redis',
        'queue' => 'notifications',
        'batch_size' => 50,
    ],
    'retry' => [
        'max_attempts' => 3,
        'delay' => 300, // seconds
        'exponential_backoff' => true,
    ],
    'user_preferences' => [
        'enabled' => true,
        'defaults' => [
            'email_notifications' => true,
            'database_notifications' => true,
        ],
    ],
];

Email Extension Configuration

// Extension configuration in extensions.php
'extensions' => [
    'email_notification' => [
        'enabled' => true,
        'config' => [
            'default_provider' => 'smtp',
            'template_path' => 'storage/templates/email',
            'cache' => [
                'enabled' => true,
                'driver' => 'redis',
                'ttl' => 3600
            ],
            'tracking' => [
                'opens' => true,
                'clicks' => true,
                'bounces' => true,
            ],
        ]
    ]
]

Usage Examples

Basic Notification Sending

use Glueful\Notifications\Services\NotificationService;

class OrderController
{
    private NotificationService $notificationService;

    public function shipOrder(string $orderId): Response
    {
        $order = $this->orderService->ship($orderId);
        
        // Send shipping notification
        $this->notificationService->send(
            'order_shipped',
            $order->getUser(),
            'Your order has shipped!',
            [
                'order_id' => $order->getId(),
                'tracking_number' => $order->getTrackingNumber(),
                'estimated_delivery' => $order->getEstimatedDelivery()
            ],
            ['channels' => ['email', 'database']]
        );

        return $this->json(['success' => true]);
    }
}

Scheduled Notifications

// Schedule reminder notifications
$reminderDate = (new DateTime())->add(new DateInterval('P7D'));

$notificationService->create(
    'subscription_expiry_reminder',
    $user,
    'Your subscription expires soon',
    [
        'expiry_date' => $user->getSubscriptionExpiry()->format('Y-m-d'),
        'renewal_url' => $this->urlGenerator->generate('subscription_renew')
    ],
    [
        'channels' => ['email'],
        'schedule' => $reminderDate,
        'priority' => 'normal'
    ]
);

Bulk Notifications

// Send notifications to multiple users (loop or create an app helper)
$users = $this->userRepository->findActiveUsers();

foreach ($users as $user) {
    $notificationService->send(
        'feature_announcement',
        $user, // implements Notifiable
        'New Feature Available!',
        [
            'feature_name' => 'Advanced Analytics',
            'learn_more_url' => '/features/analytics'
        ],
        ['channels' => ['email', 'database']]
    );
}

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()->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']]
);

Advanced Features

Notification Retry System

use Glueful\Notifications\Services\NotificationRetryService;

$retryService = container()->get(NotificationRetryService::class);

// Queue a notification for retry after a failure
$retryService->queueForRetry($notification, $user /* Notifiable */, 'email');

// Process due retries (e.g., from a cron job)
$results = $retryService->processDueRetries(50, $notificationService);

// Configure retry behavior (example)
$retryService->setConfig('retry', [
    'max_attempts' => 5,
    'delay' => 300,
    'backoff' => 'exponential', // or 'linear'
]);

Notification Analytics

// Get delivery statistics
$analytics = $notificationService->getAnalytics([
    'start_date' => '2025-01-01',
    'end_date' => '2025-01-31',
    'channels' => ['email', 'database'],
    'types' => ['order_update', 'marketing']
]);

// Response includes:
// - Total notifications sent
// - Delivery rates by channel
// - Open rates (email)
// - Click rates (email)
// - Failure rates and reasons
// - Average delivery time

User Preferences Management

// Get user notification preferences
$preferences = $notificationService->getUserPreferences($userUuid);

// Update preferences
$notificationService->updateUserPreferences($userUuid, [
    'email_notifications' => true,
    'types' => [
        'order_updates' => ['email', 'database'],
        'promotions' => ['database'],
        'security_alerts' => ['email', 'database'],
        'newsletters' => []  // Disabled
    ],
    'quiet_hours' => [
        'enabled' => true,
        'start' => '22:00',
        'end' => '08:00',
        'timezone' => 'America/New_York'
    ]
]);

Notification Queuing

// Queue notifications for batch processing
$notificationService->queue([
    'type' => 'weekly_digest',
    'recipients' => $activeUsers,
    'template' => 'weekly-digest',
    'data' => $digestData,
    'options' => [
        'channels' => ['email'],
        'priority' => 'low',
        'batch_size' => 100,
        'delay' => 3600 // Send in 1 hour
    ]
]);

Error Handling

Exception Types

use Glueful\Notifications\Exceptions\NotificationException;
use Glueful\Notifications\Exceptions\TemplateNotFoundException;
use Glueful\Notifications\Exceptions\ChannelNotFoundException;
use Glueful\Notifications\Exceptions\DeliveryException;

try {
    $notificationService->send($type, $user, $subject, $data);
} catch (TemplateNotFoundException $e) {
    // Handle missing template
    $this->logger->error('Template not found', [
        'template' => $e->getTemplateName(),
        'type' => $type
    ]);
} catch (ChannelNotFoundException $e) {
    // Handle invalid channel
    $this->logger->error('Channel not found', [
        'channel' => $e->getChannelName()
    ]);
} catch (DeliveryException $e) {
    // Handle delivery failure
    $this->logger->error('Delivery failed', [
        'channel' => $e->getChannel(),
        'reason' => $e->getMessage(),
        'retryable' => $e->isRetryable()
    ]);
    
    if ($e->isRetryable()) {
        $retryService->scheduleRetry($e->getNotification());
    }
} catch (NotificationException $e) {
    // Handle general notification error
    $this->logger->error('Notification error', [
        'error' => $e->getMessage(),
        'type' => $type
    ]);
}

Delivery Status Tracking

// Check delivery status
$status = $notificationService->getDeliveryStatus($notificationUuid);

// Status includes:
// - sent_at: When notification was sent
// - delivered_at: When notification was delivered
// - read_at: When notification was read (if applicable)
// - failed_at: When delivery failed
// - retry_count: Number of retry attempts
// - last_error: Last error message
// - channel_results: Results from each channel

Best Practices

1. Notification Type Naming

Use descriptive, hierarchical naming conventions:

// Good examples
'user.welcome'
'user.password_reset'
'order.created'
'order.shipped'
'order.delivered'
'system.maintenance'
'security.login_alert'

// Avoid generic names
'notification'
'alert'
'message'

2. Template Organization

Organize templates by category and maintain consistency:

storage/templates/email/
├── user/
│   ├── welcome.html
│   ├── welcome.txt
│   ├── password-reset.html
│   └── password-reset.txt
├── order/
│   ├── confirmation.html
│   ├── confirmation.txt
│   ├── shipped.html
│   └── shipped.txt
└── system/
    ├── maintenance.html
    └── maintenance.txt

3. Performance Optimization

// Use queues for bulk notifications
// Implement queuing via your app/job system if needed

// Cache frequently used templates
$templateManager->enableCache(3600);

// Batch database operations
$repository->markMultipleAsRead($notificationUuids);

// Use appropriate indexes
// Database schema should include indexes on:
// - user_uuid, created_at
// - type, created_at
// - read_at (for unread queries)

4. Error Handling and Monitoring

// Implement comprehensive error handling
class NotificationErrorHandler
{
    public function handleDeliveryFailure(DeliveryException $e): void
    {
        // Log error with context
        $this->logger->error('Notification delivery failed', [
            'notification_id' => $e->getNotificationId(),
            'channel' => $e->getChannel(),
            'error' => $e->getMessage(),
            'user_id' => $e->getUserId(),
            'retryable' => $e->isRetryable()
        ]);

        // Schedule retry if appropriate
        if ($e->isRetryable() && $e->getAttempts() < 3) {
            $this->retryService->scheduleRetry($e->getNotification());
        }

        // Alert monitoring systems for critical failures
        if ($e->isCritical()) {
            $this->alertingService->sendAlert('notification_failure', $e);
        }
    }
}

5. User Experience

// Respect user preferences
$channels = $this->getEffectiveChannels($user, $notificationType);

// Implement quiet hours
if ($this->isQuietHours($user)) {
    $options['delay'] = $this->calculateDelayUntilActiveHours($user);
}

// Provide unsubscribe mechanisms
$emailData['unsubscribe_url'] = $this->generateUnsubscribeUrl($user, $type);

// Track user engagement
$this->analyticsService->trackNotificationEngagement($notification, $user);

6. Testing

// Use test channels in development
if (config('app.env') === 'testing') {
    $notificationService->setDefaultChannels(['test']);
}

// Mock external services
class MockEmailProvider implements EmailProviderInterface
{
    public function send(EmailMessage $message): bool
    {
        // Store in test database instead of sending
        $this->testStorage->store($message);
        return true;
    }
}

// Test notification flows
class NotificationTest extends TestCase
{
    public function testUserWelcomeNotification(): void
    {
        $user = $this->createTestUser();
        
        $this->notificationService->send('user.welcome', $user, 'Welcome!', []);
        
        $this->assertDatabaseHas('notifications', [
            'user_uuid' => $user->getUuid(),
            'type' => 'user.welcome'
        ]);
    }
}

This comprehensive notification system provides enterprise-grade features for reliable, scalable notification delivery across multiple channels while maintaining flexibility and ease of use.