ratoufa / laravel-messaging
Multi-channel messaging for Laravel (SMS via AfrikSMS, WhatsApp via Twilio)
Fund package maintenance!
Ratoufa
Installs: 2
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/ratoufa/laravel-messaging
Requires
- php: ^8.4
- illuminate/contracts: ^11.0||^12.0
- spatie/laravel-package-tools: ^1.16
- twilio/sdk: ^8.10
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.26
- nunomaduro/collision: ^8.8
- orchestra/testbench: ^10.0.0||^9.0.0
- peckphp/peck: ^0.2.0
- pestphp/pest: ^4.2
- pestphp/pest-plugin-arch: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
- pestphp/pest-plugin-type-coverage: ^4.0
- phpstan/extension-installer: ^1.4
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- rector/rector: ^2.2
- spatie/laravel-ray: ^1.35
This package is not auto-updated.
Last update: 2025-12-26 06:25:54 UTC
README
A multi-channel messaging package for Laravel supporting SMS (via AfrikSMS) and WhatsApp (via Twilio). Features a fluent API, OTP verification, Laravel Notifications integration, and extensible gateway system.
Requirements
- PHP 8.4+
- Laravel 11.x or 12.x
Installation
Install the package via Composer:
composer require ratoufa/laravel-messaging
Publish the configuration file:
php artisan vendor:publish --tag="messaging-config"
Configuration
Add the following environment variables to your .env file:
# Default channel (sms or whatsapp) MESSAGING_DEFAULT_CHANNEL=sms # AfrikSMS credentials AFRIKSMS_CLIENT_ID=your-client-id AFRIKSMS_API_KEY=your-api-key AFRIKSMS_SENDER_ID=MyApp # Twilio WhatsApp credentials TWILIO_SID=your-account-sid TWILIO_AUTH_TOKEN=your-auth-token TWILIO_WHATSAPP_FROM=whatsapp:+15551234567 # OTP settings (optional) MESSAGING_OTP_LENGTH=6 MESSAGING_OTP_EXPIRY=10 MESSAGING_OTP_MAX_ATTEMPTS=3 # Phone formatting (optional) MESSAGING_DEFAULT_COUNTRY_CODE=228
Usage
SMS
Send a single SMS
use Ratoufa\Messaging\Facades\Sms; // Fluent API $response = Sms::to('22890123456')->send('Hello World!'); // With custom sender ID $response = Sms::to('22890123456') ->from('MyBrand') ->send('Hello World!'); // Using a message object use Ratoufa\Messaging\Data\SmsMessage; $message = new SmsMessage( recipient: '22890123456', content: 'Hello World!', senderId: 'MyBrand', ); $response = Sms::send($message);
Send bulk SMS
use Ratoufa\Messaging\Facades\Sms; // Fluent API - same message to multiple recipients $response = Sms::toMany(['22890123456', '22891234567']) ->send('Bulk message to all'); // Using a message object use Ratoufa\Messaging\Data\BulkMessage; $message = new BulkMessage( recipients: ['22890123456', '22891234567'], content: 'Bulk message to all', senderId: 'MyBrand', ); $response = Sms::sendBulk($message);
Send personalized SMS
use Ratoufa\Messaging\Facades\Sms; use Ratoufa\Messaging\Data\PersonalizedMessage; $messages = [ new PersonalizedMessage('22890123456', 'Hello John, your code is 1234'), new PersonalizedMessage('22891234567', 'Hello Jane, your code is 5678'), ]; $response = Sms::sendPersonalized($messages);
Check balance
use Ratoufa\Messaging\Facades\Sms; $balances = Sms::getBalance(); foreach ($balances as $balance) { echo "{$balance->country}: {$balance->balance} credits"; }
Configure delivery callback
use Ratoufa\Messaging\Facades\Sms; // POST callback (default) $response = Sms::configureCallback('https://example.com/webhook/sms'); // GET callback $response = Sms::configureCallback('https://example.com/webhook/sms', 'GET');
Send a message
use Ratoufa\Messaging\Facades\WhatsApp; // Simple message $response = WhatsApp::to('22890123456')->send('Hello via WhatsApp!'); // Using a message object use Ratoufa\Messaging\Data\SmsMessage; $message = new SmsMessage( recipient: '22890123456', content: 'Hello via WhatsApp!', ); $response = WhatsApp::send($message);
Send a template message
use Ratoufa\Messaging\Facades\WhatsApp; $response = WhatsApp::sendTemplate( recipient: '22890123456', contentSid: 'HXb5a34a7e18eb123456789', variables: ['1' => 'John', '2' => 'Order #12345'], );
Send media
use Ratoufa\Messaging\Facades\WhatsApp; // Image with caption $response = WhatsApp::sendMedia( recipient: '22890123456', mediaUrl: 'https://example.com/image.jpg', caption: 'Check this out!', ); // Document without caption $response = WhatsApp::sendMedia( recipient: '22890123456', mediaUrl: 'https://example.com/document.pdf', );
Using the fluent API with templates
Note: WhatsApp has a 24-hour messaging window. You can send freeform messages only within 24 hours after the user's last reply. After that, you must use a pre-approved Message Template.
use Ratoufa\Messaging\Facades\WhatsApp; // Fluent template with variables $response = WhatsApp::to('22890123456') ->template('HXb5a34a7e18eb123456789', ['1' => 'John', '2' => 'Order #12345']) ->send(); // Template without variables $response = WhatsApp::to('22890123456') ->template('HXb5a34a7e18eb123456789') ->send(); // Fluent media with caption $response = WhatsApp::to('22890123456') ->media('https://example.com/image.jpg') ->send('Check this out!');
OTP Verification
Send OTP
use Ratoufa\Messaging\Facades\Otp; $result = Otp::send('22890123456'); if ($result->success) { echo "OTP sent, expires at: {$result->expiresAt}"; } // With custom purpose $result = Otp::send('22890123456', 'password-reset');
Verify OTP
use Ratoufa\Messaging\Facades\Otp; $isValid = Otp::verify('22890123456', '123456'); if ($isValid) { echo "OTP verified successfully!"; } // With custom purpose $isValid = Otp::verify('22890123456', '123456', 'password-reset');
Resend OTP
use Ratoufa\Messaging\Facades\Otp; $result = Otp::resend('22890123456');
Check remaining attempts
use Ratoufa\Messaging\Facades\Otp; $attempts = Otp::remainingAttempts('22890123456'); echo "Remaining attempts: {$attempts}";
Invalidate OTP
use Ratoufa\Messaging\Facades\Otp; Otp::invalidate('22890123456');
Send OTP via WhatsApp
Note: WhatsApp OTP uses a pre-approved Message Template to ensure delivery even outside the 24-hour messaging window. You must configure the template SID in your
.envfile.
TWILIO_OTP_TEMPLATE_SID=HXxxxxxxxxxxxxxxxxx TWILIO_OTP_CODE_VARIABLE=1
use Ratoufa\Messaging\Facades\Otp; // Send OTP via WhatsApp (uses template) $result = Otp::whatsapp()->send('22890123456'); // Verify OTP (same as SMS) $isValid = Otp::whatsapp()->verify('22890123456', '123456'); // Resend OTP via WhatsApp $result = Otp::whatsapp()->resend('22890123456');
Using the Messaging Facade
The Messaging facade provides access to all channels:
use Ratoufa\Messaging\Facades\Messaging; // SMS Messaging::sms()->to('22890123456')->send('Hello!'); // WhatsApp Messaging::whatsapp()->to('22890123456')->send('Hello!'); // OTP Messaging::otp()->send('22890123456'); // Dynamic channel selection Messaging::channel('sms')->to('22890123456')->send('Hello!');
Response Handling
All send operations return a Response object:
use Ratoufa\Messaging\Facades\Sms; use Ratoufa\Messaging\Enums\ResponseCode; $response = Sms::to('22890123456')->send('Hello!'); // Check success if ($response->success) { echo "Message sent! ID: {$response->resourceId}"; } // Check specific error codes if ($response->code === ResponseCode::INSUFFICIENT_BALANCE) { echo "Please recharge your account"; } // Available response codes ResponseCode::SUCCESS // 100 ResponseCode::INVALID_CREDENTIALS // 401 ResponseCode::INSUFFICIENT_BALANCE // 402 ResponseCode::INVALID_RECIPIENT // 422 ResponseCode::TEMPLATE_REQUIRED // 463 (WhatsApp 24h window expired) ResponseCode::SERVER_ERROR // 500
Laravel Notifications
SMS Channel
use Illuminate\Notifications\Notification; use Ratoufa\Messaging\Data\SmsMessage; class OrderShipped extends Notification { public function via($notifiable): array { return ['sms']; } public function toSms($notifiable): SmsMessage { return new SmsMessage( recipient: $notifiable->phone, content: "Your order #{$this->order->id} has been shipped!", ); } }
WhatsApp Channel
use Illuminate\Notifications\Notification; use Ratoufa\Messaging\Data\SmsMessage; class OrderShipped extends Notification { public function via($notifiable): array { return ['whatsapp']; } public function toWhatsApp($notifiable): SmsMessage { return new SmsMessage( recipient: $notifiable->phone, content: "Your order #{$this->order->id} has been shipped!", ); } }
Model Trait
Add the HasMessaging trait to your model for quick messaging:
use Ratoufa\Messaging\Concerns\HasMessaging; class User extends Authenticatable { use HasMessaging; // Define the phone field (defaults to 'phone') public function routeNotificationForSms(): ?string { return $this->phone_number; } public function routeNotificationForWhatsApp(): ?string { return $this->whatsapp_number; } }
Usage:
$user->sendSms('Hello!'); $user->sendWhatsApp('Hello via WhatsApp!'); $user->sendOtp(); $user->verifyOtp('123456');
Custom Gateways
You can register custom gateways:
use Ratoufa\Messaging\Contracts\GatewayInterface; use Ratoufa\Messaging\Facades\Messaging; class MyCustomGateway implements GatewayInterface { public function send(SmsMessage $message): Response { // Your implementation } public function getBalance(): Collection { // Your implementation } } // Register the gateway Messaging::extend('custom', new MyCustomGateway()); // Use it Messaging::channel('custom')->to('22890123456')->send('Hello!');
Artisan Command
Check your account balance:
php artisan messaging balance php artisan messaging balance --channel=whatsapp
Phone Number Formatting
The package includes a phone formatter utility:
use Ratoufa\Messaging\Support\PhoneFormatter; $formatter = new PhoneFormatter(); // Format with default country code (from config) $phone = $formatter->format('90123456'); // "22890123456" // Format with specific country code $phone = $formatter->format('90123456', '229'); // "22990123456" // Format for WhatsApp $phone = $formatter->formatForWhatsApp('22890123456'); // "whatsapp:+22890123456" // Format multiple numbers $phones = $formatter->formatMany(['90123456', '91234567']); // Validate phone number $isValid = $formatter->isValid('22890123456'); // true
Events
The package dispatches events for delivery reports:
use Ratoufa\Messaging\Events\MessageDeliveryReportReceived; class MessageDeliveryListener { public function handle(MessageDeliveryReportReceived $event): void { $messageId = $event->messageId; $status = $event->status; // DeliveryStatus enum $recipient = $event->recipient; $deliveredAt = $event->deliveredAt; } }
Register in EventServiceProvider:
protected $listen = [ MessageDeliveryReportReceived::class => [ MessageDeliveryListener::class, ], ];
Testing
composer test
This runs:
- PHPStan (static analysis)
- Pest (unit & feature tests)
- Pint (code style)
- Rector (refactoring checks)
- Peck (typo detection)
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
The MIT License (MIT). Please see License File for more information.