Documentation
¶
Overview ¶
Package kwtsms provides a Go client for the kwtSMS SMS API (kwtsms.com).
Zero external dependencies. Uses only the Go standard library.
Quick start:
sms, err := kwtsms.FromEnv("")
ok, balance, err := sms.Verify()
result, err := sms.Send("96598765432", "Your OTP for MYAPP is: 123456", "")
report := sms.Validate([]string{"96598765432", "+96512345678"})
balance := sms.Balance()
Server timezone: Asia/Kuwait (GMT+3). unix-timestamp values in API responses are GMT+3 server time, not UTC. Log timestamps written by this client are always UTC ISO-8601.
Index ¶
- Constants
- Variables
- func CleanMessage(text string) string
- func EnrichError(data map[string]any) map[string]any
- func FindCountryCode(normalized string) string
- func LoadEnvFile(path string) map[string]string
- func NormalizePhone(phone string) string
- func ValidatePhoneFormat(normalized string) string
- type BatchError
- type BulkSendResult
- type InvalidEntry
- type KwtSMS
- func (c *KwtSMS) Balance() (float64, error)
- func (c *KwtSMS) CachedBalance() *float64
- func (c *KwtSMS) CachedPurchased() *float64
- func (c *KwtSMS) Coverage() map[string]any
- func (c *KwtSMS) DLR(msgID string) map[string]any
- func (c *KwtSMS) Send(mobile string, message string, sender string) (*SendResult, error)
- func (c *KwtSMS) SendMulti(mobiles []string, message string, sender string) (*SendResult, error)
- func (c *KwtSMS) SendWithRetry(mobile string, message string, sender string, maxRetries int) (*SendResult, error)
- func (c *KwtSMS) SenderIDs() map[string]any
- func (c *KwtSMS) Status(msgID string) map[string]any
- func (c *KwtSMS) Validate(phones []string) ValidateResult
- func (c *KwtSMS) Verify() (bool, float64, error)
- type Option
- type PhoneRule
- type PhoneValidation
- type SendResult
- type ValidateResult
Constants ¶
const Version = "0.4.0"
Version is the library version.
Variables ¶
var APIErrors = map[string]string{
"ERR001": "API is disabled on this account. Enable it at kwtsms.com → Account → API.",
"ERR002": "A required parameter is missing. Check that username, password, sender, mobile, and message are all provided.",
"ERR003": "Wrong API username or password. Check KWTSMS_USERNAME and KWTSMS_PASSWORD. These are your API credentials, not your account mobile number.",
"ERR004": "This account does not have API access. Contact kwtSMS support to enable it.",
"ERR005": "This account is blocked. Contact kwtSMS support.",
"ERR006": "No valid phone numbers. Make sure each number includes the country code (e.g., 96598765432 for Kuwait, not 98765432).",
"ERR007": "Too many numbers in a single request (maximum 200). Split into smaller batches.",
"ERR008": "This sender ID is banned or not found. Sender IDs are case sensitive (\"Kuwait\" is not the same as \"KUWAIT\"). Check your registered sender IDs at kwtsms.com.",
"ERR009": "Message is empty. Provide a non-empty message text.",
"ERR010": "Account balance is zero. Recharge credits at kwtsms.com.",
"ERR011": "Insufficient balance for this send. Buy more credits at kwtsms.com.",
"ERR012": "Message is too long (over 6 SMS pages). Shorten your message.",
"ERR013": "Send queue is full (1000 messages). Wait a moment and try again.",
"ERR019": "No delivery reports found for this message.",
"ERR020": "Message ID does not exist. Make sure you saved the msg-id from the send response.",
"ERR021": "No delivery report available for this message yet.",
"ERR022": "Delivery reports are not ready yet. Try again after 24 hours.",
"ERR023": "Unknown delivery report error. Contact kwtSMS support.",
"ERR024": "Your IP address is not in the API whitelist. Add it at kwtsms.com → Account → API → IP Lockdown, or disable IP lockdown.",
"ERR025": "Invalid phone number. Make sure the number includes the country code (e.g., 96598765432 for Kuwait, not 98765432).",
"ERR026": "This country is not activated on your account. Contact kwtSMS support to enable the destination country.",
"ERR027": "HTML tags are not allowed in the message. Remove any HTML content and try again.",
"ERR028": "You must wait at least 15 seconds before sending to the same number again. No credits were consumed.",
"ERR029": "Message ID does not exist or is incorrect.",
"ERR030": "Message is stuck in the send queue with an error. Delete it at kwtsms.com → Queue to recover credits.",
"ERR031": "Message rejected: bad language detected.",
"ERR032": "Message rejected: spam detected.",
"ERR033": "No active coverage found. Contact kwtSMS support.",
"ERR_INVALID_INPUT": "One or more phone numbers are invalid. See details above.",
}
APIErrors maps every kwtSMS error code to a developer-friendly action message. Exported as read-only reference for callers who want to build custom error UIs.
var CountryNames = map[string]string{
"965": "Kuwait", "966": "Saudi Arabia", "971": "UAE", "973": "Bahrain",
"974": "Qatar", "968": "Oman", "962": "Jordan", "961": "Lebanon",
"970": "Palestine", "964": "Iraq", "963": "Syria", "967": "Yemen",
"98": "Iran", "90": "Turkey", "972": "Israel", "20": "Egypt",
"218": "Libya", "216": "Tunisia", "212": "Morocco", "213": "Algeria", "249": "Sudan",
"27": "South Africa", "234": "Nigeria", "254": "Kenya", "233": "Ghana",
"251": "Ethiopia", "255": "Tanzania", "256": "Uganda", "237": "Cameroon",
"225": "Ivory Coast", "221": "Senegal", "252": "Somalia", "250": "Rwanda",
"44": "UK", "33": "France", "49": "Germany", "39": "Italy", "34": "Spain",
"31": "Netherlands", "32": "Belgium", "41": "Switzerland", "43": "Austria",
"46": "Sweden", "47": "Norway", "45": "Denmark", "48": "Poland",
"420": "Czech Republic", "30": "Greece", "40": "Romania", "36": "Hungary", "380": "Ukraine",
"1": "USA/Canada", "52": "Mexico", "55": "Brazil", "57": "Colombia",
"54": "Argentina", "56": "Chile", "58": "Venezuela", "51": "Peru",
"593": "Ecuador", "53": "Cuba",
"91": "India", "92": "Pakistan", "86": "China", "81": "Japan", "82": "South Korea",
"886": "Taiwan", "65": "Singapore", "60": "Malaysia", "62": "Indonesia",
"63": "Philippines", "66": "Thailand", "84": "Vietnam", "95": "Myanmar",
"855": "Cambodia", "880": "Bangladesh", "94": "Sri Lanka", "960": "Maldives", "976": "Mongolia",
"61": "Australia", "64": "New Zealand",
}
CountryNames maps country codes to human-readable country names.
var PhoneRules = map[string]PhoneRule{ "965": {LocalLengths: []int{8}, MobileStartDigits: []string{"4", "5", "6", "9"}}, "966": {LocalLengths: []int{9}, MobileStartDigits: []string{"5"}}, "971": {LocalLengths: []int{9}, MobileStartDigits: []string{"5"}}, "973": {LocalLengths: []int{8}, MobileStartDigits: []string{"3", "6"}}, "974": {LocalLengths: []int{8}, MobileStartDigits: []string{"3", "5", "6", "7"}}, "968": {LocalLengths: []int{8}, MobileStartDigits: []string{"7", "9"}}, "962": {LocalLengths: []int{9}, MobileStartDigits: []string{"7"}}, "961": {LocalLengths: []int{7, 8}, MobileStartDigits: []string{"3", "7", "8"}}, "970": {LocalLengths: []int{9}, MobileStartDigits: []string{"5"}}, "964": {LocalLengths: []int{10}, MobileStartDigits: []string{"7"}}, "963": {LocalLengths: []int{9}, MobileStartDigits: []string{"9"}}, "967": {LocalLengths: []int{9}, MobileStartDigits: []string{"7"}}, "20": {LocalLengths: []int{10}, MobileStartDigits: []string{"1"}}, "218": {LocalLengths: []int{9}, MobileStartDigits: []string{"9"}}, "216": {LocalLengths: []int{8}, MobileStartDigits: []string{"2", "4", "5", "9"}}, "212": {LocalLengths: []int{9}, MobileStartDigits: []string{"6", "7"}}, "213": {LocalLengths: []int{9}, MobileStartDigits: []string{"5", "6", "7"}}, "249": {LocalLengths: []int{9}, MobileStartDigits: []string{"9"}}, "98": {LocalLengths: []int{10}, MobileStartDigits: []string{"9"}}, "90": {LocalLengths: []int{10}, MobileStartDigits: []string{"5"}}, "972": {LocalLengths: []int{9}, MobileStartDigits: []string{"5"}}, "91": {LocalLengths: []int{10}, MobileStartDigits: []string{"6", "7", "8", "9"}}, "92": {LocalLengths: []int{10}, MobileStartDigits: []string{"3"}}, "880": {LocalLengths: []int{10}, MobileStartDigits: []string{"1"}}, "94": {LocalLengths: []int{9}, MobileStartDigits: []string{"7"}}, "960": {LocalLengths: []int{7}, MobileStartDigits: []string{"7", "9"}}, "86": {LocalLengths: []int{11}, MobileStartDigits: []string{"1"}}, "81": {LocalLengths: []int{10}, MobileStartDigits: []string{"7", "8", "9"}}, "82": {LocalLengths: []int{10}, MobileStartDigits: []string{"1"}}, "886": {LocalLengths: []int{9}, MobileStartDigits: []string{"9"}}, "65": {LocalLengths: []int{8}, MobileStartDigits: []string{"8", "9"}}, "60": {LocalLengths: []int{9, 10}, MobileStartDigits: []string{"1"}}, "62": {LocalLengths: []int{9, 10, 11, 12}, MobileStartDigits: []string{"8"}}, "63": {LocalLengths: []int{10}, MobileStartDigits: []string{"9"}}, "66": {LocalLengths: []int{9}, MobileStartDigits: []string{"6", "8", "9"}}, "84": {LocalLengths: []int{9}, MobileStartDigits: []string{"3", "5", "7", "8", "9"}}, "95": {LocalLengths: []int{9}, MobileStartDigits: []string{"9"}}, "855": {LocalLengths: []int{8, 9}, MobileStartDigits: []string{"1", "6", "7", "8", "9"}}, "976": {LocalLengths: []int{8}, MobileStartDigits: []string{"6", "8", "9"}}, "44": {LocalLengths: []int{10}, MobileStartDigits: []string{"7"}}, "33": {LocalLengths: []int{9}, MobileStartDigits: []string{"6", "7"}}, "49": {LocalLengths: []int{10, 11}, MobileStartDigits: []string{"1"}}, "39": {LocalLengths: []int{10}, MobileStartDigits: []string{"3"}}, "34": {LocalLengths: []int{9}, MobileStartDigits: []string{"6", "7"}}, "31": {LocalLengths: []int{9}, MobileStartDigits: []string{"6"}}, "32": {LocalLengths: []int{9}}, "41": {LocalLengths: []int{9}, MobileStartDigits: []string{"7"}}, "43": {LocalLengths: []int{10}, MobileStartDigits: []string{"6"}}, "47": {LocalLengths: []int{8}, MobileStartDigits: []string{"4", "9"}}, "48": {LocalLengths: []int{9}}, "30": {LocalLengths: []int{10}, MobileStartDigits: []string{"6"}}, "420": {LocalLengths: []int{9}, MobileStartDigits: []string{"6", "7"}}, "46": {LocalLengths: []int{9}, MobileStartDigits: []string{"7"}}, "45": {LocalLengths: []int{8}}, "40": {LocalLengths: []int{9}, MobileStartDigits: []string{"7"}}, "36": {LocalLengths: []int{9}}, "380": {LocalLengths: []int{9}}, "1": {LocalLengths: []int{10}}, "52": {LocalLengths: []int{10}}, "55": {LocalLengths: []int{11}}, "57": {LocalLengths: []int{10}, MobileStartDigits: []string{"3"}}, "54": {LocalLengths: []int{10}, MobileStartDigits: []string{"9"}}, "56": {LocalLengths: []int{9}, MobileStartDigits: []string{"9"}}, "58": {LocalLengths: []int{10}, MobileStartDigits: []string{"4"}}, "51": {LocalLengths: []int{9}, MobileStartDigits: []string{"9"}}, "593": {LocalLengths: []int{9}, MobileStartDigits: []string{"9"}}, "53": {LocalLengths: []int{8}, MobileStartDigits: []string{"5", "6"}}, "27": {LocalLengths: []int{9}, MobileStartDigits: []string{"6", "7", "8"}}, "234": {LocalLengths: []int{10}, MobileStartDigits: []string{"7", "8", "9"}}, "254": {LocalLengths: []int{9}, MobileStartDigits: []string{"1", "7"}}, "233": {LocalLengths: []int{9}, MobileStartDigits: []string{"2", "5"}}, "251": {LocalLengths: []int{9}, MobileStartDigits: []string{"7", "9"}}, "255": {LocalLengths: []int{9}, MobileStartDigits: []string{"6", "7"}}, "256": {LocalLengths: []int{9}, MobileStartDigits: []string{"7"}}, "237": {LocalLengths: []int{9}, MobileStartDigits: []string{"6"}}, "225": {LocalLengths: []int{10}}, "221": {LocalLengths: []int{9}, MobileStartDigits: []string{"7"}}, "252": {LocalLengths: []int{9}, MobileStartDigits: []string{"6", "7"}}, "250": {LocalLengths: []int{9}, MobileStartDigits: []string{"7"}}, "61": {LocalLengths: []int{9}, MobileStartDigits: []string{"4"}}, "64": {LocalLengths: []int{8, 9, 10}, MobileStartDigits: []string{"2"}}, }
PhoneRules maps country codes to their phone number validation rules. Countries not listed here pass through with generic E.164 validation (7-15 digits).
Sources (verified across 3+ per country):
- ITU-T E.164 / National Numbering Plans (itu.int)
- Wikipedia "Telephone numbers in [Country]" articles
- HowToCallAbroad.com country dialing guides
Functions ¶
func CleanMessage ¶
CleanMessage cleans SMS message text before sending to kwtSMS.
Called automatically by Send(). No manual call needed.
Strips content that silently breaks delivery:
- Arabic-Indic / Extended Arabic-Indic digits converted to Latin
- Emojis and pictographic symbols removed
- Hidden control characters (BOM, zero-width space, soft hyphen, etc.) removed
- Directional formatting characters removed
- C0/C1 control characters removed (preserves \n and \t)
- HTML tags stripped
Arabic letters are NOT stripped. Arabic text is fully supported by kwtSMS.
func EnrichError ¶
EnrichError adds an "action" field to an API error response map. Returns a new map. Has no effect on OK responses.
func FindCountryCode ¶ added in v0.4.0
FindCountryCode extracts the country code prefix from a normalized phone number. Tries 3-digit codes first, then 2-digit, then 1-digit (longest match wins). Returns empty string if no known country code is found.
func LoadEnvFile ¶ added in v0.3.0
LoadEnvFile parses a .env file into a map of key=value pairs. Returns an empty map if the file does not exist or cannot be read. Never panics or returns an error.
Parsing rules:
- Ignores blank lines and lines starting with #
- Strips inline # comments from unquoted values
- Supports quoted values: KWTSMS_SENDER_ID="MY APP" -> MY APP
- Does NOT modify os environment variables (read-only parsing)
func NormalizePhone ¶
NormalizePhone converts a raw phone number to kwtSMS-accepted format: digits only, no leading zeros, domestic trunk prefix stripped.
Steps: convert Arabic/Persian digits to Latin, strip all non-digit characters, strip leading zeros, strip domestic trunk prefix (e.g. 9660559... becomes 966559...).
func ValidatePhoneFormat ¶ added in v0.4.0
ValidatePhoneFormat validates a normalized phone number against country-specific format rules. Checks local number length and mobile starting digits. Numbers with no matching country rules pass through (generic E.164 only). Returns empty string if valid, or an error message if invalid.
Types ¶
type BatchError ¶
type BatchError struct {
Batch int `json:"batch"`
Code string `json:"code"`
Description string `json:"description"`
}
BatchError records an error from a single batch within a bulk send.
type BulkSendResult ¶
type BulkSendResult struct {
Result string `json:"result"`
Bulk bool `json:"bulk"`
Batches int `json:"batches"`
Numbers int `json:"numbers"`
PointsCharged int `json:"points-charged"`
BalanceAfter *float64 `json:"balance-after"`
MsgIDs []string `json:"msg-ids"`
Errors []BatchError `json:"errors"`
Invalid []InvalidEntry `json:"invalid,omitempty"`
}
BulkSendResult is the aggregated response from sending to >200 numbers.
type InvalidEntry ¶
InvalidEntry represents a phone number that failed local pre-validation.
type KwtSMS ¶
type KwtSMS struct {
// contains filtered or unexported fields
}
KwtSMS is the kwtSMS API client. Safe for concurrent use.
func FromEnv ¶
FromEnv creates a KwtSMS client from environment variables, falling back to a .env file. Pass "" for envFile to use the default ".env" path.
Required env vars: KWTSMS_USERNAME, KWTSMS_PASSWORD Optional: KWTSMS_SENDER_ID (default "KWT-SMS"), KWTSMS_TEST_MODE ("1" to enable), KWTSMS_LOG_FILE (default "kwtsms.log")
func New ¶
New creates a new KwtSMS client with the given credentials.
username and password are your kwtSMS API credentials (not your account mobile number). Options can override the default sender ID ("KWT-SMS"), enable test mode, or change the log file path ("kwtsms.log").
func (*KwtSMS) Balance ¶
Balance returns the current balance via the /balance/ API call. Returns the cached value if the API call fails (and no cached value exists, returns 0 with error).
func (*KwtSMS) CachedBalance ¶
CachedBalance returns the balance from the last Verify() or successful Send() call. Returns nil if no cached value exists.
func (*KwtSMS) CachedPurchased ¶
CachedPurchased returns the total purchased credits from the last Verify() call. Returns nil if no cached value exists.
func (*KwtSMS) DLR ¶
DLR retrieves delivery reports for a sent message via /dlr/. DLR only works for international (non-Kuwait) numbers.
msgID is the message ID returned by Send() in result.MsgID.
func (*KwtSMS) Send ¶
Send sends an SMS to one or more numbers.
mobile can be a single number or comma-separated list. Numbers are normalized automatically (strips +, 00, spaces, dashes, Arabic digits). Duplicates after normalization are deduplicated.
message is cleaned automatically (strips emojis, hidden chars, HTML, converts Arabic digits to Latin).
sender overrides the default sender ID for this call. Pass "" to use the default.
For <= 200 numbers, returns a SendResult. For > 200 numbers, returns a BulkSendResult (accessed via the Bulk field).
func (*KwtSMS) SendWithRetry ¶
func (c *KwtSMS) SendWithRetry(mobile string, message string, sender string, maxRetries int) (*SendResult, error)
SendWithRetry sends SMS, retrying automatically on ERR028 (rate limit: wait 15 seconds). Waits 16 seconds between retries. All other errors are returned immediately.
func (*KwtSMS) SenderIDs ¶
SenderIDs lists sender IDs registered on this account via /senderid/. Never panics.
func (*KwtSMS) Status ¶
Status checks the delivery status for a sent message via /status/.
msgID is the message ID returned by Send() in result.MsgID.
func (*KwtSMS) Validate ¶
func (c *KwtSMS) Validate(phones []string) ValidateResult
Validate validates and normalizes phone numbers via /validate/.
Numbers that fail local validation (empty, email, too short, too long, no digits) are rejected immediately before any API call is made.
type Option ¶
type Option func(*KwtSMS)
Option configures a KwtSMS client.
func WithHTTPClient ¶ added in v0.3.0
WithHTTPClient sets a custom HTTP client. Useful for testing or proxies.
func WithLogFile ¶
WithLogFile sets the JSONL log file path. Set to "" to disable logging.
func WithSenderID ¶
WithSenderID sets the default sender ID. Defaults to "KWT-SMS".
func WithTestMode ¶
WithTestMode enables test mode (messages queued but not delivered, no credits consumed).
type PhoneRule ¶ added in v0.4.0
PhoneRule defines country-specific phone number validation rules. LocalLengths lists valid digit counts AFTER the country code. MobileStartDigits lists valid first character(s) of the local number.
type PhoneValidation ¶
PhoneValidation holds the result of ValidatePhoneInput.
func ValidatePhoneInput ¶
func ValidatePhoneInput(phone string) PhoneValidation
ValidatePhoneInput validates a raw phone number input before sending to the API.
Catches every common mistake without panicking:
- Empty or blank input
- Email address entered instead of a phone number
- Non-numeric text with no digits
- Too short after normalization (< 7 digits)
- Too long after normalization (> 15 digits, E.164 maximum)
- Country-specific length and mobile prefix validation
- Domestic trunk prefix stripping (e.g. 9660559... -> 966559...)
type SendResult ¶
type SendResult struct {
Result string `json:"result"`
Code string `json:"code,omitempty"`
Description string `json:"description,omitempty"`
Action string `json:"action,omitempty"`
MsgID string `json:"msg-id,omitempty"`
Numbers int `json:"numbers,omitempty"`
PointsCharged int `json:"points-charged,omitempty"`
BalanceAfter float64 `json:"balance-after,omitempty"`
UnixTimestamp int64 `json:"unix-timestamp,omitempty"`
Invalid []InvalidEntry `json:"invalid,omitempty"`
}
SendResult is the structured response from a single send operation (<= 200 numbers).
type ValidateResult ¶
type ValidateResult struct {
OK []string `json:"ok"`
ER []string `json:"er"`
NR []string `json:"nr"`
Raw map[string]any `json:"raw"`
Error string `json:"error,omitempty"`
Rejected []InvalidEntry `json:"rejected"`
}
ValidateResult is the structured response from validating phone numbers.
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
00-raw-api
command
Example 00: Raw API calls to every kwtSMS endpoint.
|
Example 00: Raw API calls to every kwtSMS endpoint. |
|
01-basic-usage
command
Example 01: Basic usage of kwtsms-go.
|
Example 01: Basic usage of kwtsms-go. |
|
02-otp-flow
command
Example 02: OTP (One-Time Password) flow.
|
Example 02: OTP (One-Time Password) flow. |
|
03-bulk-sms
command
Example 03: Bulk SMS using SendMulti().
|
Example 03: Bulk SMS using SendMulti(). |
|
04-http-handler
command
Example 04: Minimal HTTP handler for sending SMS.
|
Example 04: Minimal HTTP handler for sending SMS. |
|
05-error-handling
command
Example 05: Comprehensive error handling patterns.
|
Example 05: Comprehensive error handling patterns. |
|
06-otp-production
command
Production-ready OTP HTTP server using kwtsms-go.
|
Production-ready OTP HTTP server using kwtsms-go. |