Files
michaelschiemer/src/Framework/Notification/README.md
Michael Schiemer 5050c7d73a docs: consolidate documentation into organized structure
- Move 12 markdown files from root to docs/ subdirectories
- Organize documentation by category:
  • docs/troubleshooting/ (1 file)  - Technical troubleshooting guides
  • docs/deployment/      (4 files) - Deployment and security documentation
  • docs/guides/          (3 files) - Feature-specific guides
  • docs/planning/        (4 files) - Planning and improvement proposals

Root directory cleanup:
- Reduced from 16 to 4 markdown files in root
- Only essential project files remain:
  • CLAUDE.md (AI instructions)
  • README.md (Main project readme)
  • CLEANUP_PLAN.md (Current cleanup plan)
  • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements)

This improves:
 Documentation discoverability
 Logical organization by purpose
 Clean root directory
 Better maintainability
2025-10-05 11:05:04 +02:00

11 KiB

Notification System

Multi-channel notification system with support for email, database, push, SMS, and webhooks.

Features

  • Multi-Channel Delivery: Email, Database (in-app), Push, SMS, Webhook
  • Queue Integration: Async delivery via framework Queue system
  • Event System: NotificationSent and NotificationFailed events
  • Priority Levels: LOW, NORMAL, HIGH, URGENT
  • Read Tracking: Mark notifications as read, count unread
  • Action Buttons: Optional action URL and label
  • Type Safety: Fully typed with Value Objects and Enums
  • Framework Integration: Uses existing Mail, Queue, EventBus, Database modules

Basic Usage

Creating and Sending a Notification

use App\Framework\Notification\Notification;
use App\Framework\Notification\NotificationDispatcher;
use App\Framework\Notification\ValueObjects\NotificationChannel;
use App\Framework\Notification\ValueObjects\NotificationPriority;
use App\Framework\Notification\ValueObjects\NotificationType;

// Create notification
$notification = Notification::create(
    recipientId: 'user-123',
    type: NotificationType::system(),
    title: 'System Update',
    body: 'Your system has been updated to version 2.0',
    NotificationChannel::DATABASE,
    NotificationChannel::EMAIL
);

// Add optional features
$notification = $notification
    ->withPriority(NotificationPriority::HIGH)
    ->withAction('/changelog', 'View Changelog')
    ->withData([
        'version' => '2.0.0',
        'features' => ['performance', 'security']
    ]);

// Send asynchronously (via Queue)
$dispatcher->sendLater($notification);

// Or send immediately
$result = $dispatcher->sendNow($notification);

Common Notification Types

// System notifications
NotificationType::system()

// Security alerts
NotificationType::security()

// Marketing messages
NotificationType::marketing()

// Social interactions
NotificationType::social()

// Transactional emails
NotificationType::transactional()

// Custom types
NotificationType::fromString('order-status')

Retrieving Notifications

use App\Framework\Notification\Storage\NotificationRepository;

// Get user's notifications
$notifications = $repository->findByUser('user-123', limit: 20);

// Get unread notifications
$unread = $repository->findUnreadByUser('user-123');

// Count unread
$count = $repository->countUnreadByUser('user-123');

// Mark as read
$repository->markAsRead($notificationId);

// Mark all as read
$repository->markAllAsReadForUser('user-123');

Channels

Database Channel (In-App Notifications)

Stores notifications in database for user inbox/notification center.

NotificationChannel::DATABASE

Features:

  • Persistent storage
  • Read/unread tracking
  • Pagination support
  • Automatic cleanup of old notifications

Email Channel

Sends notifications via email using framework's Mail module.

NotificationChannel::EMAIL

Features:

  • HTML and plain text emails
  • Priority mapping
  • Action buttons in email
  • Automatic HTML formatting

Requirements:

  • UserEmailResolver implementation to map user IDs to email addresses
class DatabaseUserEmailResolver implements UserEmailResolver
{
    public function resolveEmail(string $userId): ?Email
    {
        // Lookup user email from database
        return $this->userRepository->findEmailById($userId);
    }
}

Push Channel (Placeholder)

For web push and mobile push notifications.

NotificationChannel::PUSH

Status: Interface defined, implementation needed.

SMS Channel (Placeholder)

For SMS notifications via external provider.

NotificationChannel::SMS

Status: Interface defined, implementation needed.

Webhook Channel (Placeholder)

For sending notifications to external systems via HTTP webhooks.

NotificationChannel::WEBHOOK

Status: Interface defined, implementation needed.

Queue Integration

The notification system integrates seamlessly with the framework's Queue system for asynchronous delivery.

Async Delivery

// Queue for background processing
$dispatcher->sendLater($notification);

// Priority is mapped from notification priority:
// URGENT → HIGH
// HIGH → MEDIUM
// NORMAL → LOW
// LOW → LOW

Immediate Delivery

// Send immediately (blocks until complete)
$result = $dispatcher->sendNow($notification);

if ($result->isSuccess()) {
    echo "Sent via: " . count($result->getSuccessful()) . " channels\n";
} else {
    echo "Errors: " . implode(', ', $result->getErrors()) . "\n";
}

Event System

The notification system dispatches events via the framework's EventBus.

NotificationSent Event

Dispatched when a notification is successfully sent via at least one channel.

use App\Framework\Notification\Events\NotificationSent;
use App\Framework\EventBus\Attributes\EventHandler;

