kwtsms/kwtsms-php
Composer 安装命令:
composer require kwtsms/kwtsms-php
包简介
kwtSMS API Client for PHP. Official library to interface with the Kuwait SMS gateway (kwtsms.com)
README 文档
README
kwtSMS API Client for PHP. Official library to interface with the Kuwait SMS gateway (kwtsms.com).
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
Requirements
- PHP 7.4+
ext-curlext-json
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
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
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:
- System environment variables (Docker, CI, server config)
.envfile 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
$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
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()
Fetch current credit balance.
$balance = $sms->balance(); // float|null
Returns: float on success, null on failure. Always makes an API call.
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 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 >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 / >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 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()
List Sender IDs registered on the account.
$result = $sms->senderids(); print_r($result['senderids']); // ['MY-APP', 'KWT-SMS']
coverage()
List active country prefixes on the account.
$result = $sms->coverage(); print_r($result['prefixes']);
status(string $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)
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
PhoneUtils::validate_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):
- Empty check
- Email detection (
@present) - Arabic/Hindi digit conversion + non-digit stripping
- Leading zero stripping
- Local trunk digit
0stripped when country code is matched (e.g.966 055...→966 55...) - Length check: 7–15 digits
- 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()
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()
Normalize without validating. Useful for bulk pre-processing.
$normalized = PhoneUtils::normalize_phone('+965 9876-5432'); // "96598765432"
MessageUtils::clean_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:
| Step | What | Why |
|---|---|---|
| 0 | Invalid UTF-8 byte sequences stripped | Prevents silent json_encode failure (ERR999) on malformed input |
| 1 | HTML tags | Prevents ERR027 |
| 2 | Arabic/Hindi digits converted to Latin | OTP codes render consistently on all handsets |
| 3 | Hidden 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 rejection |
| 4 | Emojis: 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 returned |
| 5 | Other control characters | Prevents encoding issues |
Newlines (\n, \r), tabs (\t), Arabic text, and Latin punctuation are preserved.
Phone Number Formats
The library normalizes all of these automatically:
| Input | Normalized |
|---|---|
+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
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.
| Situation | Error code | Recommended user message |
|---|---|---|
| Invalid phone number | ERR006, ERR025 | "Please enter a valid phone number." |
| Wrong credentials | ERR003 | "SMS service is temporarily unavailable." (log and alert admin) |
| No balance | ERR010, ERR011 | "SMS service is temporarily unavailable." (alert admin to top up) |
| Country not supported | ERR026 | "SMS delivery to this country is not available." |
| Rate limited | ERR028 | "Please wait before requesting another code." |
| Message rejected | ERR031, ERR032 | "Your message could not be sent. Please try again." |
| Queue full | ERR013 | Handled automatically by bulk retry; surface only if all retries fail |
| Network error | ERR999 | "Could not connect to SMS service. Please try again." |
CLI
The CLI tool has moved to a dedicated package: kwtsms-cli
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
KWT-SMSis 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
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
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
See examples/ for runnable code covering every use case:
| # | File | What it covers |
|---|---|---|
| 01 | 01-quickstart.php |
Verify credentials, send first SMS |
| 02 | 02-otp.php |
Basic OTP flow |
| 03 | 03-bulk.php |
Bulk send, auto-batching |
| 04 | 04-validation.php |
Phone number validation |
| 05 | 05-error-handling.php |
Error categories and retry logic |
| 06 | 06-message-cleaning.php |
Message cleaning internals |
| 07 | 07-laravel.php |
Laravel Service Provider and Notification channel |
| 08 | 08-wordpress.php |
WordPress plugin, WooCommerce, 2FA login |
| 09 | 09-otp-production.php |
Production OTP: DB, CAPTCHA, rate limiting, brute-force protection |
Full documentation for each example is in examples/docs/.
Help & Support
- kwtSMS FAQ: credits, sender IDs, OTP, delivery
- kwtSMS Support: tickets and help articles
- API Documentation (PDF): REST API v4.1 reference
- kwtSMS Dashboard: recharge, sender IDs, logs
- Other Integrations: plugins for other platforms
License
MIT
kwtsms/kwtsms-php 适用场景与选型建议
kwtsms/kwtsms-php 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 79 次下载、GitHub Stars 达 0, 最近一次更新时间为 2026 年 03 月 18 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「sms」 「messaging」 「notification」 「otp」 「sms-gateway」 「kuwait」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 kwtsms/kwtsms-php 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 kwtsms/kwtsms-php 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 kwtsms/kwtsms-php 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
This package makes it easy to send notifications via AfricasTalking with Laravel
Apple Push Notification & Feedback Provider
Throttle notifications on a per-channel basis
SDK for payment gateway PlatbaMobilom.sk for PHP7.0
PHP SDK for Mobile Message SMS API
Extensible library for building notifications and sending them via different delivery channels.
统计信息
- 总下载量: 79
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 35
- 依赖项目数: 1
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-03-18