PHPackages                             zeevx/lara-termii - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. zeevx/lara-termii

ActiveLibrary[API Development](/categories/api)

zeevx/lara-termii
=================

A modern Laravel package for the Termii messaging &amp; OTP API - https://www.termii.com

1.3.0(1mo ago)89.8k↓61.3%13[3 issues](https://github.com/zeevx/lara-termii/issues)[3 PRs](https://github.com/zeevx/lara-termii/pulls)MITPHPPHP ^8.1CI passing

Since Jun 14Pushed 1mo ago2 watchersCompare

[ Source](https://github.com/zeevx/lara-termii)[ Packagist](https://packagist.org/packages/zeevx/lara-termii)[ Docs](https://github.com/zeevx/lara-termii)[ RSS](/packages/zeevx-lara-termii/feed)WikiDiscussions master Synced 2w ago

READMEChangelog (4)Dependencies (10)Versions (12)Used By (0)

Lara-Termii
===========

[](#lara-termii)

A modern Laravel package for the [Termii](https://www.termii.com) messaging, voice &amp; OTP API. Set up, test, and manage your Termii integration directly in your Laravel app.

[![Latest Version on Packagist](https://camo.githubusercontent.com/63987053de5627680d054a069c0e3a23fb17b4897b5a20efcbb63cd73b5e7fd7/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f7a656576782f6c6172612d7465726d69692e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/zeevx/lara-termii)[![Total Downloads](https://camo.githubusercontent.com/a4f3ad84b4c904ad2fd51ed0d99f7a3444eee9841580bf543d60fa3c1ee0592f/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f7a656576782f6c6172612d7465726d69692e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/zeevx/lara-termii)[![License](https://camo.githubusercontent.com/44e1df165366a793830d724e45b2f545f2062853bbee7ba492d7eb825e4fa163/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f7a656576782f6c6172612d7465726d69692e7376673f7374796c653d666c61742d737175617265)](LICENSE.md)

Requirements
------------

[](#requirements)

- PHP 8.1 through 8.4
- Laravel 9 through 13

> Still on an older stack? Lara-Termii **0.1.x** supports Laravel 6–9 / PHP 7.4+.

Installation
------------

[](#installation)

Install via composer:

```
composer require zeevx/lara-termii
```

Publish the config file:

```
php artisan vendor:publish --tag="termii-config"
```

Then add your Termii credentials to your `.env` file:

```
TERMII_API_KEY=your-termii-api-key
TERMII_SENDER_ID=YourSenderID
# Optional: Termii issues an account-specific, region-based base URL.
# Find yours on your dashboard and set it here (defaults to the v4 host).
TERMII_BASE_URL=https://v4.api.termii.com
# Optional defaults
TERMII_CHANNEL=generic
TERMII_TIMEOUT=30
TERMII_THROW=false
```

Usage
-----

[](#usage)

Every method returns an `Illuminate\Http\Client\Response`, so you get the full power of Laravel's HTTP client: `->json()`, `->successful()`, `->failed()`, `->status()`, `->body()` and more.

You can use the package in three interchangeable ways.

**Facade:**

```
use Zeevx\LaraTermii\LaraTermiiFacade as Termii;

$balance = Termii::balance()->json();
```

**Dependency injection / container:**

```
use Zeevx\LaraTermii\LaraTermii;

public function show(LaraTermii $termii)
{
    return $termii->balance()->json();
}
```

**Manual instantiation** (credentials fall back to config when omitted):

```
use Zeevx\LaraTermii\LaraTermii;

$termii = new LaraTermii(); // uses config('termii.*')
// or override per instance:
$termii = new LaraTermii('another-api-key', 'https://v4.api.termii.com');
```

### Check your balance

[](#check-your-balance)

```
$termii->balance();
```

### Message history

[](#message-history)

Reports for messages sent across the sms, voice &amp; whatsapp channels.

```
$termii->history();

// or a single message's report
$termii->history(messageId: 'message-id');
```

### Verify a number &amp; detect its network

[](#verify-a-number--detect-its-network)

```
$termii->status(phoneNumber: '2348012345678', countryCode: 'NG');
```

### DND (Do Not Disturb) lookup

[](#dnd-do-not-disturb-lookup)

```
$termii->search(phoneNumber: '2348012345678');
```

### Sender IDs

[](#sender-ids)

```
// Retrieve the status of all registered Sender IDs
$termii->allSenderId();

// Optionally filter by name and/or status ("active", "pending" or "blocked")
$termii->allSenderId(name: 'Acme', status: 'active');

// Request a new Sender ID
$termii->submitSenderId(senderId: 'Acme', useCase: 'Transactional alerts', company: 'Acme Inc');
```

### Send a message

[](#send-a-message)

`from` may be `null` to fall back to your configured `TERMII_SENDER_ID`.

```
$termii->sendMessage(
    to: '2348012345678',
    from: null,            // falls back to config('termii.sender_id')
    sms: 'Hello from Lara-Termii!'
);
```

Send a WhatsApp media message. Passing a media URL automatically switches the message to the WhatsApp channel:

```
$termii->sendMessage(
    to: '2348012345678',
    from: 'your-whatsapp-device',
    sms: 'Check this out',
    channel: 'whatsapp',
    mediaUrl: 'https://example.com/image.png',
    mediaCaption: 'An optional caption'
);
```

### Send OTP

[](#send-otp)

```
$termii->sendOTP(
    to: '2348012345678',
    from: null,
    messageType: 'NUMERIC',
    pinAttempts: 3,
    pinTimeToLive: 5,
    pinLength: 6,
    pinPlaceholder: '< 1234 >',
    messageText: 'Your confirmation code is < 1234 >'
);
```

### Send Voice OTP

[](#send-voice-otp)

```
$termii->sendVoiceOTP(to: '2348012345678', pinAttempts: 3, pinTimeToLive: 5, pinLength: 6);
```

### Send Voice Call

[](#send-voice-call)

```
$termii->sendVoiceCall(to: '2348012345678', code: 123456);
```

### Verify OTP

[](#verify-otp)

```
$response = $termii->verifyOTP(pinId: 'pin-id-from-send-otp', pin: '123456');

if ($response->json('verified') === true) {
    // OTP is valid
}
```

### In-App OTP

[](#in-app-otp)

```
$termii->sendInAppOTP(
    to: '2348012345678',
    pinAttempts: 3,
    pinTimeToLive: 5,
    pinLength: 6,
    pinType: 'NUMERIC'
);
```

### Email OTP

[](#email-otp)

Note: email OTPs cannot be verified with `verifyOTP()`.

```
$termii->sendEmailOTP(
    emailAddress: 'user@example.com',
    code: '123456',
    emailConfigurationId: 'your-email-config-id'
);
```

### Send bulk messages

[](#send-bulk-messages)

Send the same message to up to 100 recipients at once.

```
$termii->sendBulkMessage(
    to: ['2348011111111', '2348022222222'],
    from: null,
    sms: 'Hello everyone!'
);
```

### WhatsApp device templates

[](#whatsapp-device-templates)

```
// Plain template
$termii->sendTemplate(
    to: '2348012345678',
    deviceId: 'your-device-id',
    templateId: 'your-template-id',
    data: ['product_name' => 'Widget', 'otp' => '1234']
);

// Template with a media attachment
$termii->sendTemplateWithMedia(
    to: '2348012345678',
    deviceId: 'your-device-id',
    templateId: 'your-template-id',
    mediaUrl: 'https://example.com/image.png',
    mediaCaption: 'Optional caption',
    data: ['product_name' => 'Widget']
);
```

### Phonebooks

[](#phonebooks)

```
$termii->phonebooks();                                  // fetch all
$termii->createPhonebook(name: 'VIP', description: 'Best customers');
$termii->updatePhonebook(phonebookId: 'pb-id', name: 'VIPs');
$termii->deletePhonebook(phonebookId: 'pb-id');
```

### Contacts

[](#contacts)

```
$termii->contacts(phonebookId: 'pb-id');                // fetch all in a phonebook

$termii->addContact(
    phonebookId: 'pb-id',
    phoneNumber: '2348012345678',
    countryCode: '234',
    emailAddress: 'ada@example.com',
    firstName: 'Ada',
    lastName: 'Lovelace'
);

// Bulk-add from a CSV file on the local filesystem
$termii->addContactsFromFile(phonebookId: 'pb-id', file: '/path/contacts.csv', countryCode: '234');

// ...or from any Laravel filesystem disk (s3, local, public, ...)
$termii->addContactsFromFile(phonebookId: 'pb-id', file: 'imports/contacts.csv', countryCode: '234', disk: 's3');

// ...or straight from an uploaded file (raw contents)
$csv = $request->file('csv');
$termii->addContactsFromContents(
    phonebookId: 'pb-id',
    contents: $csv->get(),
    filename: $csv->getClientOriginalName(),
    countryCode: '234'
);

$termii->deleteContact(phonebookId: 'pb-id', contactId: 'contact-id');
```

### Campaigns

[](#campaigns)

```
$termii->sendCampaign(
    countryCode: '234',
    senderId: 'Acme',
    message: 'Welcome to Acme.',
    phonebookId: 'pb-id',
    channel: 'generic',
    messageType: 'plain',
    campaignType: 'personalized',      // "regular" or "personalized"
    scheduleSmsStatus: 'regular',      // "regular" or "scheduled"
    options: [
        'remove_duplicate' => 'yes',
        // 'schedule_time' => '30-06-2026 6:00', // required when scheduled
    ]
);

$termii->campaigns();                          // fetch all
$termii->campaignHistory(campaignId: 'camp-id');
$termii->retryCampaign(campaignId: 'camp-id');
```

### eSIMs (Sotel)

[](#esims-sotel)

Termii's eSIM API uses its own bearer-token authentication, so it is exposed as a sub-client. It shares your configured API key and base URL, exchanges the key for a token on the first call, and reuses that token for the lifetime of the instance. Call `authenticate()` yourself only to refresh an expired token.

```
$esim = $termii->esim();

$esim->dataPlans(country: 'NG', type: 'LOCAL');   // both filters optional
$esim->createEsim(productId: 'prod-id', iso3: 'NGA');
$esim->purchasePlan(iccid: '894...', productId: 'prod-id', iso3: 'NGA');
$esim->qrCode(iccid: '894...');
$esim->profile(iccid: '894...');
$esim->usage(iccid: '894...');
$esim->planStatus(iccid: '894...');
$esim->esims(page: 0, size: 15);
$esim->countries(page: 0, size: 15);
```

Error handling
--------------

[](#error-handling)

By default a failed request returns the `Response` so you can inspect it:

```
$response = $termii->balance();

if ($response->failed()) {
    report($response->json('message'));
}
```

Set `TERMII_THROW=true` (or `config('termii.throw')`) to have any 4xx/5xx response throw an `Illuminate\Http\Client\RequestException` instead.

Testing
-------

[](#testing)

Because the package uses Laravel's HTTP client, you can fake Termii in your own tests:

```
use Illuminate\Support\Facades\Http;

Http::fake([
    '*/api/sms/send' => Http::response(['message' => 'Successfully Sent'], 200),
]);
```

Run the package test suite (powered by [Pest](https://pestphp.com)):

```
composer test
```

Contributing
------------

[](#contributing)

The dev toolchain uses [Pest](https://pestphp.com) for tests and [Laravel Pint](https://laravel.com/docs/pint) for code style.

```
composer test    # run the test suite
composer lint    # check code style (pint --test)
composer format  # fix code style (pint)
```

> Pint and Pest are dev-only dependencies and never affect what your application needs at runtime.

Upgrading from 0.1.x
--------------------

[](#upgrading-from-01x)

1.0.0 is the first stable release and a modernized rewrite. Key changes from the 0.1.x line:

- **Return types**: methods now return `Illuminate\Http\Client\Response` instead of a raw JSON string. Call `->json()` / `->body()` to get the old data.
- **Config-driven**: the facade and container binding now work out of the box. Set `TERMII_API_KEY` (the 0.1.x binding required a constructor argument and could fatal). `new LaraTermii()` with no arguments reads from config.
- **Phone numbers are strings** (were `int`), so international/`+`-prefixed numbers are preserved.
- **`sendMessage`**: the first four positional arguments (`to`, `from`, `sms`, `channel`) are unchanged, but the old (broken) `bool $media` flag was removed. Any 0.1.x call that passed the media flag and URL positionally, e.g. `sendMessage($to, $from, $sms, 'whatsapp', true, $url, $caption)`, must be updated to `sendMessage($to, $from, $sms, 'whatsapp', $url, $caption)`. Media now works correctly, and a media request no longer sends the `sms` field (per Termii's docs).
- **`sendOTP`**: now also sends `pin_type` (required by Termii's send-token endpoint), defaulting to the `message_type` you pass, so existing calls keep working. An optional `$pinType` argument was added at the end if you need to set it separately.
- **`from`** now accepts `null` on `sendMessage()` / `sendOTP()` to fall back to your configured Sender ID.

Security
--------

[](#security)

If you discover any security-related issues, please email instead of using the issue tracker.

Credits
-------

[](#credits)

- [Paul Adams](https://github.com/zeevx)
- [All Contributors](../../contributors)

License
-------

[](#license)

The MIT License (MIT). Please see the [License File](LICENSE.md) for more information.

###  Health Score

54

—

FairBetter than 97% of packages

Maintenance85

Actively maintained with recent releases

Popularity32

Limited adoption so far

Community15

Small or concentrated contributor base

Maturity68

Established project with proven stability

 Bus Factor1

Top contributor holds 88.6% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~185 days

Recently: every ~1 days

Total

11

Last Release

41d ago

Major Versions

0.1.5 → 1.0.02026-07-06

PHP version history (2 changes)0.1.0PHP ^7.4|^8.0

1.0.0PHP ^8.1

### Community

Maintainers

![](https://www.gravatar.com/avatar/89e891f62dbee3c7f6979348f5e75af5b72267a9160456918e63f80092281ff4?d=identicon)[zeevx](/maintainers/zeevx)

---

Top Contributors

[![zeevx](https://avatars.githubusercontent.com/u/44035730?v=4)](https://github.com/zeevx "zeevx (31 commits)")[![drchibs](https://avatars.githubusercontent.com/u/66283877?v=4)](https://github.com/drchibs "drchibs (2 commits)")[![horlathunbhosun](https://avatars.githubusercontent.com/u/25985738?v=4)](https://github.com/horlathunbhosun "horlathunbhosun (2 commits)")

---

Tags

laravelotpsmswhatsappvoicezeevxlara-termiitermii

###  Code Quality

TestsPest

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/zeevx-lara-termii/health.svg)

```
[![Health](https://phpackages.com/badges/zeevx-lara-termii/health.svg)](https://phpackages.com/packages/zeevx-lara-termii)
```

###  Alternatives

[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9922.4M146](/packages/roots-acorn)[simplestats-io/laravel-client

Server-side analytics for Laravel that follows the full funnel from visit to registration to payment, attributed to the channel that drove it. Revenue, MRR, churn and ad-spend profit (ROAS/CAC) per channel. GDPR compliant, ad-blocker proof.

5226.7k](/packages/simplestats-io-laravel-client)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

265.2k](/packages/aedart-athenaeum)[psalm/plugin-laravel

Psalm plugin for Laravel

3345.4M354](/packages/psalm-plugin-laravel)[pressbooks/pressbooks

Pressbooks is an open source book publishing tool built on a WordPress multisite platform. Pressbooks outputs books in multiple formats, including PDF, EPUB, web, and a variety of XML flavours, using a theming/templating system, driven by CSS.

45844.8k1](/packages/pressbooks-pressbooks)[jasara/php-amzn-selling-partner-api

A fluent interface for Amazon's Selling Partner API in PHP

1349.3k1](/packages/jasara-php-amzn-selling-partner-api)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
