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

v0.2.0 2025-12-26 02:56 UTC

This package is not auto-updated.

Last update: 2025-12-26 06:25:54 UTC


README

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads

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

WhatsApp

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 .env file.

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.