{"id":49888346,"url":"https://github.com/boxlinknet/kwtsms-ruby","last_synced_at":"2026-05-15T19:33:59.955Z","repository":{"id":342576405,"uuid":"1174086885","full_name":"boxlinknet/kwtsms-ruby","owner":"boxlinknet","description":"Ruby client for the kwtSMS API (kwtsms.com). Send SMS, check balance, validate numbers.","archived":false,"fork":false,"pushed_at":"2026-03-15T00:33:12.000Z","size":91,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-15T09:27:37.845Z","etag":null,"topics":["api-client","kuwait","kwtsms","ruby","ruby-gem","sms","sms-api","sms-gateway"],"latest_commit_sha":null,"homepage":"https://www.kwtsms.com/developers.html","language":"Ruby","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":"SECURITY.md","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-06T03:51:37.000Z","updated_at":"2026-03-15T00:31:33.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/boxlinknet/kwtsms-ruby","commit_stats":null,"previous_names":["boxlinknet/kwtsms-ruby"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/boxlinknet/kwtsms-ruby","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boxlinknet%2Fkwtsms-ruby","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boxlinknet%2Fkwtsms-ruby/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boxlinknet%2Fkwtsms-ruby/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boxlinknet%2Fkwtsms-ruby/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/boxlinknet","download_url":"https://codeload.github.com/boxlinknet/kwtsms-ruby/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boxlinknet%2Fkwtsms-ruby/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33076226,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-15T11:35:32.926Z","status":"ssl_error","status_checked_at":"2026-05-15T11:35:31.362Z","response_time":103,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: 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":["api-client","kuwait","kwtsms","ruby","ruby-gem","sms","sms-api","sms-gateway"],"created_at":"2026-05-15T19:33:59.188Z","updated_at":"2026-05-15T19:33:59.949Z","avatar_url":"https://github.com/boxlinknet.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# kwtSMS Ruby Client\n\n[![Gem Version](https://img.shields.io/gem/v/kwtsms.svg)](https://rubygems.org/gems/kwtsms)\n[![Gem Downloads](https://img.shields.io/gem/dt/kwtsms.svg)](https://rubygems.org/gems/kwtsms)\n[![CI](https://github.com/boxlinknet/kwtsms-ruby/actions/workflows/ci.yml/badge.svg)](https://github.com/boxlinknet/kwtsms-ruby/actions/workflows/ci.yml)\n[![CodeQL](https://github.com/boxlinknet/kwtsms-ruby/actions/workflows/codeql.yml/badge.svg)](https://github.com/boxlinknet/kwtsms-ruby/actions/workflows/codeql.yml)\n[![Bundle Audit](https://github.com/boxlinknet/kwtsms-ruby/actions/workflows/audit.yml/badge.svg)](https://github.com/boxlinknet/kwtsms-ruby/actions/workflows/audit.yml)\n[![GitGuardian](https://github.com/boxlinknet/kwtsms-ruby/actions/workflows/gitguardian.yml/badge.svg)](https://github.com/boxlinknet/kwtsms-ruby/actions/workflows/gitguardian.yml)\n[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/boxlinknet/kwtsms-ruby/badge)](https://scorecard.dev/viewer/?uri=github.com/boxlinknet/kwtsms-ruby)\n[![Ruby](https://img.shields.io/badge/Ruby-%3E%3D%202.7-red.svg)](https://www.ruby-lang.org/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n\nRuby client for the [kwtSMS API](https://www.kwtsms.com). Send SMS, check balance, validate numbers, list sender IDs, and check coverage.\n\n## About kwtSMS\n\nkwtSMS is a Kuwaiti SMS gateway trusted by top businesses to deliver messages anywhere in the world, with private Sender ID, free API testing, non-expiring credits, and competitive flat-rate pricing. Secure, simple to integrate, built to last. Open a free account in under 1 minute, no paperwork or payment required. [Click here to get started](https://www.kwtsms.com/signup/)\n\n## Prerequisites\n\nYou need **Ruby** (\u003e= 2.7) installed.\n\n### Check if Ruby is installed\n\n```bash\nruby -v\n```\n\nIf not installed, see [ruby-lang.org/en/downloads](https://www.ruby-lang.org/en/downloads/).\n\n## Install\n\n```bash\ngem install kwtsms\n```\n\nOr add to your `Gemfile`:\n\n```ruby\ngem \"kwtsms\"\n```\n\n## Quick Start\n\n```ruby\nrequire \"kwtsms\"\n\nsms = KwtSMS::Client.from_env\n\n# Verify credentials\nok, balance, err = sms.verify\nputs \"Balance: #{balance}\" if ok\n\n# Send SMS\nresult = sms.send_sms(\"96598765432\", \"Your OTP for MyApp is: 123456\")\nputs \"msg-id: #{result['msg-id']}\" if result[\"result\"] == \"OK\"\n```\n\n## Configuration\n\n### Environment Variables\n\nCreate a `.env` file or set environment variables:\n\n```ini\nKWTSMS_USERNAME=ruby_username\nKWTSMS_PASSWORD=ruby_password\nKWTSMS_SENDER_ID=YOUR-SENDER\nKWTSMS_TEST_MODE=1\nKWTSMS_LOG_FILE=kwtsms.log\n```\n\n### Direct Construction\n\n```ruby\nsms = KwtSMS::Client.new(\n  \"ruby_username\",\n  \"ruby_password\",\n  sender_id: \"YOUR-SENDER\",\n  test_mode: true,\n  log_file: \"kwtsms.log\"\n)\n```\n\n## API Reference\n\n### verify\n\nTest credentials and check balance.\n\n```ruby\nok, balance, err = sms.verify\n# ok:      true/false\n# balance: Float or nil\n# err:     nil or error message string\n```\n\n### balance\n\nGet current balance.\n\n```ruby\nbalance = sms.balance  # Float or nil\n```\n\n### send_sms\n\nSend SMS to one or more numbers.\n\n```ruby\n# Single number\nresult = sms.send_sms(\"96598765432\", \"Hello!\")\n\n# Multiple numbers\nresult = sms.send_sms([\"96598765432\", \"96512345678\"], \"Bulk message\")\n\n# Override sender ID\nresult = sms.send_sms(\"96598765432\", \"Hello!\", sender: \"MY-APP\")\n```\n\nResponse on success:\n```ruby\n{\n  \"result\" =\u003e \"OK\",\n  \"msg-id\" =\u003e \"12345\",\n  \"numbers\" =\u003e 1,\n  \"points-charged\" =\u003e 1,\n  \"balance-after\" =\u003e 149.0\n}\n```\n\n**Never call `balance` after `send_sms`.** The send response already includes your updated balance in `balance-after`.\n\n### send_with_retry\n\nSend with automatic retry on ERR028 (15-second rate limit).\n\n```ruby\nresult = sms.send_with_retry(\"96598765432\", \"Hello!\", max_retries: 3)\n```\n\n### senderids\n\nList sender IDs registered on your account.\n\n```ruby\nresult = sms.senderids\nputs result[\"senderids\"]  # =\u003e [\"KWT-SMS\", \"MY-APP\"]\n```\n\n### coverage\n\nList active country prefixes.\n\n```ruby\nresult = sms.coverage\n```\n\n### validate\n\nValidate phone numbers.\n\n```ruby\nresult = sms.validate([\"96598765432\", \"invalid\", \"+96512345678\"])\nputs result[\"ok\"]        # valid numbers\nputs result[\"er\"]        # error numbers\nputs result[\"rejected\"]  # locally rejected with error messages\n```\n\n## Utility Functions\n\n```ruby\n# Normalize phone number: Arabic digits, strip non-digits, strip leading zeros\nKwtSMS.normalize_phone(\"+965 9876 5432\")  # =\u003e \"96598765432\"\n\n# Validate phone input (returns [valid, error, normalized])\nvalid, error, normalized = KwtSMS.validate_phone_input(\"user@email.com\")\n# =\u003e [false, \"'user@email.com' is an email address, not a phone number\", \"\"]\n\n# Clean message: strip emojis, HTML, hidden chars, convert Arabic digits\nKwtSMS.clean_message(\"Hello \\u{1F600} \u003cb\u003eworld\u003c/b\u003e\")  # =\u003e \"Hello  world\"\n\n# Enrich error with developer-friendly action message\nKwtSMS.enrich_error({\"result\" =\u003e \"ERROR\", \"code\" =\u003e \"ERR003\"})\n# =\u003e adds \"action\" key with guidance\n\n# Access all error codes\nKwtSMS::API_ERRORS  # =\u003e Hash of all error codes with action messages\n```\n\n## Bulk Send (\u003e200 Numbers)\n\nWhen passing more than 200 numbers to `send_sms`, the library automatically:\n\n1. Splits into batches of 200\n2. Sends each batch with a 0.5s delay\n3. Retries ERR013 (queue full) up to 3 times with 30s/60s/120s backoff\n4. Returns aggregated result: `OK`, `PARTIAL`, or `ERROR`\n\n```ruby\nresult = sms.send_sms(large_number_list, \"Announcement\")\nputs result[\"batches\"]         # number of batches\nputs result[\"msg-ids\"]         # array of message IDs\nputs result[\"points-charged\"]  # total points\nputs result[\"balance-after\"]   # final balance\nputs result[\"errors\"]          # any batch errors\n```\n\n## Phone Number Formats\n\nAll formats are accepted and normalized automatically:\n\n| Input | Normalized | Valid? |\n|-------|-----------|--------|\n| `96598765432` | `96598765432` | Yes |\n| `+96598765432` | `96598765432` | Yes |\n| `0096598765432` | `96598765432` | Yes |\n| `965 9876 5432` | `96598765432` | Yes |\n| `965-9876-5432` | `96598765432` | Yes |\n| `(965) 98765432` | `96598765432` | Yes |\n| `٩٦٥٩٨٧٦٥٤٣٢` | `96598765432` | Yes |\n| `۹۶۵۹۸۷۶۵۴۳۲` | `96598765432` | Yes |\n| `+٩٦٥٩٨٧٦٥٤٣٢` | `96598765432` | Yes |\n| `٠٠٩٦٥٩٨٧٦٥٤٣٢` | `96598765432` | Yes |\n| `٩٦٥ ٩٨٧٦ ٥٤٣٢` | `96598765432` | Yes |\n| `٩٦٥-٩٨٧٦-٥٤٣٢` | `96598765432` | Yes |\n| `965٩٨٧٦٥٤٣٢` | `96598765432` | Yes |\n| `123456` (too short) | rejected | No |\n| `user@gmail.com` | rejected | No |\n\nNormalization rules:\n- Arabic-Indic and Extended Arabic-Indic digits converted to Latin\n- Non-digit characters stripped (`+`, spaces, dashes, dots, brackets)\n- Leading zeros stripped (handles `00` country code prefix)\n- Duplicate numbers deduplicated before sending\n- Invalid numbers rejected locally with clear error messages\n\n## Message Cleaning\n\nMessages are cleaned automatically before sending to prevent silent delivery failures:\n\n- Emojis stripped (cause messages to get stuck in queue)\n- HTML tags stripped (causes ERR027)\n- Hidden characters stripped (BOM, zero-width spaces, soft hyphens, directional marks)\n- Arabic-Indic digits converted to Latin\n- C0/C1 control characters removed (except `\\n` and `\\t`)\n\n## CLI\n\nFor a standalone CLI tool that works on all operating systems, see [kwtsms-cli](https://github.com/boxlinknet/kwtsms-cli).\n\n## Credential Management\n\n**Never hardcode credentials in source code.** Credentials must be changeable without recompiling or redeploying.\n\n### Environment variables (recommended for servers)\n\n```ruby\nsms = KwtSMS::Client.from_env  # reads KWTSMS_USERNAME, KWTSMS_PASSWORD, etc.\n```\n\n### Rails initializer\n\n```ruby\n# config/initializers/kwtsms.rb\nKWTSMS_CLIENT = KwtSMS::Client.from_env\n```\n\n### Constructor injection (for custom config systems)\n\n```ruby\nsms = KwtSMS::Client.new(\n  config[:username],\n  config[:password],\n  sender_id: config[:sender_id]\n)\n```\n\n## Best Practices\n\n### Validate before calling the API\n\n```ruby\nvalid, error, normalized = KwtSMS.validate_phone_input(user_input)\nunless valid\n  # Don't waste an API call on invalid input\n  return { error: error }\nend\nresult = sms.send_sms(normalized, message)\n```\n\n### User-facing error messages\n\nNever expose raw API errors to end users:\n\n| Situation | API Code | Show to User |\n|-----------|----------|--------------|\n| Invalid phone | ERR006, ERR025 | \"Please enter a valid phone number in international format.\" |\n| Auth error | ERR003 | \"SMS service temporarily unavailable. Please try again later.\" |\n| No balance | ERR010, ERR011 | \"SMS service temporarily unavailable. Please try again later.\" |\n| Rate limited | ERR028 | \"Please wait a moment before requesting another code.\" |\n| Content rejected | ERR031, ERR032 | \"Your message could not be sent. Please try again with different content.\" |\n\n### Sender ID\n\n- `KWT-SMS` is a shared test sender: delays, blocked on some carriers. Never use in production.\n- Register a private Sender ID at kwtsms.com (takes ~5 working days for Kuwait).\n- **Sender ID is case sensitive:** `Kuwait` is not the same as `KUWAIT`.\n- **For OTP, use Transactional Sender ID.** Promotional IDs are filtered by DND on Zain and Ooredoo.\n\n### Timezone\n\n`unix-timestamp` in API responses is GMT+3 (Asia/Kuwait server time), not UTC. Always convert when storing or displaying. Log timestamps written by this client are UTC.\n\n### Security Checklist\n\nBefore going live:\n\n- [ ] Bot protection enabled (CAPTCHA for web)\n- [ ] Rate limit per phone number (max 3-5/hour)\n- [ ] Rate limit per IP address (max 10-20/hour)\n- [ ] Rate limit per user/session if authenticated\n- [ ] Monitoring/alerting on abuse patterns\n- [ ] Admin notification on low balance\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\n## JSONL Logging\n\nEvery API call is logged to a JSONL file (default: `kwtsms.log`):\n\n```json\n{\"ts\":\"2026-03-06T12:00:00Z\",\"endpoint\":\"send\",\"request\":{\"username\":\"ruby_username\",\"password\":\"***\",\"mobile\":\"96598765432\",\"message\":\"Hello\"},\"response\":{\"result\":\"OK\",\"msg-id\":\"12345\"},\"ok\":true,\"error\":null}\n```\n\nPasswords are always masked as `***`. Logging never crashes the main flow.\n\nDisable logging by setting `log_file: \"\"` in the constructor.\n\n## Examples\n\nSee the [examples/](examples/) directory:\n\n| # | Example | Description |\n|---|---------|-------------|\n| 00 | [Raw API](examples/00_raw_api.rb) | Call all 7 endpoints directly, no gem needed |\n| 01 | [Basic Usage](examples/01_basic_usage.rb) | Connect, verify, send SMS, validate |\n| 02 | [OTP Flow](examples/02_otp_flow.rb) | Send OTP codes |\n| 03 | [Bulk SMS](examples/03_bulk_sms.rb) | Send to many recipients |\n| 04 | [Rails Endpoint](examples/04_rails_endpoint.rb) | Rails controller |\n| 05 | [Error Handling](examples/05_error_handling.rb) | Handle every error type |\n| 06 | [OTP Production](examples/06-otp-production/) | Production OTP with rate limiting, CAPTCHA, Redis |\n\n## Requirements\n\n- Ruby \u003e= 2.7\n- No external runtime dependencies\n\n## Publishing to RubyGems\n\n```bash\n# 1. Create account at https://rubygems.org/sign_up\n# 2. Build the gem\ngem build kwtsms.gemspec\n\n# 3. Push to RubyGems\ngem push kwtsms-0.1.0.gem\n\n# 4. Or use the automated GitHub Actions workflow:\n#    Push a tag and it publishes automatically\ngit tag v0.1.0\ngit push origin v0.1.0\n```\n\n## FAQ\n\n**1. My message was sent successfully (result: OK) but the recipient didn't receive it. What happened?**\n\nCheck the **Sending Queue** at [kwtsms.com](https://www.kwtsms.com/login/). If your message is stuck there, it was accepted by the API but not dispatched. Common causes are emoji in the message, hidden characters from copy-pasting, or spam filter triggers. Delete it from the queue to recover your credits. Also verify that `test` mode is off (`KWTSMS_TEST_MODE=0`). Test messages are queued but never delivered.\n\n**2. What is the difference between Test mode and Live mode?**\n\n**Test mode** (`KWTSMS_TEST_MODE=1`) sends your message to the kwtSMS queue but does NOT deliver it to the handset. No SMS credits are consumed. Use this during development. **Live mode** (`KWTSMS_TEST_MODE=0`) delivers the message for real and deducts credits. Always develop in test mode and switch to live only when ready for production.\n\n**3. What is a Sender ID and why should I not use \"KWT-SMS\" in production?**\n\nA **Sender ID** is the name that appears as the sender on the recipient's phone (e.g., \"MY-APP\" instead of a random number). `KWT-SMS` is a shared test sender. It causes delivery delays, is blocked on Virgin Kuwait, and should never be used in production. Register your own private Sender ID through your kwtSMS account. For OTP/authentication messages, you need a **Transactional** Sender ID to bypass DND (Do Not Disturb) filtering.\n\n**4. I'm getting ERR003 \"Authentication error\". What's wrong?**\n\nYou are using the wrong credentials. The API requires your **API username and API password**, NOT your account mobile number. Log in to [kwtsms.com](https://www.kwtsms.com/login/), go to Account, and check your API credentials. Also make sure you are using POST (not GET) and `Content-Type: application/json`.\n\n**5. Can I send to international numbers (outside Kuwait)?**\n\nInternational sending is **disabled by default** on kwtSMS accounts. Log in to your [kwtSMS dashboard](https://www.kwtsms.com/login/) and add coverage for the country prefixes you need. Use `coverage()` to check which countries are currently active on your account. Be aware that activating international coverage increases exposure to automated abuse. Implement rate limiting and CAPTCHA before enabling.\n\n## Help \u0026 Support\n\n- **[kwtSMS FAQ](https://www.kwtsms.com/faq/)**: Answers to common questions about credits, sender IDs, OTP, and delivery\n- **[kwtSMS Support](https://www.kwtsms.com/support.html)**: Open a support ticket or browse help articles\n- **[Contact kwtSMS](https://www.kwtsms.com/#contact)**: Reach the kwtSMS team directly for Sender ID registration and account issues\n- **[API Documentation (PDF)](https://www.kwtsms.com/doc/KwtSMS.com_API_Documentation_v41.pdf)**: kwtSMS REST API v4.1 full reference\n- **[Best Practices](https://www.kwtsms.com/articles/sms-api-implementation-best-practices.html)**: SMS API implementation best practices\n- **[Integration Test Checklist](https://www.kwtsms.com/articles/sms-api-integration-test-checklist.html)**: Pre-launch testing checklist\n- **[Sender ID Help](https://www.kwtsms.com/sender-id-help.html)**: Sender ID registration and troubleshooting guide\n- **[kwtSMS Dashboard](https://www.kwtsms.com/login/)**: Recharge credits, buy Sender IDs, view message logs, manage coverage\n- **[Other Integrations](https://www.kwtsms.com/integrations.html)**: Plugins and integrations for other platforms and languages\n- **[RubyGems](https://rubygems.org/gems/kwtsms)**: Package on RubyGems.org\n- **[GitHub](https://github.com/boxlinknet/kwtsms-ruby)**: Source code and issue tracker\n- **[Changelog](CHANGELOG.md)** | **[Contributing](CONTRIBUTING.md)** | **[Security](SECURITY.md)**\n\n## License\n\nMIT License. See [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fboxlinknet%2Fkwtsms-ruby","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fboxlinknet%2Fkwtsms-ruby","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fboxlinknet%2Fkwtsms-ruby/lists"}