PHPackages                             kwtsms/kwtsms-php - 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. [Mail &amp; Notifications](/categories/mail)
4. /
5. kwtsms/kwtsms-php

ActiveLibrary[Mail &amp; Notifications](/categories/mail)

kwtsms/kwtsms-php
=================

kwtSMS API Client for PHP. Official library to interface with the Kuwait SMS gateway (kwtsms.com)

v1.7.0(5d ago)02↓100%[1 PRs](https://github.com/boxlinknet/kwtsms-php/pulls)MITPHPPHP &gt;=7.4CI passing

Since Mar 5Pushed 5d agoCompare

[ Source](https://github.com/boxlinknet/kwtsms-php)[ Packagist](https://packagist.org/packages/kwtsms/kwtsms-php)[ Docs](https://github.com/boxlinknet/kwtsms-php)[ RSS](/packages/kwtsms-kwtsms-php/feed)WikiDiscussions main Synced today

READMEChangelog (8)Dependencies (3)Versions (10)Used By (0)

kwtSMS PHP Client
=================

[](#kwtsms-php-client)

[![Latest Version](https://camo.githubusercontent.com/244a1053874bcf932eea5464a650ed8e4f1529817e69d4962bd41ca78b522826/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6b7774736d732f6b7774736d732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/kwtsms/kwtsms)[![Total Downloads](https://camo.githubusercontent.com/311d7f48bfd9b02f13464e7fe6f127242e96285dfd054151746f46ae007fcaa1/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6b7774736d732f6b7774736d732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/kwtsms/kwtsms)[![PHP Version](https://camo.githubusercontent.com/f03d6d55ebccc1198dd03f671b8b66bedc839baf90b9c0157dfc7e25ca35d1cb/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f6b7774736d732f6b7774736d732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/kwtsms/kwtsms)[![License](https://camo.githubusercontent.com/f1144baac214c74937b1e9aba95905d854d558afa0162661003fe2bcd73990ee/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f6b7774736d732f6b7774736d732e7376673f7374796c653d666c61742d737175617265)](LICENSE)[![CI](https://camo.githubusercontent.com/8f60a3fecb1008bc949fd5817eba4e75e95146270ad0376ed30101e5f180dad8/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f626f786c696e6b6e65742f6b7774736d732d7068702f63692e796d6c3f6272616e63683d6d61696e267374796c653d666c61742d737175617265266c6162656c3d4349)](https://github.com/boxlinknet/kwtsms-php/actions/workflows/ci.yml)

kwtSMS API Client for PHP. Official library to interface with the Kuwait SMS gateway (kwtsms.com).

---

About kwtSMS
------------

[](#about-kwtsms)

kwtSMS is a Kuwait-based SMS gateway trusted by businesses to deliver messages worldwide, with private Sender IDs, free API testing, non-expiring credits, and competitive flat-rate pricing. Open a free account in under a minute, no paperwork required. [Get started](https://www.kwtsms.com/signup/)

---

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

[](#requirements)

- PHP 7.4+
- `ext-curl`
- `ext-json`

---

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

[](#installation)

```
composer require kwtsms/kwtsms
```

If you don't have a project directory yet:

```
mkdir my-project && cd my-project
composer require kwtsms/kwtsms
```

---

Quick Start
-----------

[](#quick-start)

```
require 'vendor/autoload.php';

use KwtSMS\KwtSMS;

$sms = KwtSMS::from_env();

$result = $sms->send('96598765432', 'Your OTP for MYAPP is: 123456');

if ($result['result'] === 'OK') {
    echo "Sent! Cost: {$result['points-charged']} credits\n";
} else {
    echo "Error [{$result['code']}]: {$result['description']}. {$result['action']}\n";
}
```

---

Configuration
-------------

[](#configuration)

### Environment variables / `.env` file (recommended)

[](#environment-variables--env-file-recommended)

Create a `.env` file in your project root (add it to `.gitignore`):

```
KWTSMS_USERNAME=php_username        # API user, NOT your phone number or website login
KWTSMS_PASSWORD=php_password
KWTSMS_SENDER_ID=KWT-SMS            # Replace with a private Sender ID before going live
KWTSMS_TEST_MODE=1                  # Set to 0 when ready to deliver real messages
KWTSMS_LOG_FILE=kwtsms.log          # Path for NDJSON request log. Leave empty to disable.
```

Credentials are at: kwtsms.com → Account → API.

Load with:

```
$sms = KwtSMS::from_env();
```

`from_env()` reads credentials in this order:

1. System environment variables (Docker, CI, server config)
2. `.env` file in the current working directory

`.env` parsing rules:

- Lines starting with `#` are skipped
- Quoted values (`"value"` or `'value'`) have quotes stripped
- Unquoted values: trailing inline comments (space + `#`) are stripped
- Embedded newlines in values are stripped (prevents env-injection)

### Constructor injection

[](#constructor-injection)

```
$sms = new KwtSMS(
    'php_username',
    'php_password',
    'MY-BRAND',           // sender ID
    false,                // test mode
    'storage/kwtsms.log'  // log file; empty string disables logging
);
```

Credentials have embedded newlines stripped automatically. The `log_file` path is rejected if it contains `..` (path traversal guard).

---

Methods
-------

[](#methods)

### `verify()`

[](#verify)

Test credentials and fetch current balance.

```
[$ok, $balance, $error] = $sms->verify();

if ($ok) {
    echo "Connected! Balance: {$balance} credits\n";
} else {
    echo "Failed: {$error}\n";
}
```

**Returns:** `[bool $ok, float $balance, string $error]`

Always call `verify()` at startup to detect wrong credentials (ERR003), blocked account (ERR005), or IP not whitelisted (ERR024).

---

### `balance()`

[](#balance)

Fetch current credit balance.

```
$balance = $sms->balance(); // float|null
```

**Returns:** `float` on success, `null` on failure. Always makes an API call.

---

### `purchased()`

[](#purchased)

Return the total credits purchased on the account, cached from the last `verify()` or `balance()` call.

```
$purchased = $sms->purchased(); // float|null. Null until verify() or balance() has been called.
```

**Returns:** `float|null`

---

### `send()`

[](#send)

Send SMS to one or more recipients.

```
// Single number
$result = $sms->send('96598765432', 'Hello from kwtSMS!');

// Per-message sender ID override
$result = $sms->send('96598765432', 'Hello!', 'MY-BRAND');

// Multiple numbers: array or comma-separated string
$result = $sms->send(['96598765432', '96512345678'], 'Bulk announcement');
$result = $sms->send('96598765432,96512345678', 'Bulk announcement');
```

**What `send()` does automatically:**

- Normalizes all phone numbers (strips `+`, `00`, spaces, dashes, converts Arabic/Hindi digits)
- Deduplicates: the same normalized number is only charged and dispatched once
- Cleans the message via `MessageUtils::clean_message()` (see below)
- Returns ERR009 locally (no API call, no credits consumed) if the message is empty or becomes empty after cleaning
- Splits &gt;200 numbers into 200-number batches with 0.5s inter-batch delay
- Retries ERR013 (queue full) automatically: 30s, 60s, 120s backoff, up to 4 attempts

**Success response (single / ≤200 numbers):**

```
{
  "result": "OK",
  "msg-id": "f4c841adee210f31307633ceaebff2ec",
  "numbers": 1,
  "points-charged": 1,
  "balance-after": 180
}
```

**Success response (bulk / &gt;200 numbers):**

```
{
  "result": "OK",
  "batches": 3,
  "msg-ids": ["abc...", "def...", "ghi..."],
  "numbers": 550,
  "points-charged": 550,
  "balance-after": 450,
  "errors": []
}
```

**Error response:**

```
{
  "result": "ERROR",
  "code": "ERR006",
  "description": "No valid phone numbers.",
  "action": "Make sure each number includes the country code (e.g. 96598765432)."
}
```

Always save `msg-id` (needed for status/DLR lookups) and `balance-after`(avoids an extra `/balance/` call).

---

### `validate()`

[](#validate)

Validate and normalize a list of phone numbers locally. No API call is made.

```
$report = $sms->validate(['abcd', '+965 9876 5432', '96522334455']);

echo $report['nr'];  // total submitted: 3
echo $report['ok'];  // locally valid:   2
echo $report['er'];  // invalid:         1

foreach ($report['rejected'] as $r) {
    echo "{$r['number']}: {$r['error']}\n";
}

// Full per-number detail:
foreach ($report['raw'] as $entry) {
    // $entry['phone']      original input
    // $entry['valid']      bool
    // $entry['normalized'] normalized number (if valid)
    // $entry['error']      error message (if invalid)
}
```

For pre-campaign routing validation (checks if numbers are routable on your account), use the kwtSMS web dashboard or call the API directly via `/API/validate/`.

---

### `senderids()`

[](#senderids)

List Sender IDs registered on the account.

```
$result = $sms->senderids();
print_r($result['senderids']); // ['MY-APP', 'KWT-SMS']
```

---

### `coverage()`

[](#coverage)

List active country prefixes on the account.

```
$result = $sms->coverage();
print_r($result['prefixes']);
```

---

### `status(string $msgId)`

[](#statusstring-msgid)

Check the queue/dispatch status of a sent message. Use the `msg-id` returned by `send()`.

```
$result = $sms->status($msgId);

if ($result['result'] === 'OK') {
    echo $result['status'];      // e.g. "sent"
    echo $result['description']; // e.g. "Message successfully sent to gateway"
} else {
    // ERR029: msg-id not found
    // ERR030: stuck in queue — delete at kwtsms.com → Queue to recover credits
    echo $result['action'];
}
```

---

### `dlr(string $msgId)`

[](#dlrstring-msgid)

Retrieve delivery reports for a sent message. Only available for international (non-Kuwait) numbers. Wait at least 5 minutes after sending before calling.

```
$result = $sms->dlr($msgId);

if ($result['result'] === 'OK') {
    foreach ($result['report'] as $entry) {
        echo $entry['Number'] . ': ' . $entry['Status'] . "\n";
        // e.g. "96550123456: Received by recipient"
    }
}
```

> Kuwait numbers do not support DLR. For Kuwait delivery confirmation, use `status()` instead.

---

Utility Classes
---------------

[](#utility-classes)

### `PhoneUtils::validate_phone_input()`

[](#phoneutilsvalidate_phone_input)

Validate and normalize a single phone number string.

```
use KwtSMS\PhoneUtils;

[$valid, $error, $normalized] = PhoneUtils::validate_phone_input('+٩٦٥ ٩٨٧٦ ٥٤٣٢');
// $valid      true
// $error      null
// $normalized "96598765432"
```

Validation steps (in order):

1. Empty check
2. Email detection (`@` present)
3. Arabic/Hindi digit conversion + non-digit stripping
4. Leading zero stripping
5. Local trunk digit `0` stripped when country code is matched (e.g. `966 055...` → `966 55...`)
6. Length check: 7–15 digits
7. Country-specific format check: local digit count and mobile starting digit (60+ countries via `PHONE_RULES`). Unknown country codes pass with length-only validation.

### `PhoneUtils::find_country_code()` / `PhoneUtils::validate_phone_format()`

[](#phoneutilsfind_country_code--phoneutilsvalidate_phone_format)

Lower-level helpers used internally by `validate_phone_input()`. Useful if you need to inspect the country code or run format checks independently.

```
$cc    = PhoneUtils::find_country_code('96598765432'); // "965"
[$ok, $err] = PhoneUtils::validate_phone_format('96598765432'); // [true, null]
[$ok, $err] = PhoneUtils::validate_phone_format('96510000000'); // [false, "Invalid Kuwait mobile number: ..."]
```

### `PhoneUtils::normalize_phone()`

[](#phoneutilsnormalize_phone)

Normalize without validating. Useful for bulk pre-processing.

```
$normalized = PhoneUtils::normalize_phone('+965 9876-5432'); // "96598765432"
```

### `MessageUtils::clean_message()`

[](#messageutilsclean_message)

Strip content that causes silent delivery failures. Called automatically by `send()`.

```
use KwtSMS\MessageUtils;

$clean = MessageUtils::clean_message($rawTemplate);
```

What it strips and why:

StepWhatWhy0Invalid UTF-8 byte sequences strippedPrevents silent `json_encode` failure (ERR999) on malformed input1HTML tagsPrevents ERR0272Arabic/Hindi digits converted to LatinOTP codes render consistently on all handsets3Hidden Unicode (U+200B zero-width space, U+FEFF BOM, U+00AD soft hyphen, etc.)Common in copy-pasted text from Word/PDFs; causes spam filter rejection4Emojis: standard, country flags (U+1F1E0–U+1F1FF), mahjong tiles (U+1F000), keycap combiner (U+20E3), tags block (U+E0000–E007F)Messages with emojis queue indefinitely with no error returned5Other control charactersPrevents encoding issuesNewlines (`\n`, `\r`), tabs (`\t`), Arabic text, and Latin punctuation are preserved.

---

Phone Number Formats
--------------------

[](#phone-number-formats)

The library normalizes all of these automatically:

InputNormalized`+965 9876 5432``96598765432``0096598765432``96598765432``(965) 9876-5432``96598765432``٩٦٥٩٨٧٦٥٤٣٢` (Arabic-Indic)`96598765432``۹۶۵۹۸۷۶۵۴۳۲` (Extended Arabic-Indic)`96598765432``966 055 123 4567` (Saudi with trunk 0)`966551234567`Rejected inputs: email addresses, empty strings, fewer than 7 digits, more than 15 digits, numbers that don't match their country's digit length or mobile prefix rules.

---

Error Handling
--------------

[](#error-handling)

The library adds an `action` field to every error response:

```
$result = $sms->send($phone, $message);

if ($result['result'] !== 'OK') {
    $code   = $result['code'];        // "ERR003"
    $desc   = $result['description']; // "Wrong API username or password."
    $action = $result['action'];      // "Check KWTSMS_USERNAME and KWTSMS_PASSWORD."
}
```

Never expose raw `ERR0XX` codes to end users.

SituationError codeRecommended user messageInvalid phone numberERR006, ERR025"Please enter a valid phone number."Wrong credentialsERR003"SMS service is temporarily unavailable." (log and alert admin)No balanceERR010, ERR011"SMS service is temporarily unavailable." (alert admin to top up)Country not supportedERR026"SMS delivery to this country is not available."Rate limitedERR028"Please wait before requesting another code."Message rejectedERR031, ERR032"Your message could not be sent. Please try again."Queue fullERR013Handled automatically by bulk retry; surface only if all retries failNetwork errorERR999"Could not connect to SMS service. Please try again."---

CLI
---

[](#cli)

The CLI tool has moved to a dedicated package: **[kwtsms-cli](https://github.com/boxlinknet/kwtsms-cli)**

---

Logging
-------

[](#logging)

When `KWTSMS_LOG_FILE` is set, every API request is appended to the file as a newline-delimited JSON (NDJSON) entry:

```
{"ts":"2026-03-05T12:00:00Z","endpoint":"send","request":{...,"password":"***"},"response":{...},"ok":true,"error":null}
```

The password is always masked as `***`. Logging never throws; failures are silently ignored so they cannot crash the main application flow.

---

Sender ID
---------

[](#sender-id)

- `KWT-SMS` is a shared test sender. It causes delivery delays and is blocked on Virgin Kuwait. Never use it in production.
- **Transactional SenderID:** required for OTP. Bypasses DND (Do Not Disturb) filtering. Cost: 15 KD one-time. Processing: ~5 business days.
- **Promotional SenderID:** for bulk/marketing. Silently blocked on DND numbers (credits still charged). Cost: 10 KD one-time.
- SenderIDs are case-sensitive and cannot be transferred between providers.

---

Security Checklist
------------------

[](#security-checklist)

```
BEFORE GOING LIVE:
[ ] Private Sender ID registered (not KWT-SMS)
[ ] Transactional Sender ID for OTP (not promotional)
[ ] Test mode OFF (KWTSMS_TEST_MODE=0)
[ ] CAPTCHA on all SMS-triggering forms
[ ] Rate limit per phone number (max 3–5 per hour)
[ ] Rate limit per IP address (max 10–20 per hour)
[ ] OTP codes stored as HMAC hash, not plaintext
[ ] Admin alert on low balance
[ ] .env in .gitignore; credentials never committed

```

---

FAQ
---

[](#faq)

**My message returned OK but the recipient didn't receive it. What happened?**

Check the Sending Queue at kwtsms.com. If it is stuck there, it was accepted but not dispatched. Common causes: emoji in the message, hidden characters from copy-pasting, spam filter trigger, or `KWTSMS_TEST_MODE=1` still set. Delete stuck messages from the queue to recover credits.

**What is the difference between Test mode and Live mode?**

Test mode (`KWTSMS_TEST_MODE=1`) queues the message but never delivers it. No SMS is sent, no credits consumed. Use this during development. Set to `0` before going live.

**Why should I not use `KWT-SMS` as my Sender ID in production?**

`KWT-SMS` is a shared promotional sender. It causes delivery delays, is blocked on Virgin Kuwait, and cannot bypass DND filtering. Register a private Transactional Sender ID for OTP flows.

**I'm getting ERR003. What's wrong?**

You are using the wrong credentials. The API requires your API username and password, not your account phone number or website login. Find them at kwtsms.com → Account → API.

**Can I send to international numbers?**

International sending is disabled by default. Contact kwtSMS support to activate specific country prefixes. Use `coverage()` to see which are currently active. Note: enabling international coverage increases bot/abuse exposure. Implement rate limiting and CAPTCHA before enabling.

---

Examples
--------

[](#examples)

See `examples/` for runnable code covering every use case:

\#FileWhat it covers01`01-quickstart.php`Verify credentials, send first SMS02`02-otp.php`Basic OTP flow03`03-bulk.php`Bulk send, auto-batching04`04-validation.php`Phone number validation05`05-error-handling.php`Error categories and retry logic06`06-message-cleaning.php`Message cleaning internals07`07-laravel.php`Laravel Service Provider and Notification channel08`08-wordpress.php`WordPress plugin, WooCommerce, 2FA login09`09-otp-production.php`Production OTP: DB, CAPTCHA, rate limiting, brute-force protectionFull documentation for each example is in `examples/docs/`.

---

Help &amp; Support
------------------

[](#help--support)

- **[kwtSMS FAQ](https://www.kwtsms.com/faq/)**: credits, sender IDs, OTP, delivery
- **[kwtSMS Support](https://www.kwtsms.com/support.html)**: tickets and help articles
- **[API Documentation (PDF)](https://www.kwtsms.com/doc/KwtSMS.com_API_Documentation_v41.pdf)**: REST API v4.1 reference
- **[kwtSMS Dashboard](https://www.kwtsms.com/login/)**: recharge, sender IDs, logs
- **[Other Integrations](https://www.kwtsms.com/integrations.html)**: plugins for other platforms

---

License
-------

[](#license)

MIT

###  Health Score

38

—

LowBetter than 86% of packages

Maintenance99

Actively maintained with recent releases

Popularity3

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity39

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% 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 ~2 days

Total

8

Last Release

5d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/4f35fd30b8e83a930879ee05a2bbfb0b093f5cfd36dd88db725df4eff4002d06?d=identicon)[kwtsms](/maintainers/kwtsms)

---

Top Contributors

[![boxlinknet](https://avatars.githubusercontent.com/u/130248345?v=4)](https://github.com/boxlinknet "boxlinknet (28 commits)")

---

Tags

composerkuwaitkwtsmspackagistphpsmssms-apiotpnotificationsmsmessagingsms-gatewaykuwaitkwtsms

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/kwtsms-kwtsms-php/health.svg)

```
[![Health](https://phpackages.com/badges/kwtsms-kwtsms-php/health.svg)](https://phpackages.com/packages/kwtsms-kwtsms-php)
```

###  Alternatives

[laravel-notification-channels/twilio

Provides Twilio notification channel for Laravel

2587.4M9](/packages/laravel-notification-channels-twilio)[ghasedak/php

ghasedak sms gateway package for PHP

2044.2k7](/packages/ghasedak-php)[gr8shivam/laravel-sms-api

A modern, flexible Laravel package for integrating any SMS gateway with REST API support

10238.1k](/packages/gr8shivam-laravel-sms-api)[taiwan-sms/every8d

every8d sms api client

1817.6k](/packages/taiwan-sms-every8d)

PHPackages © 2026

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