To send SMS from PHP, install the esmsafrica/sms Composer package, create an API key in the eSMS dashboard and call $esms->messages->send() with the number in international format. One thing to know first: Packagist currently serves version 1.0.0 of this PHP SMS API package, while the full 1.2.0 release is tagged on GitHub, so this guide shows how to install either.

Last reviewed: 29 October 2026

How to send SMS with the PHP SMS API

  1. Create an API key

    Sign in at sms.esmsafrica.io and open API Keys under DEVELOPERS in the left menu. Type a Key name, pick the Environment and tick the Permissions the key needs: Send SMS and Read messages and reports cover sending and status checks. Choose Test (sandbox) while you build. Click Generate key and copy it straight away: the full key is shown once, right after you create it. Test keys start with esms_test_, live keys with esms_live_.

    eSMS API keys page with the key name, environment, permissions, Generate key button and keys table numbered 1 to 5
    1 Key name, 2 Environment (Live or Test), 3 Permissions checkboxes, 4 Generate key button, 5 API keys table.
  2. Install version 1.2.0 from GitHub (recommended)

    Version 1.2.0 adds automatic idempotency keys on sends, Verify (OTP), opt-outs, bulk and batch sending, number validation and a webhook signature helper. Until Packagist lists it, point Composer at the GitHub repository. Add this to your composer.json:

    {
      "repositories": [
        { "type": "vcs", "url": "https://github.com/eSMS-Africa/esms-sdk-php" }
      ],
      "require": {
        "esmsafrica/sms": "^1.2"
      }
    }

    Then run composer update esmsafrica/sms (or, after adding only the repositories entry, composer require esmsafrica/sms:^1.2). Composer reads the v1.2.0 tag directly from the public repository.

  3. Or install 1.0.0 from Packagist

    A plain composer require esmsafrica/sms installs 1.0.0 today. It can send and schedule messages, list and fetch them, retry, send to saved contact lists, and read your balance and routes. It does not include Verify, opt-outs or the webhook helper, and its send call does not add an idempotency key while it retries failed requests by default. If you stay on 1.0.0, switch automatic retries off for sends so a timeout can never produce a duplicate message:

    // esmsafrica/sms 1.0.0 only: turn automatic retries off for sends
    $esms = new Client(getenv('ESMS_API_KEY'), ['max_retries' => 0]);
  4. Send a message

    Store the key in an environment variable such as ESMS_API_KEY, never in your repository. Then:

    <?php
    require __DIR__ . '/vendor/autoload.php';
    
    use Esms\Client;
    use Esms\Exception\EsmsException;
    use Esms\Exception\InsufficientBalanceException;
    
    $esms = new Client(getenv('ESMS_API_KEY'));
    
    try {
        $res = $esms->messages->send([
            'to'   => '+254712340001',            // international (E.164) format
            'text' => 'Hello from eSMS Africa',
            // 'sender_id' => 'YOURBRAND',        // optional: approved sender ID
        ]);
        echo $res['id'], ' ', $res['status'], PHP_EOL;
    } catch (InsufficientBalanceException $e) {
        echo 'Top up needed: ', $e->getMessage(), PHP_EOL;
    } catch (EsmsException $e) {
        // getApiCode() is the stable API code, e.g. country_not_enabled
        echo $e->getStatus(), ' ', $e->getApiCode(), ': ', $e->getMessage(), PHP_EOL;
    }

    Responses are associative arrays that mirror the API JSON. With a test key the id starts with test_ and nothing is sent or charged. With a live key the status is normally submitted. Leave out sender_id to use the country's default sender.

  5. Check the delivery status

    $msg = $esms->messages->get($res['id']);
    echo $msg['status'], PHP_EOL;          // queued | submitted | sent | delivered | failed ...
    foreach ($msg['timeline'] as $event) {
        echo $event['at'], ' ', $event['event'], PHP_EOL;
    }

    For production, receive delivery reports by webhook. In 1.2.0, Client::verifyWebhook($rawBody, $signature, $secret) checks the X-Webhook-Signature header for you.

  6. Go live

    Create a live key, enable each destination country, top up and swap the key. The same code then sends real messages.

Common errors and how to fix them

What you seeWhat it meansHow to fix it
Class "Esms\Client" not foundComposer's autoloader is not loaded.Add require __DIR__ . '/vendor/autoload.php'; at the top of the script.
Call to undefined method for verify or optOutsYou have 1.0.0 installed, which has no Verify or opt-out resources.Install 1.2.0 from GitHub as shown in step 2. Check with composer show esmsafrica/sms.
AuthenticationException (401)The key is missing or revoked.Check getenv('ESMS_API_KEY') returns the key in the PHP process (CLI and web server may differ).
403 country_not_enabledLive sending to that country is off for your account. Nothing was charged.Enable it on the Countries page.
403 sender_not_approvedThat sender ID is not approved for the destination country.Omit sender_id or request it on the Sender IDs page.
InsufficientBalanceException (402)Balance too low for the message.Top up at auth.esmsafrica.io/topup and retry.

Frequently asked questions

Which version should I use?

Use 1.2.0 from the GitHub repository for new projects. It is the release documented on docs.esmsafrica.io and it makes retries safe with idempotency keys.

Will plain composer require get 1.2.0 later?

Yes, once Packagist lists 1.2.0 a normal composer require esmsafrica/sms:^1.2 will work and you can remove the repositories entry.

Does it need a framework?

No. It is a plain PSR-4 library that only needs the curl and json extensions, so it works in Laravel, Symfony, WordPress plugins or a single script.

Can I send OTP codes from PHP?

Yes, with 1.2.0: $esms->verify->start($to) sends a code and $esms->verify->check($code, ['verification_id' => $id]) checks it.

Ready? Create a free eSMS account, then create a test API key and send your first simulated message in minutes. Nothing is charged until you switch to a live key.