All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- SmsSender: Rewritten with unified send() method. One function handles single, multiple, and bulk sends.
- SmsSender: Added SMS page calculator with GSM-7/UCS-2 encoding detection and cost estimation.
- SmsSender: Balance check now syncs from API if cached value is stale (>24h) or unknown.
- SmsSender: All failure responses now include both machine-readable
reasonand human-readableerror_description. - SmsSender: Dispatches SmsSent and SmsFailed events after every send.
- BalanceService: Added getCachedOrSync() with 24-hour stale threshold.
- KwtSmsEventSubscriber: Admin alert sends now tagged with recipient_type 'admin'.
recipient_typecolumn in kwtsms_logs (customer or admin)pagescolumn in kwtsms_logs (SMS page count)is_test,pages,recipient_typefields in send() success response
- GitHub Actions workflow to auto-create GitHub Releases with changelog notes on tag push
- Unit tests for default event type and fluent method chaining on KwtSmsMessage
- PhoneNormalizer: Removed duplicated
PHONE_RULES,COUNTRY_NAMES,findCountryCode, andvalidatePhoneFormatimplementations. All logic now delegates toKwtSMS\PhoneUtilsv1.3.0, which ships the same rules and methods. The public interface (normalize,findCountryCode,validatePhoneFormat,verify,normalizeMany) is unchanged. - Requires
kwtsms/kwtsms-php^1.3.0 (updated viacomposer update kwtsms/kwtsms-php).
- PhoneNormalizer:
normalize()now strips the trunk prefix0from the local portion when a country code is recognized (e.g.9660559...becomes966559...). Covers Saudi Arabia and any other country where users supply international format with a local trunk zero.
- PhoneNormalizer: Country-specific phone format validation ported from the kwtSMS Shopify integration. Added
PHONE_RULEStable (81 countries),findCountryCode(), andvalidatePhoneFormat().verify()now rejects landlines and wrong-length numbers for all known country codes. Unknown country codes continue to pass with generic E.164 validation (7-15 digits).
- LOW: Template create/edit form help text used single-brace
{variable_name}syntax (which the template engine does not support). Corrected to{{variable_name}}(double braces) in both the placeholder text and help line. - LOW:
help.blade.phpTemplate Variables card showed single-brace{variable_name}syntax. Corrected to{{variable_name}}to match the actual template engine. - LOW: Settings page Rate Limiting description incorrectly stated per-IP limit applies to all web requests. Corrected to clarify both per-IP and per-phone limits apply to single-recipient sends only.
- CRIT: Admin alerts never fired because
KwtSmsEventSubscriberread the wrong settings key (alert_phoneinstead ofadmin_phone). - CRIT: Settings page partially revealed the API username (first 3 characters visible). Now shows only a Set/Not Set badge, matching password behavior.
- CRIT: Admin Alerts page displayed the admin phone number in plaintext. Now masked to last 4 digits.
- HIGH:
KwtSmsSetting::get()silently returned raw ciphertext when called on an encrypted key. Now logs a warning and returns the default instead. - HIGH: Per-IP rate limit incorrectly applied to bulk sends (order notifications) triggered from HTTP context. Limit now applies to single-recipient sends only, matching per-phone behavior.
- HIGH:
LogsController::clear()accepted any DELETE request without additional confirmation. Now requiresconfirm=yesin the request body. - HIGH: Admin phone validation accepted arbitrary strings. Now enforces
digits_between:7,15for international format. - HIGH:
IntegrationsUpdateRequestaccepted arbitrary values in theintegrationsarray. Addedintegrations.*rule enforcing boolean values. - MED:
KwtSmsSetting::set()silently overwrote encrypted keys with plaintext. Now logs a warning before overwriting. - MED:
kwtsms_logs.recipientcolumn wasvarchar(30), truncating comma-separated phone lists for bulk sends. New migration widens totext. - MED:
kwtsms_templates.namehad a single-column unique constraint, preventing the same template name in multiple locales. New migration replaces it with a composite unique on(name, locale). - MED:
BalanceServiceused raw cURL. Replaced with Laravel Http facade for consistency and testability. - MED: Template body had no length limit. Added
max:1600(10 SMS pages). - LOW:
SmsFailedevent exposed the full recipient phone number as a public property. AddedrecipientMasked()helper method for safe logging. - LOW:
KwtSmsSetting::set()stored plain strings without JSON-encoding, causing inconsistent round-trips for numeric strings viaget(). All values are now JSON-encoded on write. - LOW:
TemplateRequesthad no uniqueness validation. AddedRule::uniqueon(name, locale)withignoresupport for updates. - LOW:
integrations.blade.phpreferenced the non-existentSmsSender::sendForEvent()method. Updated to show the correctSmsSender::send()API. - LOW:
help.blade.phpreferenced the non-existent$sender->sendForEvent()method and showed incorrect single-brace template syntax. Updated to usesend()and double-brace@{{variable}}syntax.
- Rate limiting config keys (
per_phone_per_hour,per_ip_per_hour) were defined in config but never enforced. Now applied inSmsSender::send()using Laravel's built-inRateLimiter. Per-phone limit applies to single-recipient sends (OTP/password reset). Per-IP limit applies in HTTP context only (skipped in CLI/queues).
- Restructured README: moved About kwtSMS section to top, License to bottom
- Replaced Help and Support table with linked list matching style of other kwtSMS packages
- Removed Security section, WhatsApp number, and email address from README (refer to SECURITY.md)
- Removed kwtSMS SDKs and Integrations section from README
- Fixed em dash in About kwtSMS paragraph
- GitHub Actions CI: Laravel Pint code style check (
pint.yml) on push/PR to main - GitHub Actions CI: PHP syntax check across 8.1, 8.2, 8.3 (
php.yml) on push/PR to main
- Added
laravel/pinttorequire-devincomposer.json
- kwtSMS REST/JSON API client (
KwtSMS\KwtSMS): send, balance, senderids, validate, coverage, status, dlr endpoints - Laravel Notification Channel (
KwtSmsChannel+KwtSmsMessage): plugs into standard$notifiable->notify()flow - SmsSender service: core send pipeline with pre-send guards (enabled, configured, balance, coverage)
- Bulk send with batching: max 200 numbers per request, 0.2s delay between batches to stay within API rate limits
- PhoneNormalizer service: strips
+,00, spaces, dashes, converts Arabic/Hindi digits to Latin - MessageUtils (MessageCleaner): strips emojis, zero-width characters, HTML tags, converts Arabic numerals
- BalanceService: syncs and caches account balance, powers low-balance threshold checks
- KwtSmsTemplate model: SMS templates with
{{variable_name}}placeholder substitution - KwtSmsSetting model: key/value settings store backed by the
kwtsms_settingstable - KwtSmsLog model: per-send log with full API request/response JSON, event type, status, points charged
- Admin panel (7 tabs): Dashboard, Settings, Templates, Integrations, Logs, Admin Alerts, Help
- Dashboard: live balance, send count, failed count, recent logs
- Settings: credentials, test connection button, low balance threshold, admin phone
- Templates: create/edit/delete/activate templates with Arabic character counter
- Integrations: toggle which events trigger SMS sends
- Logs: view, export CSV, clear all
- Admin Alerts: configure alert types (low balance, send failure, daily summary, API error, OTP flood)
- Help: quick start guide and code examples
- Default SMS templates (English + Arabic) for:
otp,password_reset,order_placed,order_confirmed,order_shipped,order_delivered,order_cancelled,order_status,cod_otp,low_balance_alert - KwtSmsSyncCommand (
php artisan kwtsms:sync): syncs balance, sender IDs, and coverage from API; scheduled daily at 03:00 - KwtSms Facade for static access:
KwtSms::send(),KwtSms::balance(),KwtSms::senderids() - Event system:
BalanceLowevent,KwtSmsNotifiablecontract, order/auth event integration hooks - Global kill switch:
KWTSMS_ENABLED=falseblocks all sends without touching other config - Test mode:
KWTSMS_TEST_MODE=truequeues messages without delivery, no credits consumed - PHPUnit feature test suite: 27 tests, 44 assertions, all hitting real API with test mode
- Published config:
config/kwtsms.phpwith all settings documented - Published migrations:
kwtsms_logs,kwtsms_settings,kwtsms_templatestables