{"id":46435911,"url":"https://github.com/boxlinknet/kwtsms-js","last_synced_at":"2026-03-05T21:01:31.624Z","repository":{"id":342211958,"uuid":"1173189963","full_name":"boxlinknet/kwtsms-js","owner":"boxlinknet","description":"JavaScript/TypeScript client for the kwtSMS API. Send SMS, validate Kuwaiti numbers, check balance. Zero dependencies.","archived":false,"fork":false,"pushed_at":"2026-03-05T07:05:44.000Z","size":130,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-03-05T12:10:44.629Z","etag":null,"topics":["javascript","kuwait","otp","sms","sms-api","typescript"],"latest_commit_sha":null,"homepage":"https://www.kwtsms.com/integrations.html","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/boxlinknet.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-05T05:11:57.000Z","updated_at":"2026-03-05T07:05:46.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/boxlinknet/kwtsms-js","commit_stats":null,"previous_names":["boxlinknet/kwtsms-js"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/boxlinknet/kwtsms-js","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boxlinknet%2Fkwtsms-js","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boxlinknet%2Fkwtsms-js/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boxlinknet%2Fkwtsms-js/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boxlinknet%2Fkwtsms-js/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/boxlinknet","download_url":"https://codeload.github.com/boxlinknet/kwtsms-js/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boxlinknet%2Fkwtsms-js/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30141296,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-05T16:58:46.102Z","status":"ssl_error","status_checked_at":"2026-03-05T16:58:45.706Z","response_time":93,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["javascript","kuwait","otp","sms","sms-api","typescript"],"created_at":"2026-03-05T21:00:54.712Z","updated_at":"2026-03-05T21:01:31.612Z","avatar_url":"https://github.com/boxlinknet.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# kwtsms\n\nJavaScript/TypeScript client for the [kwtSMS SMS API](https://www.kwtsms.com). Send SMS, validate numbers, check balance. Zero runtime dependencies.\n\n[![npm version](https://badge.fury.io/js/kwtsms.svg)](https://www.npmjs.com/package/kwtsms)\n[![CI](https://github.com/boxlinknet/kwtsms-js/actions/workflows/ci.yml/badge.svg)](https://github.com/boxlinknet/kwtsms-js/actions/workflows/ci.yml)\n\n## Install\n\n```bash\nnpm install kwtsms\n# or\nyarn add kwtsms\n# or\npnpm add kwtsms\n# or\nbun add kwtsms\n```\n\nWorks in Node.js 16+ and Bun. Uses Node.js built-in modules (`node:https`, `node:fs`).\n\n## Quick Start\n\n**TypeScript / ESM:**\n```typescript\nimport { KwtSMS } from 'kwtsms';\n\nconst sms = KwtSMS.fromEnv(); // reads from .env or environment variables\n\nconst [ok, balance, err] = await sms.verify();\nif (ok) console.log(`Balance: ${balance}`);\n\nconst result = await sms.send('96598765432', 'Your OTP for MYAPP is: 123456');\nif (result.result === 'OK') {\n  console.log(`Sent! msg-id: ${result['msg-id']}, balance-after: ${result['balance-after']}`);\n  // Always save msg-id — you need it for status checks later\n} else {\n  console.error(`Failed: ${result.code}: ${result.description}`);\n  if (result.action) console.error(`What to do: ${result.action}`);\n}\n```\n\n**JavaScript / CommonJS:**\n```javascript\nconst { KwtSMS } = require('kwtsms');\n\nconst sms = KwtSMS.fromEnv();\nconst result = await sms.send('96598765432', 'Hello from kwtsms!');\n```\n\n## Setup\n\nCreate a `.env` file (never commit this file):\n```ini\nKWTSMS_USERNAME=your_api_user\nKWTSMS_PASSWORD=your_api_pass\nKWTSMS_SENDER_ID=YOUR-SENDERID\nKWTSMS_TEST_MODE=1\nKWTSMS_LOG_FILE=kwtsms.log\n```\n\nOr use the interactive CLI wizard:\n```bash\nnpx kwtsms setup\n```\n\nOr pass credentials directly:\n```typescript\nconst sms = new KwtSMS('your_api_user', 'your_api_pass', {\n  senderId: 'MY-APP',\n  testMode: false,\n  logFile: 'kwtsms.log',\n});\n```\n\n\u003e **Important:** `KWTSMS_USERNAME` and `KWTSMS_PASSWORD` are your **API credentials**, not your account mobile number. Find them at kwtsms.com → Account → API.\n\n## All Methods\n\n### `KwtSMS.fromEnv(envFile?)`\nLoad credentials from environment variables, falling back to `.env` file.\n\n```typescript\nconst sms = KwtSMS.fromEnv();           // reads .env in current directory\nconst sms = KwtSMS.fromEnv('/app/.env'); // custom path\n```\n\n### `sms.verify()`\nTest credentials. Returns `[ok, balance, error]`. Never throws.\n\n```typescript\nconst [ok, balance, err] = await sms.verify();\n```\n\n### `sms.balance()`\nGet current balance. Returns `number | null`.\n\n```typescript\nconst bal = await sms.balance();\n```\n\n\u003e **Tip:** Never call `balance()` after `send()`. The send response already includes `balance-after`. Save it. No extra API call needed.\n\n### `sms.send(mobile, message, sender?)`\nSend SMS to one or more numbers. Validates inputs and cleans the message automatically.\n\n```typescript\n// Single number\nconst result = await sms.send('96598765432', 'Your OTP is: 123456');\n\n// Multiple numbers\nconst result = await sms.send(['96598765432', '+96512345678'], 'Hello all!');\n\n// Override sender for this call\nconst result = await sms.send('96598765432', 'Alert!', 'OTHER-ID');\n```\n\nPhone numbers are normalized automatically: `+`, `00`, spaces, dashes, and Arabic digits are all handled.\n\nFor \u003e200 numbers: automatically split into batches of 200 with 0.5s delay between batches.\n\n**Response (single batch):**\n```json\n{\n  \"result\": \"OK\",\n  \"msg-id\": \"f4c841adee210f31307633ceaebff2ec\",\n  \"numbers\": 1,\n  \"points-charged\": 1,\n  \"balance-after\": 180\n}\n```\n\n\u003e **Always save `msg-id` immediately.** You need it for status checks and delivery reports. It cannot be retrieved later.\n\n\u003e **`unix-timestamp` in responses is GMT+3 (Asia/Kuwait server time)**, not UTC. Always convert when storing.\n\n### `sms.validate(phones[])`\nValidate phone numbers before sending.\n\n```typescript\nconst result = await sms.validate(['+96598765432', '0096512345678', 'bad-number']);\n// result.ok  — valid and routable\n// result.er  — format error\n// result.nr  — no route (country not activated)\n// result.rejected — locally rejected with error messages\n```\n\n### `sms.senderids()`\nList sender IDs registered on your account.\n\n```typescript\nconst result = await sms.senderids();\nif (result.result === 'OK') console.log(result.senderids);\n```\n\n### `sms.coverage()`\nList active country prefixes.\n\n```typescript\nconst result = await sms.coverage();\n```\n\n## Utility Functions\n\n```typescript\nimport { normalizePhone, validatePhoneInput, cleanMessage } from 'kwtsms';\n\nnormalizePhone('+96598765432');     // → '96598765432'\nnormalizePhone('00 965 9876 5432'); // → '96598765432'\n\nconst [valid, error, normalized] = validatePhoneInput('+96598765432');\n// [true, null, '96598765432']\n\ncleanMessage('Hello 😀 \u003cb\u003eWorld\u003c/b\u003e \\uFEFF'); // → 'Hello  World '\n```\n\n## CLI\n\n```bash\nkwtsms setup                                         # interactive setup wizard\nkwtsms verify                                        # test credentials, show balance\nkwtsms balance                                       # show balance\nkwtsms senderid                                      # list sender IDs\nkwtsms coverage                                      # list active country prefixes\nkwtsms send 96598765432 \"Your OTP is: 123456\"\nkwtsms send 96598765432,96512345678 \"Hello\" --sender MY-APP\nkwtsms validate +96598765432 0096512345678 abc\n```\n\nInstall globally for direct use:\n```bash\nnpm install -g kwtsms\n```\n\nOr use without installing:\n```bash\nnpx kwtsms setup\n```\n\n## Credential Management\n\n**Never hardcode credentials.** They must be changeable without redeploying.\n\n### Recommended patterns:\n\n**1. Environment variables / .env file (default)**\n```typescript\nconst sms = KwtSMS.fromEnv(); // reads KWTSMS_USERNAME, KWTSMS_PASSWORD from env or .env\n```\nAdd `.env` to `.gitignore`. Update credentials by editing the file.\n\n**2. Admin settings UI (web apps)**\nStore credentials in your database. Load and pass to constructor:\n```typescript\nconst creds = await db.getSettings('kwtsms');\nconst sms = new KwtSMS(creds.username, creds.password, { senderId: creds.senderId });\n```\n\n**3. Secrets manager (production)**\n```typescript\nconst secret = await secretsManager.getSecret('kwtsms-credentials');\nconst sms = new KwtSMS(secret.username, secret.password);\n```\n\n**Never:**\n- Hardcode credentials in source code\n- Commit `.env` files to git\n- Put credentials in client-side JavaScript\n\n## Best Practices\n\n### 1. Save msg-id and balance-after from every send\n\n```typescript\nconst result = await sms.send(phone, message);\nif (result.result === 'OK') {\n  await db.save({ msgId: result['msg-id'], balance: result['balance-after'] });\n  // You NEED msg-id later for status/DLR checks\n  // balance-after eliminates the need for a separate balance() call\n}\n```\n\n### 2. Validate before calling the API\n\n```typescript\nconst [valid, error, normalized] = validatePhoneInput(userPhone);\nif (!valid) return { error }; // never hits API for invalid input\n\nconst prefixes = await sms.coverage(); // cache this at startup\nif (!isCountryActive(normalized, prefixes)) return { error: 'Country not supported' };\n\nconst result = await sms.send(normalized, message);\n```\n\n### 3. OTP implementation\n\n```typescript\n// Always include app name (telecom compliance requirement)\nconst otpMessage = `Your OTP for MYAPP is: ${otp}`;\n\n// Always use Transactional Sender ID for OTP\n// Promotional IDs are silently blocked on DND numbers (credits still deducted)\nconst result = await sms.send(phone, otpMessage, 'MY-TRANS-ID');\n\n// One number per OTP request (avoids ERR028 rate limit affecting entire batch)\n// Minimum 3-4 min resend timer, 3-5 min expiry, new code on every resend\n```\n\n### 4. Show user-friendly errors, not raw API codes\n\n```typescript\nconst USER_ERRORS: Record\u003cstring, string\u003e = {\n  ERR025: 'Please enter a valid phone number in international format (e.g., +965 9876 5432).',\n  ERR028: 'Please wait a moment before requesting another code.',\n  ERR026: 'SMS delivery to this country is not available.',\n};\n\nif (result.result === 'ERROR') {\n  const userMsg = USER_ERRORS[result.code ?? '']\n    ?? 'SMS service temporarily unavailable. Please try again.';\n  // Log result.action for admin — never show raw API errors to end users\n}\n```\n\n### 5. Server timezone\n\n`unix-timestamp` in all API responses is **GMT+3 (Asia/Kuwait)**, not UTC. Convert when storing:\n```typescript\nconst serverTime = new Date(result['unix-timestamp'] * 1000);\n// This is GMT+3. Subtract 3 hours for UTC if needed.\n```\n\n### 6. Sender ID types\n\n| | Promotional | Transactional |\n|--|-------------|---------------|\n| Use for | Bulk SMS, marketing | OTP, alerts, notifications |\n| DND delivery | Blocked (credits lost) | Bypasses DND |\n| Cost | 10 KD | 15 KD |\n\n**For OTP, always use Transactional.** Using Promotional for OTP means messages to DND numbers are silently blocked and credits are still deducted.\n\n`KWT-SMS` is a shared test sender. Never use in production.\n\n## Security Checklist\n\nBefore going live, verify all of these:\n\n```\n[ ] CAPTCHA enabled on all SMS-triggering forms\n[ ] Rate limit per phone number (max 3-5 OTP requests/hour)\n[ ] Rate limit per IP address (max 10-20 requests/hour)\n[ ] Rate limit per user/session if authenticated\n[ ] .env file is in .gitignore and never committed\n[ ] Credentials stored securely (not hardcoded, not in client-side code)\n[ ] Test mode OFF (KWTSMS_TEST_MODE=0)\n[ ] Private Sender ID registered (not KWT-SMS)\n[ ] Transactional Sender ID for OTP (not Promotional)\n[ ] Admin notification on low balance\n[ ] Monitoring on failed sends and error rate spikes\n```\n\n- **OTP / sensitive messages:** set `logFile: ''` to disable logging, or ensure `kwtsms.log` has `chmod 600`. Log entries include message bodies and phone numbers (passwords are always masked).\n\n## Error Codes\n\n| Code | Meaning | Action |\n|------|---------|--------|\n| ERR001 | API disabled | Enable at kwtsms.com → Account → API |\n| ERR003 | Wrong credentials | Check KWTSMS_USERNAME and KWTSMS_PASSWORD |\n| ERR006 | No valid numbers | Include country code (e.g., 96598765432) |\n| ERR008 | Sender ID banned/not found | Check registered sender IDs (case-sensitive) |\n| ERR010 | Zero balance | Recharge at kwtsms.com |\n| ERR011 | Insufficient balance | Buy more credits |\n| ERR013 | Queue full | Library retries automatically |\n| ERR024 | IP not whitelisted | Add IP at Account → API → IP Lockdown |\n| ERR026 | Country not activated | Contact kwtSMS support |\n| ERR028 | 15s rate limit | Wait before resending to same number |\n| ERR031/032 | Rejected (language/spam) | Review message content |\n\nFull error table with all 33 codes: see [kwtSMS API docs](https://www.kwtsms.com/doc/KwtSMS.com_API_Documentation_v41.pdf).\n\n## Publishing (for maintainers)\n\n```bash\n# 1. Bump version\nnpm version patch   # 0.1.0 → 0.1.1  (bug fix)\nnpm version minor   # 0.1.x → 0.2.0  (new feature)\nnpm version major   # 0.x.x → 1.0.0  (breaking change)\n\n# 2. Build\nnpm run build\n\n# 3. Dry run — review what will be uploaded\nnpm publish --dry-run\n\n# 4. Publish\nnpm publish --access public\n\n# 5. Tag and push\ngit push \u0026\u0026 git push --tags\n```\n\n## License\n\nMIT. See [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fboxlinknet%2Fkwtsms-js","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fboxlinknet%2Fkwtsms-js","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fboxlinknet%2Fkwtsms-js/lists"}