#[EventHandler]
final class NotificationLogger
{
    public function handleNotificationSent(NotificationSent $event): void
    {
        $this->logger->info('Notification sent', [
            'notification_id' => $event->notification->id->toString(),
            'recipient' => $event->notification->recipientId,
            'channels' => count($event->result->getSuccessful())
        ]);
    }
}

NotificationFailed Event

Dispatched when a notification fails on all channels.

use App\Framework\Notification\Events\NotificationFailed;
use App\Framework\EventBus\Attributes\EventHandler;

#[EventHandler]
final class NotificationFailureHandler
{
    public function handleNotificationFailure(NotificationFailed $event): void
    {
        $this->alerting->sendAlert(
            'Notification delivery failed',
            $event->result->getErrors()
        );
    }
}

Database Schema

The notifications table is created via migration:

src/Framework/Notification/Migrations/CreateNotificationsTable.php

Table Structure:

  • id (ULID) - Primary key
  • recipient_id - User/entity receiving notification
  • type - Notification category
  • title - Notification title
  • body - Notification message
  • data - JSON structured data
  • channels - JSON array of delivery channels
  • priority - Delivery priority
  • status - Current status (pending, sent, delivered, failed, read, archived)
  • created_at - Creation timestamp
  • sent_at - Delivery timestamp
  • read_at - Read timestamp
  • action_url - Optional action URL
  • action_label - Optional action button label

Indexes:

  • recipient_id + status + created_at (composite)
  • recipient_id + type (composite)
  • read_at (for unread queries)

Configuration

Setting Up Channels

// In your Initializer
use App\Framework\Notification\Channels\DatabaseChannel;
use App\Framework\Notification\Channels\EmailChannel;
use App\Framework\Notification\NotificationDispatcher;

$container->singleton(NotificationDispatcher::class, function($c) {
    return new NotificationDispatcher(
        channels: [
            $c->get(DatabaseChannel::class),
            $c->get(EmailChannel::class),
            // Add more channels as needed
        ],
        queue: $c->get(Queue::class),
        eventBus: $c->get(EventBus::class)
    );
});

Email Channel Setup

$container->singleton(EmailChannel::class, function($c) {
    return new EmailChannel(
        mailer: $c->get(MailerInterface::class),
        fromAddress: new Email('notifications@example.com'),
        userEmailResolver: $c->get(UserEmailResolver::class)
    );
});

Best Practices

  1. Use Async Delivery: Always prefer sendLater() for better performance
  2. Set Appropriate Priority: Reserve URGENT for critical notifications
  3. Include Actions: Add action URLs when user interaction is expected
  4. Structured Data: Use withData() for additional context
  5. Type Classification: Use proper NotificationType for filtering
  6. Cleanup Old Notifications: Periodically delete old read/archived notifications

Examples

Welcome Notification

$notification = Notification::create(
    recipientId: $user->id,
    type: NotificationType::system(),
    title: 'Welcome to Our Platform!',
    body: 'Thank you for signing up. Get started by completing your profile.',
    NotificationChannel::DATABASE,
    NotificationChannel::EMAIL
)->withAction('/profile/complete', 'Complete Profile');

$dispatcher->sendLater($notification);

Security Alert

$notification = Notification::create(
    recipientId: $user->id,
    type: NotificationType::security(),
    title: 'New Login Detected',
    body: "We detected a login from {$location} at {$time}",
    NotificationChannel::DATABASE,
    NotificationChannel::EMAIL
)
->withPriority(NotificationPriority::HIGH)
->withData([
    'ip_address' => $ipAddress,
    'location' => $location,
    'device' => $device
]);

$dispatcher->sendNow($notification); // Immediate for security

Order Confirmation

$notification = Notification::create(
    recipientId: $order->userId,
    type: NotificationType::transactional(),
    title: 'Order Confirmed',
    body: "Your order #{$order->number} has been confirmed",
    NotificationChannel::EMAIL
)
->withData([
    'order_id' => $order->id,
    'total' => $order->total->toDecimal(),
    'items' => count($order->items)
])
->withAction("/orders/{$order->id}", 'View Order');

$dispatcher->sendLater($notification);

Testing

Run tests with:

./vendor/bin/pest tests/Feature/NotificationSystemTest.php

Extending the System

Adding a New Channel

  1. Implement NotificationChannelInterface
  2. Add channel to NotificationChannel enum
  3. Register channel in dispatcher initialization
  4. Implement send() method with delivery logic
final readonly class SmsChannel implements NotificationChannelInterface
{
    public function __construct(
        private SmsProvider $provider
    ) {}

    public function send(Notification $notification): ChannelResult
    {
        // Implementation
    }

    public function supports(Notification $notification): bool
    {
        return $notification->supportsChannel(NotificationChannel::SMS);
    }

    public function getChannel(): NotificationChannel
    {
        return NotificationChannel::SMS;
    }
}

Architecture

The notification system follows framework principles:

  • Readonly Value Objects: Notification, NotificationId, etc.
  • Final Classes: All implementation classes are final
  • Composition over Inheritance: Channel interface composition
  • Event-Driven: Integration with EventBus
  • Queue Integration: Async delivery via Queue system
  • Module Reuse: Leverages Mail, Queue, EventBus, Database modules

Future Enhancements

  • User preference management for notification types
  • Notification templates with variables
  • Push notification implementation (Web Push API)
  • SMS channel implementation
  • Webhook channel implementation
  • Notification batching/digest mode
  • Quiet hours / Do Not Disturb
  • A/B testing for notification content
  • Analytics tracking