shabayek / laravel-sms
Laravel Package For SMS Sending
Requires
- php: ^8.1
- guzzlehttp/guzzle: ^7.4
Requires (Dev)
- mockery/mockery: ^1.4.4
- nunomaduro/collision: ^5.10
- orchestra/testbench: ^6.0
- phpunit/phpunit: ^9.5
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-28 19:02:54 UTC
README
SMS and OTP gateway drivers for Laravel, behind one facade.
Documentation · Getting started · Changelog
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
- Supported gateways
- Installation
- Quick start
- Usage
- Response codes
- OTP results and verification
- Gateway response notes
- Adding custom drivers
- Testing
- Contributing
- Security vulnerabilities
- License
Features
- One API for every gateway:
send(),sendOtp(),verify()andbalance()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 withhash_equals(). SMS EG can also generate and check the code on the gateway. - Structured results:
send()returns aResponseCodeenum, 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.
nulldriver: 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
messageconfig. 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(ormessageinconfig/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, andnullwhen it is not (rejected by the gateway, unexpected response, or thenulldriver). Check fornullbefore storing the code: passingnulltoverify()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\ConnectionExceptioninstead of returningnull. - Requests time out after 30 seconds, or 10 seconds while connecting. Change this with
SMS_TIMEOUTandSMS_CONNECT_TIMEOUT(ortimeoutandconnect_timeoutinconfig/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 like1.2e5) returnsfalse. The integer123456and 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
localandtestingenvironments the code is always1234. Every other environment gets a random 6-digit code, so make sureAPP_ENVis neitherlocalnortestingon any server real users can reach.
Gateway response notes
- SMS EG:
send()reports success only for an HTTP 2xx response whosetypeissuccess. 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 code1901. With service otp, thevSMSresponse body is not interpreted, because its format has not been verified. Any HTTP 2xx response counts as sent, sosendOtp()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
extendcan be the connection name (the key under connections) or thedrivervalue of the connection; a creator registered for the connection name wins. Resolve the driver through theSmsfacade, for exampleSms::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.