Search by

shabayek / laravel-sms

shabayekdes

Laravel Package For SMS Sending

Package info

github.com/shabayekdes/laravel-sms

pkg:composer/shabayek/laravel-sms

Statistics

Installs: 2 034

Dependents: 0

Suggesters: 0

Stars: 2

Open Issues: 2

v3.0 2026-10-28 18:09 UTC

README

Laravel SMS

SMS and OTP gateway drivers for Laravel, behind one facade.

Documentation · Getting started · Changelog

Tests StyleCI Latest version on Packagist PHP version Total downloads License

Laravel SMS sends text messages and one-time passwords through several SMS gateways from one Sms facade. You switch gateways in config, get structured results from every send, and can register your own drivers.

Contents

Features

  • One API for every gateway: send(), sendOtp(), verify() and balance() work the same way on every driver.
  • Several connections: set a default in config and pick another one at runtime with Sms::driver('…').
  • OTP built in: codes are generated with random_int() and checked with hash_equals(). SMS EG can also generate and check the code on the gateway.
  • Structured results: send() returns a ResponseCode enum, a message you can change in config, and the gateway's own code and message.
  • Encoding detection: the package picks English, Arabic or Unicode encoding from the message text.
  • Timeouts and safe errors: request and connect timeouts are configurable. Transport errors never include credentials, phone numbers or codes.
  • null driver: turns sending off in local and test environments.
  • Custom drivers: add your own gateway with Sms::extend().

Supported gateways

