itxshakil / laravel-fast2sms
A Laravel package for sending SMS and WhatsApp messages via the Fast2SMS API with a fluent interface.
Requires
- php: ^8.3
- illuminate/events: ^11.0|^12.0|^13.0
- illuminate/http: ^11.0|^12.0|^13.0
- illuminate/notifications: ^11.0|^12.0|^13.0
- illuminate/queue: ^11.0|^12.0|^13.0
- illuminate/support: ^11.0|^12.0|^13.0
Requires (Dev)
- driftingly/rector-laravel: ^2.1
- larastan/larastan: ^3.0
- laravel/pint: ^1.0
- orchestra/testbench: ^9.0|^10.0|^11.0
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-strict-rules: ^2.0
- phpunit/phpunit: ^11.0|^12.0
- rector/rector: ^2.0
- dev-main
- 2.0.0.x-dev
- v2.0.0
- v1.3.0
- v1.2.0
- v1.1.2
- v1.1.1
- v1.1.0
- v1.0.0
- dev-dependabot/github_actions/actions/stale-10
- dev-dependabot/github_actions/actions/labeler-6
- dev-dependabot/github_actions/release-drafter/release-drafter-7
- dev-dependabot/github_actions/actions/checkout-6
- dev-dependabot/github_actions/actions/cache-5
- dev-dependabot/github_actions/actions/first-interaction-3
This package is auto-updated.
Last update: 2026-03-28 15:55:33 UTC
README
Laravel Fast2SMS is a first-class Laravel package for sending SMS and WhatsApp messages via the Fast2SMS API. It provides a fluent, type-safe API, Laravel Notifications support, queue integration, a powerful fake for testing, and a rich exception hierarchy โ so you can build reliable messaging features with confidence.
Table of Contents
- Features
- Requirements
- Installation
- Quick Start
- Configuration
- Cost-Saving Features
- Sending SMS
- Sending WhatsApp Messages
- Laravel Notifications
- Queuing
- Events & Listeners
- Testing
- Artisan Commands
- Phone Validation
- Error Handling
- Upgrade Guide
- Contributing
- License
Features
- ๐ฑ SMS โ Quick, OTP, and DLT routes with flash and bulk support
- ๐ฌ WhatsApp โ Text, image, document, location, and interactive messages
- ๐ Laravel Notifications โ
SmsChannelandWhatsAppChannelout of the box - โก Queue support โ Dispatch sends as background jobs with per-send overrides
- ๐งช Fake & assert โ
Fast2sms::fake()with 16 rich assertion helpers - ๐จ Typed exceptions โ
AuthenticationException,RateLimitException,ApiException, and more - ๐ฐ Cost-saving features โ Recipient deduplication (on by default), dedup guard, throttle, balance gate, batch splitting, invalid-recipient stripping
- ๐ PHPStan level 6 โ Fully typed, zero suppressions
- ๐ Artisan commands โ Balance monitor, WABA details, event discovery, IDE helper
- ๐ฎ๐ณ Indian phone validation โ
Fast2smsPhonerule class
Requirements
| Requirement | Version |
|---|---|
| PHP | ^8.3 |
| Laravel | ^11.0 | ^12.0 | ^13.0 |
| Fast2SMS account | Sign up free |
Installation
Install via Composer:
composer require itxshakil/laravel-fast2sms
The package auto-discovers its service provider. Publish the config file:
php artisan vendor:publish --tag=fast2sms-config
Add your API key to .env:
FAST2SMS_API_KEY=your_api_key_here
Full installation guide: docs/installation.md
Quick Start
use Shakil\Fast2sms\Facades\Fast2sms; // Send a quick SMS $response = Fast2sms::quick( numbers: '9876543210', message: 'Your OTP is 123456', ); if ($response->isSuccess()) { echo 'SMS sent! Request ID: ' . $response->requestId; }
Configuration
After publishing, edit config/fast2sms.php. All keys can be set via environment variables:
| Key | Env Variable | Default | Description |
|---|---|---|---|
api_key |
FAST2SMS_API_KEY |
โ | Your Fast2SMS API key (required) |
default_sender_id |
FAST2SMS_DEFAULT_SENDER_ID |
FSTSMS |
Default DLT sender ID |
default_route |
FAST2SMS_DEFAULT_ROUTE |
dlt |
Default SMS route |
base_url |
โ | https://www.fast2sms.com/dev |
Fast2SMS API base URL (do not change unless instructed) |
timeout |
โ | 30 |
HTTP request timeout in seconds |
driver |
FAST2SMS_DRIVER |
api |
api for real sends, log for local dev |
database_logging |
FAST2SMS_DATABASE_LOGGING |
false |
Log sends to database โ see docs/db-logging.md |
recipients.deduplicate |
FAST2SMS_DEDUP_RECIPIENTS |
true |
Remove duplicate numbers before every send |
recipients.batch_size |
FAST2SMS_BATCH_SIZE |
0 |
Split large lists into chunks (0 = disabled) |
validation.strip_invalid_recipients |
FAST2SMS_STRIP_INVALID |
false |
Strip invalid numbers before sending |
balance_gate.enabled |
FAST2SMS_BALANCE_GATE |
false |
Enable balance check before every send |
balance_gate.threshold |
FAST2SMS_BALANCE_THRESHOLD |
10.0 |
Balance alert threshold (โน) |
balance_gate.abort |
FAST2SMS_BALANCE_ABORT |
true |
Throw InsufficientBalanceException when balance is low |
deduplication.enabled |
FAST2SMS_DEDUP_ENABLED |
false |
Enable idempotency / dedup guard |
deduplication.ttl |
FAST2SMS_DEDUP_TTL |
60 |
Dedup window in seconds |
deduplication.store |
FAST2SMS_DEDUP_STORE |
null |
Cache store for dedup guard (null = default store) |
throttle.enabled |
FAST2SMS_THROTTLE_ENABLED |
false |
Enable send-rate throttle |
throttle.max_per_minute |
FAST2SMS_THROTTLE_MAX |
60 |
Max sends per minute |
throttle.store |
FAST2SMS_THROTTLE_STORE |
null |
Cache store for send-rate throttle (null = default store) |
whatsapp.default_phone_number_id |
FAST2SMS_WHATSAPP_PHONE_NUMBER_ID |
โ | Default WhatsApp phone number ID |
whatsapp.default_waba_id |
FAST2SMS_WHATSAPP_WABA_ID |
โ | Default WhatsApp Business Account ID |
whatsapp.version |
FAST2SMS_WHATSAPP_VERSION |
v24.0 |
WhatsApp API version |
events.enabled |
FAST2SMS_EVENTS_ENABLED |
true |
Enable/disable event dispatching |
queue.enabled |
FAST2SMS_QUEUE_ENABLED |
false |
Send via queue |
queue.connection |
FAST2SMS_QUEUE_CONNECTION |
null |
Queue connection name |
queue.name |
FAST2SMS_QUEUE_NAME |
default |
Queue name |
queue.tries |
FAST2SMS_QUEUE_TRIES |
3 |
Max job attempts |
Use FAST2SMS_DRIVER=log in local/testing environments to log messages instead of making real API calls.
Full configuration reference: docs/configuration.md
Cost-Saving Features
Most cost-saving features are opt-in and disabled by default โ enable only what you need. Recipient deduplication is the exception: it is enabled by default.
Recipient Deduplication
Automatically removes duplicate numbers from the recipient list before every send. This is enabled by default โ set to false to disable:
FAST2SMS_DEDUP_RECIPIENTS=true # default: true
Invalid Recipient Stripping
Validates each number against the Fast2smsPhone rule, logs a warning for each removed number, and throws ValidationException if all numbers are invalid:
FAST2SMS_STRIP_INVALID=true
Idempotency / Dedup Guard
Prevents the same message being sent twice within a configurable window (applies to both SMS and WhatsApp):
FAST2SMS_DEDUP_ENABLED=true FAST2SMS_DEDUP_TTL=60 # seconds
Throws DuplicateSendException on a repeated identical call within the TTL.
Send-Rate Throttle
Limits the number of sends per minute using a sliding-window cache counter (applies to both SMS and WhatsApp):
FAST2SMS_THROTTLE_ENABLED=true FAST2SMS_THROTTLE_MAX=60
Throws ThrottleExceededException when the limit is reached.
Balance Gate
Checks your wallet balance before every send and fires LowBalanceDetected. When abort is true, throws InsufficientBalanceException to prevent the API call (applies to both SMS and WhatsApp):
FAST2SMS_BALANCE_GATE=true FAST2SMS_BALANCE_THRESHOLD=10.0 FAST2SMS_BALANCE_ABORT=true
Batch Splitting
Splits large recipient lists into chunks and issues one API call per chunk:
FAST2SMS_BATCH_SIZE=100 # 0 = disabled
SMS Credit Estimation
Use SmsMessage helpers to estimate cost before sending:
use Shakil\Fast2sms\Notifications\Messages\SmsMessage; $msg = new SmsMessage('Your OTP is 123456'); $msg->charCount(); // 22 $msg->isUnicode(); // false $msg->creditCount(); // 1 $msg->exceedsOneSms(); // false
Full cost-saving features guide: docs/cost-saving-features.md
Full upgrade guide: UPGRADING.md
Sending SMS
Quick SMS
use Shakil\Fast2sms\Facades\Fast2sms; Fast2sms::quick(numbers: '9876543210', message: 'Hello from Fast2SMS!');
OTP SMS
Fast2sms::otp( numbers: '9876543210', otpValue: '123456', );
DLT SMS
Fast2sms::dlt( numbers: ['9876543210', '9123456789'], templateId: 'your_template_id', variablesValues: 'Your order #1234 has been shipped.', senderId: 'MYSHOP', );
Flash SMS
Fast2sms::to('9876543210') ->message('Flash message!') ->flash() ->send();
Multiple Recipients
Fast2sms::quick( numbers: ['9876543210', '9123456789', '9000000001'], message: 'Broadcast message', );
Full SMS guide: docs/sms-guide.md
Sending WhatsApp Messages
Phone number format for WhatsApp: Always include the country code prefix (e.g.
919876543210for India โ91+ 10-digit number). This differs from SMS, which accepts 10-digit numbers directly.
Text
Fast2sms::whatsapp() ->to('919876543210') ->sendText('Hello from Fast2SMS!');
Image
Fast2sms::whatsapp() ->to('919876543210') ->sendImage('https://example.com/image.jpg');
Document
Fast2sms::whatsapp() ->to('919876543210') ->sendDocument('https://example.com/invoice.pdf');
Location
Fast2sms::whatsapp() ->to('919876543210') ->sendLocation(latitude: 28.6139, longitude: 77.2090, name: 'New Delhi');
Interactive
Fast2sms::whatsapp() ->to('919876543210') ->sendInteractive([ 'type' => 'button', 'body' => ['text' => 'Choose an option'], 'action' => ['buttons' => [/* ... */]], ]);
Convenience alias:
Fast2sms::viaWhatsApp($to)is shorthand forFast2sms::whatsapp()->to($to). Both are public API;whatsapp()->to(...)is the canonical form.
Full WhatsApp guide: docs/whatsapp.md
Laravel Notifications
SMS Notification
use Illuminate\Notifications\Notification; use Shakil\Fast2sms\Enums\SmsRoute; use Shakil\Fast2sms\Notifications\Messages\SmsMessage; class OrderShipped extends Notification { public function via(object $notifiable): array { return ['fast2sms']; } public function toSms(object $notifiable): SmsMessage { return SmsMessage::create("Your order #{$this->order->id} has shipped!") ->withRoute(SmsRoute::QUICK); } }
Add routeNotificationForFast2sms() to your notifiable model (e.g. User) to tell the channel which number to use:
// app/Models/User.php public function routeNotificationForFast2sms(): string { return $this->phone_number; }
WhatsApp Notification
use Illuminate\Notifications\Notification; use Shakil\Fast2sms\Notifications\Messages\WhatsAppMessage; class OrderShippedWhatsApp extends Notification { public function via(object $notifiable): array { return ['whatsapp']; } public function toWhatsApp(object $notifiable): WhatsAppMessage { return WhatsAppMessage::text("Your order #{$this->order->id} has shipped!"); } }
Add routeNotificationForWhatsapp() to your notifiable model (e.g. User) to tell the channel which number to use:
// app/Models/User.php public function routeNotificationForWhatsapp(): string { return $this->phone_number; }
Full notifications guide: docs/notifications.md
Queuing
Enable queuing in .env:
FAST2SMS_QUEUE_ENABLED=true FAST2SMS_QUEUE_NAME=sms FAST2SMS_QUEUE_TRIES=3
Then send as usual โ the package dispatches a background job automatically:
Fast2sms::quick(numbers: '9876543210', message: 'Queued message');
Override queue settings per-send:
Fast2sms::onQueue('high-priority') ->onConnection('redis') ->quick(numbers: '9876543210', message: 'Urgent!');
Full queuing guide: docs/queuing.md
Events & Listeners
The package dispatches the following events:
| Event | When |
|---|---|
SmsSent |
After a successful SMS send |
SmsFailed |
When an SMS send fails |
WhatsAppSent |
After a successful WhatsApp send |
WhatsAppFailed |
When a WhatsApp send fails |
LowBalanceDetected |
When wallet balance drops below threshold |
Listening to Events
// In EventServiceProvider protected $listen = [ \Shakil\Fast2sms\Events\SmsSent::class => [ \App\Listeners\LogSmsSent::class, ], \Shakil\Fast2sms\Events\LowBalanceDetected::class => [ \App\Listeners\NotifyAdminOfLowBalance::class, ], ];
Discover All Events
php artisan fast2sms:events
Full events guide: docs/events.md
Testing
Use Fast2sms::fake() to prevent real HTTP calls in tests:
use Shakil\Fast2sms\DataTransferObjects\SmsParameters; use Shakil\Fast2sms\Enums\SmsRoute; use Shakil\Fast2sms\Facades\Fast2sms; Fast2sms::fake(); // Run code that sends SMS... $this->post('/send-otp', ['phone' => '9876543210']); // Assert Fast2sms::assertSmsSent(); Fast2sms::assertSmsSentTo('9876543210'); Fast2sms::assertSmsSentWithMessage('Your OTP'); Fast2sms::assertSmsSentCount(1); Fast2sms::assertSmsNotSent(); Fast2sms::assertSmsSentWithRoute(SmsRoute::QUICK); // Closure-based assertion Fast2sms::assertSmsSent(function (SmsParameters $params): bool { return str_contains($params->message, 'OTP'); }); // Assert nothing was sent Fast2sms::assertNothingSent();
WhatsApp Assertions
use Shakil\Fast2sms\Enums\WhatsAppType; Fast2sms::assertWhatsAppSent(); Fast2sms::assertWhatsAppSentTo('919876543210'); Fast2sms::assertWhatsAppSentCount(1); Fast2sms::assertWhatsAppSentWithType(WhatsAppType::TEXT); Fast2sms::assertWhatsAppNotSent(); // Closure-based assertion use Shakil\Fast2sms\DataTransferObjects\WhatsAppParameters; Fast2sms::assertWhatsAppSent(function (WhatsAppParameters $params): bool { return $params->to === '919876543210' && $params->type === WhatsAppType::TEXT; });
Combined Assertions
// Assert nothing was sent (SMS or WhatsApp) Fast2sms::assertNothingSent(); // Assert total sends across both channels (counts typed SMS + WhatsApp records) Fast2sms::assertSentCount(3); // Assert exact total sends via raw sentMessages log entries Fast2sms::assertSentTimes(3); // Generic low-level assertion with optional closure (closure receives raw array payload) Fast2sms::assertSent(fn (array $message) => $message['numbers'] === ['9876543210']); // Assert no message matching criteria was sent Fast2sms::assertNotSent(fn (array $message) => $message['numbers'] === ['9876543210']);
Stopping the Fake
Fast2sms::fake() stores the fake in a static property. Call Fast2sms::stopFaking() in tearDown to restore real HTTP behaviour when managing the fake lifecycle manually in plain PHPUnit tests:
protected function tearDown(): void { Fast2sms::stopFaking(); parent::tearDown(); }
Note: When using Orchestra Testbench or Laravel's full boot cycle the container is re-booted between tests, so
stopFaking()is usually not required.
Full testing guide: docs/testing.md
Artisan Commands
| Command | Description |
|---|---|
fast2sms:balance |
Check wallet balance and alert if below threshold |
fast2sms:waba |
Show WhatsApp Business Account details |
fast2sms:events |
List all package events |
fast2sms:ide-helper |
Generate IDE helper stub for better autocomplete (non-production only) |
# Check balance with custom threshold php artisan fast2sms:balance --threshold=500 # Output as JSON (for scripting/CI) php artisan fast2sms:balance --json # Show WABA details php artisan fast2sms:waba # List all events php artisan fast2sms:events # Generate IDE helper php artisan fast2sms:ide-helper
Phone Validation
Use the Fast2smsPhone rule to validate Indian mobile numbers:
use Shakil\Fast2sms\Rules\Fast2smsPhone; $request->validate([ 'phone' => ['required', 'string', new Fast2smsPhone], ]);
The rule validates 10-digit Indian mobile numbers starting with 6โ9.
Error Handling
All exceptions extend Fast2smsException, so you can catch broadly or specifically:
use Shakil\Fast2sms\Exceptions\ApiException; use Shakil\Fast2sms\Exceptions\AuthenticationException; use Shakil\Fast2sms\Exceptions\Fast2smsException; use Shakil\Fast2sms\Exceptions\NetworkException; use Shakil\Fast2sms\Exceptions\RateLimitException; use Shakil\Fast2sms\Exceptions\ValidationException; try { Fast2sms::quick(numbers: '9876543210', message: 'Hello!'); } catch (AuthenticationException $e) { // Invalid API key โ check FAST2SMS_API_KEY } catch (RateLimitException $e) { // Too many requests โ back off and retry } catch (ValidationException $e) { // Invalid input โ $e->getMessage() describes the problem } catch (ApiException $e) { // API returned an error โ check $e->getMessage() } catch (NetworkException $e) { // Network timeout or connection failure } catch (Fast2smsException $e) { // Catch-all for any other package exception }
Exception Reference
| Exception | When |
|---|---|
ConfigurationException |
Invalid config (missing API key, bad driver) |
ValidationException |
Invalid input (empty numbers, bad phone format) |
AuthenticationException |
Invalid or missing API key (HTTP 401) |
RateLimitException |
Too many requests (HTTP 429) |
ServerException |
Fast2SMS server error (HTTP 5xx) |
ApiException |
Other API error (4xx) |
NetworkException |
Network timeout or connection failure |
DuplicateSendException |
Identical send repeated within the deduplication TTL |
InsufficientBalanceException |
Wallet balance too low when Balance Gate abort is true |
ThrottleExceededException |
Send-rate limit reached for the configured window |
Full error handling guide: docs/error-handling.md
Full API reference: docs/api-reference.md
Upgrade Guide
Upgrading from v1.x? See UPGRADING.md for the full migration guide, including:
- New exception types to catch
- Readonly DTO changes
- Fake assertion API changes
- Deprecated method renames (
content()โwithContent(),to()โwithNumbers()) - Response type-hint updates
Also available as an extended guide: docs/upgrade-v1-to-v2.md
Contributing
Contributions are welcome! Please read CONTRIBUTING.md before submitting a pull request.
# Run the full test suite composer test # Static analysis composer analyse # Auto-fix code style composer lint:fix # Full QA pipeline composer qa
License
The MIT License (MIT). Please see LICENSE.md for more information.
Made with โค๏ธ for the Laravel community ยท Shakil Alam