A powerful Laravel package that extends Laravel's notification system with advanced features for Email, SMS (Taqnyat), and WhatsApp channels.
- Universal Channel Support: Works with any Laravel notification channel from laravel-notification-channels.com (100+ channels available)
- Built-in Channels: Email (Mail), SMS (Taqnyat), WhatsApp Business API
- Flexible Delivery Strategies: Send to all channels or use round-robin fallback
- Channel Priority/Ordering: Define the order in which channels are attempted
- Retry Logic: Automatic retry with exponential or linear backoff
- Rate Limiting: Prevent notification spam
- User Preferences: Allow users to choose their preferred channels
- Sandbox Mode: Test notifications without actually sending them
- Events System: Monitor notification lifecycle with dispatchable events
- Notification History: Log all sent notifications to database
- Channel Health Monitoring: Check channel configuration and API connectivity
- Conditional Channel Rules: Custom logic to determine when each channel should be used
- Template Management: Named WhatsApp templates with helper methods
- Localization Support: Multi-language notification support
- Testing Helpers: Sandbox assertions for easy testing
composer require softlandtech/notifyphp artisan vendor:publish --tag=notify-configIf you want to use notification history:
php artisan vendor:publish --tag=notify-migrations
php artisan migrateThe package is configured via config/notify.php and environment variables.
# Notification Strategy
NOTIFICATION_STRATEGY=all # or 'round_robin'
# Channels to use (comma-separated)
NOTIFICATION_CHANNELS=mail,sms,whatsapp
# Channel Order
NOTIFICATION_CHANNEL_ORDER=mail,sms,whatsapp
# Sandbox Mode
NOTIFICATION_SANDBOX=false
# Retry Configuration
NOTIFICATION_RETRY_ENABLED=false
NOTIFICATION_RETRY_ATTEMPTS=3
NOTIFICATION_RETRY_BACKOFF=exponential # or 'linear'
NOTIFICATION_RETRY_DELAY=1000
# Rate Limiting
NOTIFICATION_RATE_LIMIT_ENABLED=true
NOTIFICATION_RATE_LIMIT_MAX=5
NOTIFICATION_RATE_LIMIT_DECAY=1
# Notification History
NOTIFICATION_HISTORY_ENABLED=false
NOTIFICATION_HISTORY_PRUNE_DAYS=30
# User Preferences
NOTIFICATION_USER_PREFERENCES_ENABLED=false
# Events
NOTIFICATION_EVENTS_ENABLED=true
# Taqnyat SMS
TAQNYAT_BASE_URL=https://api.taqnyat.sa
TAQNYAT_TOKEN=your-token
TAQNYAT_SENDER=your-sender
# WhatsApp
WHATSAPP_API_VERSION=v22.0
WHATSAPP_PHONE_NUMBER_ID=your-phone-number-id
WHATSAPP_TOKEN=your-access-token
WHATSAPP_TEMPLATE_NAME=your-default-template
WHATSAPP_TEMPLATE_LANGUAGE=en_US
WHATSAPP_DEFAULT_LANGUAGE=ar
# WhatsApp Templates
WHATSAPP_TEMPLATE_OTP=otp_template
WHATSAPP_TEMPLATE_VERIFICATION=verification_template
WHATSAPP_TEMPLATE_PASSWORD_RESET=password_reset_templateAlso configure channels in config/notifications.php:
'channels' => array_filter(array_map('trim', explode(',', env('NOTIFICATION_CHANNELS', 'mail')))),
'channel_mapping' => [
'mail' => \SoftLandTech\Notify\Channels\MailChannel::class,
'sms' => \SoftLandTech\Notify\Channels\TaqnyatChannel::class,
'whatsapp' => \SoftLandTech\Notify\Channels\WhatsappChannel::class,
],Create a notification that extends the package's base class:
<?php
namespace App\Notifications;
use SoftLandTech\Notify\Notification;
use SoftLandTech\Notify\Contracts\MailNotification;
use SoftLandTech\Notify\Contracts\SmsNotification;
use SoftLandTech\Notify\Contracts\WhatsAppNotification;
class WelcomeNotification extends Notification implements MailNotification, SmsNotification, WhatsAppNotification
{
public function toMail(object $notifiable): array
{
return [
'view' => 'emails.welcome',
'subject' => 'Welcome to Our App',
'data' => ['user' => $notifiable],
];
}
public function toSms(object $notifiable): string
{
return 'Welcome to our app!';
}
public function toWhatsapp(object $notifiable): array
{
return [
'template_name' => 'welcome_template',
'language' => 'ar',
'parameters' => [$notifiable->name],
];
}
}Important: No need to define the via() method! The package handles channel routing automatically based on configuration.
$user->notify(new WelcomeNotification());NOTIFICATION_STRATEGY=allAll configured channels will receive the notification, regardless of success/failure.
NOTIFICATION_STRATEGY=round_robinChannels are tried in order. The first successful send stops the process.
You can also override per notification:
class UrgentNotification extends Notification
{
protected function getStrategy(): string
{
return 'round_robin'; // Try channels until one succeeds
}
}Define the order in which channels are attempted:
NOTIFICATION_CHANNEL_ORDER=whatsapp,sms,mailOr override per notification:
class SmsFirstNotification extends Notification
{
protected function getChannelOrder(): array
{
return ['sms', 'whatsapp', 'mail'];
}
}Enable automatic retries for failed notifications:
NOTIFICATION_RETRY_ENABLED=true
NOTIFICATION_RETRY_ATTEMPTS=3
NOTIFICATION_RETRY_BACKOFF=exponential # or 'linear'
NOTIFICATION_RETRY_DELAY=1000 # millisecondsOr customize per notification:
class ImportantNotification extends Notification
{
protected function shouldRetry(): bool
{
return true;
}
protected function getRetryConfig(): array
{
return [
'attempts' => 5,
'backoff' => 'exponential',
'delay' => 2000,
];
}
}Prevent notification spam:
NOTIFICATION_RATE_LIMIT_ENABLED=true
NOTIFICATION_RATE_LIMIT_MAX=5 # Max 5 notifications
NOTIFICATION_RATE_LIMIT_DECAY=1 # Per minuteOverride per notification:
class LimitedNotification extends Notification
{
protected function shouldRateLimit(): bool
{
return true;
}
protected function getRateLimitConfig(): array
{
return [
'max_attempts' => 3,
'decay_minutes' => 5,
];
}
}Allow users to control which channels they receive notifications on:
NOTIFICATION_USER_PREFERENCES_ENABLED=trueAdd a notification_preferences attribute to your User model:
// User model
protected $casts = [
'notification_preferences' => 'array',
];
// Set user preferences
$user->notification_preferences = ['mail', 'whatsapp'];
$user->save();Test notifications without sending them:
NOTIFICATION_SANDBOX=trueuse SoftLandTech\Notify\Channels\SandboxChannel;
// In tests
SandboxChannel::assertSent(WelcomeNotification::class);
SandboxChannel::assertSent(WelcomeNotification::class, function ($notification, $notifiable) use ($user) {
return $notifiable->id === $user->id;
});
SandboxChannel::assertNotSent(SpamNotification::class);
SandboxChannel::clear(); // Clear sandbox historyListen to notification events:
use SoftLandTech\Notify\Events\NotificationSending;
use SoftLandTech\Notify\Events\NotificationSent;
use SoftLandTech\Notify\Events\NotificationFailed;
use SoftLandTech\Notify\Events\ChannelSkipped;
use SoftLandTech\Notify\Events\RoundRobinFallback;
Event::listen(NotificationSent::class, function ($event) {
Log::info('Notification sent', [
'notification' => get_class($event->notification),
'channel' => $event->channel,
]);
});Enable database logging of all notifications:
NOTIFICATION_HISTORY_ENABLED=truephp artisan vendor:publish --tag=notify-migrations
php artisan migrateQuery notification history:
use SoftLandTech\Notify\Models\NotificationHistory;
// Get all sent notifications for a user
$history = NotificationHistory::where('notifiable_type', User::class)
->where('notifiable_id', $user->id)
->where('status', 'sent')
->get();
// Get failed notifications
$failed = NotificationHistory::where('status', 'failed')->get();Check if your notification channels are properly configured:
# Check all channels
php artisan notifications:check-health
# Check specific channel
php artisan notifications:check-health mail
php artisan notifications:check-health sms
php artisan notifications:check-health whatsappProgrammatically:
use SoftLandTech\Notify\ChannelHealthMonitor;
$monitor = app(ChannelHealthMonitor::class);
$results = $monitor->checkAll();
// ['mail' => [...], 'sms' => [...], 'whatsapp' => [...]]
$mailHealth = $monitor->checkMail();Implement custom logic to determine when each channel should be used:
class PremiumNotification extends Notification
{
protected function shouldUseChannel(string $channelName, object $notifiable): bool
{
// Only use WhatsApp for premium users
if ($channelName === 'whatsapp') {
return $notifiable->isPremium();
}
// Only send emails during business hours
if ($channelName === 'mail') {
return now()->hour >= 9 && now()->hour < 17;
}
return true;
}
}Use the template management trait for WhatsApp notifications:
use SoftLandTech\Notify\Notification;
use SoftLandTech\Notify\Concerns\HasTemplates;
use SoftLandTech\Notify\Contracts\WhatsAppNotification;
class OtpNotification extends Notification implements WhatsAppNotification
{
use HasTemplates;
public function __construct(public string $code) {}
public function toWhatsapp(object $notifiable): array
{
// Helper method for OTP templates
return $this->buildOtpTemplate($this->code, 5);
}
}Available template helpers:
$this->buildOtpTemplate($code, $expiryMinutes);
$this->buildVerificationTemplate($code);
$this->buildPasswordResetTemplate($code, $expiryMinutes);
$this->buildTemplate('custom_template', ['param1', 'param2'], 'ar');
$this->getTemplate('otp'); // Gets template name from configUse the localization trait for multi-language support:
use SoftLandTech\Notify\Notification;
use SoftLandTech\Notify\Concerns\Localizable;
use SoftLandTech\Notify\Contracts\MailNotification;
class LocalizedNotification extends Notification implements MailNotification
{
use Localizable;
public function toMail(object $notifiable): array
{
return $this->withLocale($notifiable, function () use ($notifiable) {
return [
'view' => 'emails.welcome',
'subject' => $this->trans('notifications.welcome.subject', [], $notifiable),
'data' => [
'message' => $this->trans('notifications.welcome.message', ['name' => $notifiable->name]),
],
];
});
}
}The trait will automatically detect the user's locale from:
$notifiable->localeattribute$notifiable->preferredLocale()method- Application default locale
The package supports any Laravel notification channel from laravel-notification-channels.com. All features (retry, rate limiting, sandbox mode, events, etc.) automatically work with any channel!
- Install the Slack channel:
composer require laravel/slack-notification-channel- Add to
config/notifications.php:
'channels' => array_filter(array_map('trim', explode(',', env('NOTIFICATION_CHANNELS', 'mail,slack')))),
'channel_mapping' => [
'mail' => \SoftLandTech\Notify\Channels\MailChannel::class,
'sms' => \SoftLandTech\Notify\Channels\TaqnyatChannel::class,
'whatsapp' => \SoftLandTech\Notify\Channels\WhatsappChannel::class,
'slack' => \Illuminate\Notifications\Slack\SlackChannel::class, // Add this
],- Update your
.env:
NOTIFICATION_CHANNELS=mail,slack
SLACK_WEBHOOK_URL=your-webhook-url- Use in notifications:
use SoftLandTech\Notify\Notification;
use SoftLandTech\Notify\Contracts\MailNotification;
use Illuminate\Notifications\Slack\SlackMessage;
class OrderShipped extends Notification implements MailNotification
{
public function toMail(object $notifiable): array
{
return [
'view' => 'emails.order-shipped',
'subject' => 'Your order has shipped!',
];
}
public function toSlack(object $notifiable): SlackMessage
{
return (new SlackMessage)
->content('Your order has shipped!');
}
}
// Add routing in your User model
public function routeNotificationFor(string $channel): mixed
{
return match($channel) {
'mail' => $this->email,
'slack' => env('SLACK_WEBHOOK_URL'),
'sms', 'whatsapp' => $this->phone,
default => null,
};
}That's it! Slack notifications now have retry logic, rate limiting, sandbox mode, and all other features.
composer require laravel-notification-channels/discord'discord' => \NotificationChannels\Discord\DiscordChannel::class,composer require laravel-notification-channels/telegram'telegram' => \NotificationChannels\Telegram\TelegramChannel::class,composer require laravel/vonage-notification-channel'vonage' => \Illuminate\Notifications\VonageChannel::class,composer require laravel-notification-channels/twilio'twilio' => \NotificationChannels\Twilio\TwilioChannel::class,composer require laravel-notification-channels/fcm'fcm' => \NotificationChannels\Fcm\FcmChannel::class,Visit laravel-notification-channels.com to see 100+ available channels including:
- Social: Facebook, Twitter, LinkedIn
- Messaging: Discord, Telegram, Slack, Microsoft Teams
- SMS: Twilio, Nexmo, Vonage, MessageBird
- Push: Firebase, OneSignal, Pusher
- And many more!
The package provides convenient facades:
use Taqnyat;
use Whatsapp;
// Send SMS via Taqnyat
Taqnyat::sendSms('966500000000', 'Your OTP is 123456');
// Send WhatsApp template
Whatsapp::sendTemplate('966500000000', [
'template_name' => 'otp',
'language' => 'ar',
'parameters' => ['123456', '5'],
]);vendor/bin/pestuse SoftLandTech\Notify\Channels\SandboxChannel;
class NotificationTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
config(['notify.sandbox' => true]);
SandboxChannel::clear();
}
public function test_welcome_notification_is_sent()
{
$user = User::factory()->create();
$user->notify(new WelcomeNotification());
SandboxChannel::assertSent(WelcomeNotification::class, function ($notification, $notifiable) use ($user) {
return $notifiable->id === $user->id;
});
}
}The package modifies the make:notification stub to automatically extend the package's base class:
php artisan make:notification MyNotificationThis will generate:
<?php
namespace App\Notifications;
use SoftLandTech\Notify\Notification;
use Illuminate\Bus\Queueable;
class MyNotification extends Notification
{
use Queueable;
// No via() method needed!
}| Event | Properties | Description |
|---|---|---|
NotificationSending |
$notification, $notifiable, $channel |
Fired before sending |
NotificationSent |
$notification, $notifiable, $channel, $response |
Fired after successful send |
NotificationFailed |
$notification, $notifiable, $channel, $exception |
Fired on failure |
ChannelSkipped |
$notification, $notifiable, $channel, $reason |
Fired when a channel is skipped |
RoundRobinFallback |
$notification, $notifiable, $failedChannel, $nextChannel, $exception |
Fired during round-robin fallback |
The package uses the Decorator pattern to layer features around any Laravel notification channel:
SandboxChannel (if enabled)
└─ RateLimitedChannel (if enabled)
└─ RetryableChannel (if enabled)
└─ Any Channel (MailChannel, SlackChannel, DiscordChannel, etc.)
This architecture ensures that:
- All features (retry, rate limiting, sandbox, events, history) work with any Laravel notification channel
- Features can be enabled/disabled independently
- New channels can be added instantly without code changes
- The decorator layers are applied automatically to all channels
Simply add any community channel to your channel_mapping config and all features will work immediately!
This package is proprietary software.
Created by Abdallah Isham