Skip to content
Devix Open Source

Guide

Gateways and testing

The http driver

Almost every regional SMS gateway is the same thing: a URL, a few parameters with names of its own choosing, and a response you have to squint at. So it is configuration rather than code.

'gateways' => [
    'mygateway' => [
        'driver' => 'http',
        'url' => 'https://api.example.com/send',
        'method' => 'POST',          // or GET
        'json' => false,             // send the body as JSON rather than a form
        'headers' => ['Authorization' => 'Bearer '.env('SMS_TOKEN')],
        'params' => [
            'to' => ':to',
            'body' => ':text',
            'sender' => ':from',
            'encoding' => ':encoding',
        ],
        'text_value' => 'GSM',
        'unicode_value' => 'UCS2',
        'success' => ['json' => ['status' => 'ok']],
        'id_path' => 'data.messageId',
        'error_path' => 'error.message',
        'timeout' => 15,
    ],
],
In a value Becomes
:to +923001234567
:to_plain 923001234567
:text the message
:from the sender ID
:parts how many parts it will be billed as
:encoding text_value or unicode_value

:encoding matters more than it looks. Most gateways need to be told the message is Unicode; send Arabic without the flag and the recipient gets question marks, and you are still billed.

Recognising success

'success' => ['contains' => 'OK'],                    // the body has this in it
'success' => ['json' => ['status' => 'success']],     // a field equals this
'success' => ['json' => ['data.code' => 0]],          // nested, dotted

With no success rule, any 2xx is taken as sent.

Inside Laravel

The provider hands the http driver Laravel's own HTTP client, so Http::fake(), retries, timeouts and request logging all work exactly as they do everywhere else:

Http::fake(['api.example.com/*' => Http::response(['status' => 'ok'], 200)]);

$sender->send('0501234567', 'Hello');

Http::assertSent(fn ($request) => $request['to'] === '+971501234567');

Testing without a gateway

use Devix\Sms\Driver;
use Devix\Sms\Drivers\ArrayDriver;

$driver = new ArrayDriver;
$this->app->instance(Driver::class, $driver);

$user->notify(new VerificationCode('1234'));

$this->assertSame(1, $driver->count());
$this->assertSame('+971501234567', $driver->last()->to);
$this->assertStringContainsString('1234', $driver->last()->text);

// And the thing worth asserting that nobody asserts:
$this->assertSame(1, $driver->last()->cost()->parts, 'this notification must stay one message');

That last line is the one to copy. A template that grows by a sentence and silently becomes two parts is a doubled bill on every send, and it is exactly the kind of change that passes review.

A word about the gateways themselves

We have not run this against a live Jazz, Zong, Ufone, Telenor, Unifonic or Etisalat account. We do not have accounts with them, and a package that claimed tested support for a gateway nobody here has ever authenticated against would be dishonest.

What that means in practice:

  • The http driver is declarative, so you can read exactly what it will send and check it against your operator's documentation without reading PHP.
  • The array driver lets you assert that request in your own test suite.
  • The log driver lets you watch it in development before you spend anything.
  • Everything before the request — the counting, the encoding flag, the E.164 number, the sender ID — is ours, and all of it is tested.

If you are on Twilio, use the official channel. It is maintained, it takes 303,601 downloads a month, and there is no reason to replace it. Use this for what happens before the send.

Updated 15 Sep 2026