- 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
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 keyrecipient_id- User/entity receiving notificationtype- Notification categorytitle- Notification titlebody- Notification messagedata- JSON structured datachannels- JSON array of delivery channelspriority- Delivery prioritystatus- Current status (pending, sent, delivered, failed, read, archived)created_at- Creation timestampsent_at- Delivery timestampread_at- Read timestampaction_url- Optional action URLaction_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
- Use Async Delivery: Always prefer
sendLater()for better performance - Set Appropriate Priority: Reserve URGENT for critical notifications
- Include Actions: Add action URLs when user interaction is expected
- Structured Data: Use
withData()for additional context - Type Classification: Use proper NotificationType for filtering
- 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
- Implement
NotificationChannelInterface - Add channel to
NotificationChannelenum - Register channel in dispatcher initialization
- 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