Gateway Connection send sendOtp balance OTP service
SMS EG smseg ✓ ✓ ✓ normal or otp (the gateway generates and checks the code)
SMS Misr smsmisr ✓ ✓ ✓ normal or otp (the package's code is sent through the OTP endpoint)
Victory Link victorylink ✓ ✓ ✓ normal only
Ooredoo ooredoo ✓ ✓ ✓ normal only
Null (sending turned off) null — — — —

Need another gateway? See Adding custom drivers.

Installation

Requires PHP 8.1 or later.

Install with Composer:

composer require shabayek/laravel-sms

The service provider and the Sms facade alias are registered automatically through Laravel package discovery.

Quick start

Set the connection and credentials in .env:

SMS_CONNECTION=smseg
SMS_USERNAME=username
SMS_PASSWORD=password
SMS_SENDER_ID=sender
SMS_SERVICE=normal

Send a message, or send a code and check it:

use Shabayek\Sms\Facade\Sms;

$result = Sms::send('0120000000', 'Hello world');

if (! $result['success']) {
    logger()->warning('SMS not sent', $result['provider']);
}

$code = Sms::sendOtp('0120000000'); // the code that was sent, or null if it wasn't sent

// Later, compare the code the user typed with the one you stored:
$valid = Sms::verify('0120000000', $request->input('otp'), $storedCode);

The sections below cover each step in detail.

Usage

  • Publish the config file
php artisan vendor:publish --provider="Shabayek\Sms\Providers\SmsServiceProvider"

or by tag:

php artisan vendor:publish --tag=laravel-sms
  • Choose what gateway you would like to use for your application. Then make that as default driver so that you don't have to specify that everywhere. But, you can also use multiple gateways in a project.
'default' => 'smseg',
  • Then fill the credentials for that gateway in the drivers array.
SMS_CONNECTION=smseg
SMS_USERNAME=username
SMS_PASSWORD=password
SMS_SENDER_ID=sender
SMS_SERVICE=normal
  • Send normal sms message
use Shabayek\Sms\Facade\Sms;

$sms = Sms::send('0120000000', 'Hello world');
  • Send otp sms message

With service normal, the package generates the code and sends it in a regular message built from the message config. With service otp, SMS EG generates and checks the code itself, while SMS Misr sends a code generated by the package through its OTP endpoint. Victory Link and Ooredoo always behave like normal.

use Shabayek\Sms\Facade\Sms;

$sms = Sms::sendOtp('0120000000');
  • Verify phone number
use Shabayek\Sms\Facade\Sms;

$phone = '09121234567'; // phone number
$otp = '123456'; // otp that you sent to phone
$actualOtp = '123456'; // this is the actual otp that you sent to the user

$verify = Sms::verify($phone, $otp, $actualOtp); // third param is optional only for SMS EG with service otp
  • Change the OTP message with SMS_MESSAGE (or message in config/sms.php). Use {code} where the code goes. The message can be in any language: the encoding sent to the gateway (English, Arabic or Unicode) is detected from the text.
SMS_MESSAGE="Your verification code is: {code}"

Response codes

send() returns an array with a code you can check (a Shabayek\Sms\Enums\ResponseCode case), a message you can customize, and the gateway's own code and message when it returned them:

[
    'success' => false,
    'code' => ResponseCode::Rejected,
    'message' => 'Message not sent successfully',
    'provider' => ['code' => '1902', 'message' => null],
]
Value Case Meaning
sent ResponseCode::Sent The gateway accepted the message.
rejected ResponseCode::Rejected The gateway answered with a failure value. See provider.
http_error ResponseCode::HttpError The gateway answered with a non-2xx HTTP status.
invalid_response ResponseCode::InvalidResponse The body was empty, malformed or missing the expected value.
disabled ResponseCode::Disabled The null driver is in use, so nothing was sent.
use Shabayek\Sms\Enums\ResponseCode;
use Shabayek\Sms\Facade\Sms;

$result = Sms::send('0120000000', 'Hello world');

if ($result['code'] === ResponseCode::Rejected) {
    logger()->warning('SMS rejected', $result['provider']);
}

Check code rather than message in your application. The case serializes to its string value in JSON, and ResponseCode::from('rejected') turns a stored value back into a case. To change the texts, publish the config and edit the messages array:

'messages' => [
    'sent' => 'Message sent successfully',
    'rejected' => 'Message not sent successfully',
    'http_error' => 'Message not sent successfully',
    'invalid_response' => 'Message not sent successfully',
    'disabled' => 'SMS driver is disabled',
],

OTP results and verification

  • sendOtp() returns the generated code when the request is treated as sent, and null when it is not (rejected by the gateway, unexpected response, or the null driver). Check for null before storing the code: passing null to verify() as the actual otp throws an exception.
$code = Sms::sendOtp('0120000000');

if ($code === null) {
    // not sent: show an error or let the user retry
}

SMS EG with service otp always returns null, because the gateway generates and checks the code itself. The return value does not tell you whether the gateway accepted the request, so don't use the check above with this service.

  • Connection failures such as timeouts throw Illuminate\Http\Client\ConnectionException instead of returning null.
  • Requests time out after 30 seconds, or 10 seconds while connecting. Change this with SMS_TIMEOUT and SMS_CONNECT_TIMEOUT (or timeout and connect_timeout in config/sms.php). A timed-out request may still have been accepted by the gateway, so don't resend automatically: the user could get the SMS twice.
  • A returned code means the gateway accepted the request at best. No driver confirms that the SMS was delivered.
  • verify() only accepts non-negative integers and strings of digits. Anything else (booleans, null, floats, spaces, values like 1.2e5) returns false. The integer 123456 and the string '123456' match each other, but a string with a leading zero only matches the same string. Validate the input first, for example with 'otp' => 'required|digits:6'.
  • In the local and testing environments the code is always 1234. Every other environment gets a random 6-digit code, so make sure APP_ENV is neither local nor testing on any server real users can reach.

Gateway response notes

  • SMS EG: send() reports success only for an HTTP 2xx response whose type is success. This format comes from the package's own code and tests and has not been confirmed against SMS EG's API documentation.
  • SMS Misr: send() reports success only for an HTTP 2xx response with code 1901. With service otp, the vSMS response body is not interpreted, because its format has not been verified. Any HTTP 2xx response counts as sent, so sendOtp() can return a code even when SMS Misr rejected the request inside a 200 response.

See CHANGELOG.md for upgrade notes.

Adding custom drivers

  • Writing The Driver

To create our custom sms driver, we first need to implement the Shabayek\Sms\Contracts\SmsGatewayContract contract. So, a new SMS gateway implementation might look something like this:

namespace App\Sms;

use Shabayek\Sms\Contracts\SmsGatewayContract;
use Shabayek\Sms\Enums\ResponseCode;

class CustomSms implements SmsGatewayContract
{
    private $config;

    public function __construct(array $config)
    {
        $this->config = $config;
    }

    public function send($phone, $message): array
    {
        // Call your gateway here.
        return ['success' => true, 'code' => ResponseCode::Sent, 'message' => ResponseCode::Sent->message(), 'provider' => ['code' => null, 'message' => null]];
    }

    public function sendOtp($phone)
    {
        // Return the code you sent, or null when nothing was sent.
        return null;
    }

    public function verify(string $phone, int $otp, $actualOtp = null): bool
    {
        return $actualOtp !== null && hash_equals((string) $actualOtp, (string) $otp);
    }

    public function balance()
    {
        return 0;
    }
}

Then, we need to add a new config for our custom sms driver. in sms config within connections key

'connections' => [
    ...
    'custom' => [
        'driver' => 'custom',
        'username' => 'username',
        'password' => 'password',
        'sender_id' => 'sender',
        'service' => 'normal',
    ],
    ...
],

we can finish our custom driver registration by calling the Sms facade's extend method, for example in a service provider's boot method:

use App\Sms\CustomSms;
use Shabayek\Sms\Facade\Sms;

Sms::extend('custom', function ($app, array $config) {
    return new CustomSms($config);
});

The closure receives the application and the config of the connection being resolved. The first argument of extend can be the connection name (the key under connections) or the driver value of the connection; a creator registered for the connection name wins. Resolve the driver through the Sms facade, for example Sms::driver('custom').

Testing

composer test

Contributing

Issues and pull requests are welcome on GitHub. Run composer test before you open a pull request. Code style follows the Laravel preset and is checked by StyleCI. See CHANGELOG.md for release history and upgrade notes.

Security vulnerabilities

If you've found a bug regarding security please mail esmail.shabayek@gmail.com instead of using the issue tracker.

License

The Laravel SMS Gateway package is open-sourced software licensed under the MIT license.

Laravel is a trademark of Taylor Otwell. This is a community package and is not affiliated with or endorsed by Laravel. Brand assets are in assets/branding.