SMS remains one of the most reliable communication channels for web applications. Whether you’re sending OTP codes, order confirmations, marketing promotions, or two-factor authentication codes, integrating SMS into your Laravel application is a critical capability.
This guide covers everything you need to know about Laravel SMS integration in 2026 — from choosing the right gateway to advanced patterns like queueing, bulk messaging, and webhook handling.
Laravel provides a first-class notifications system that abstracts away the complexities of sending messages across multiple channels. With its built-in queue support, event system, and elegant API, Laravel makes SMS integration both powerful and maintainable.
Key advantages of using Laravel for SMS:
Your choice of SMS gateway depends on your use case, budget, and target region. Here’s a comparison of the most popular providers for Laravel:
| Provider | Price (approx) | Global Coverage | Laravel Package | Two-Way SMS | Delivery Reports |
|---|---|---|---|---|---|
| Twilio | $0.0079/msg | Excellent | twilio/sdk | Yes | Yes |
| Vonage (Nexmo) | $0.0061/msg | Very Good | vonage/client | Yes | Yes |
| AWS SNS | $0.00645/msg | Good | aws/aws-sdk-php | No | Yes |
| Plivo | $0.007/msg | Very Good | plivo/plivo-php | Yes | Yes |
| TextLocal | $0.04/msg | UK/EU Focus | Custom | Yes | Yes |
| MessageBird | $0.005/msg | Excellent | Custom | Yes | Yes |
Twilio is the most popular SMS API globally. It offers excellent documentation, a robust PHP SDK, and features like WhatsApp integration, verification services, and SIP calling.
Pros: Deep documentation, broad coverage, reliable infrastructure.
Cons: Slightly higher per-message pricing than some competitors.
Vonage offers competitive pricing and a well-maintained PHP client. It excels in Europe and provides strong two-way SMS capabilities.
Pros: Good pricing, simple API, strong European coverage.
Cons: Smaller ecosystem than Twilio.
If your infrastructure is already on AWS, SNS is a natural choice. It integrates tightly with other AWS services and offers pay-as-you-go pricing.
Pros: Low cost at scale, deep AWS integration.
Cons: No two-way SMS, requires AWS SDK setup, more complex delivery report handling.
Plivo provides a strong alternative to Twilio with competitive pricing and a straightforward API.
Pros: Competitive pricing, good coverage, easy API.
Cons: Smaller community, fewer tutorials.
For a deeper comparison, check our guide on best SMS APIs for Laravel.
Start by installing the SDK for your chosen provider. Here’s a standard setup:
composer require twilio/sdk
---
Add your credentials to `.env`:
```env
TWILIO_SID=your_account_sid
TWILIO_AUTH_TOKEN=your_auth_token
TWILIO_FROM=+15551234567
---
Create a dedicated config file at `config/sms.php`:
```php
<?php
return [
'default' => env('SMS_DRIVER', 'twilio'),
'drivers' => [
'twilio' => [
'sid' => env('TWILIO_SID'),
'token' => env('TWILIO_AUTH_TOKEN'),
'from' => env('TWILIO_FROM'),
],
'vonage' => [
'api_key' => env('VONAGE_KEY'),
'api_secret' => env('VONAGE_SECRET'),
'from' => env('VONAGE_FROM'),
],
],
];
---
## Laravel Notifications System
Laravel's notification system is the recommended way to send SMS. Each notification is a dedicated class that defines one or more delivery channels.
### Creating an SMS Notification
```bash
php artisan make:notification OrderShipped
---
```php
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
class OrderShipped extends Notification implements ShouldQueue
{
use Queueable;
public function __construct(
protected string $orderNumber
) {}
public function via(object $notifiable): array
{
return $notifiable->prefers_sms ? ['vonage'] : ['mail'];
}
public function toVonage(object $notifiable): \Illuminate\Notifications\Messages\VonageMessage
{
return (new \Illuminate\Notifications\Messages\VonageMessage)
->content("Your order #{$this->orderNumber} has shipped!");
}
}
---
### Custom SMS Channel
If your provider isn't supported out of the box, create a custom channel:
```php
<?php
namespace App\Notifications\Channels;
use App\Services\SmsService;
use Illuminate\Notifications\Notification;
class SmsChannel
{
public function __construct(
protected SmsService $sms
) {}
public function send(object $notifiable, Notification $notification): void
{
$message = $notification->toSms($notifiable);
$this->sms->send(
$notifiable->phone_number,
$message->content
);
}
}
---
For a full walkthrough, see our tutorial on [how to send SMS in Laravel](/blog/how-to-send-sms-in-laravel).
## Queueing SMS for Performance
Sending SMS synchronously during an HTTP request increases response time and creates a poor user experience. Always queue SMS notifications.
### Making Notifications Queueable
```php
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;
class WelcomeSms extends Notification implements ShouldQueue
{
use Queueable;
public function __construct(
public string $message
) {
$this->onQueue('sms');
}
}
---
Dispatch the notification as usual — Laravel automatically pushes it to the queue:
```php
$user->notify(new WelcomeSms('Welcome to our platform!'));
---
### Configuring Horizon for SMS
Create a dedicated queue for SMS in `config/horizon.php`:
```php
'environments' => [
'production' => [
'sms' => [
'connection' => 'redis',
'queue' => ['sms'],
'balance' => 'auto',
'minProcesses' => 1,
'maxProcesses' => 5,
'tries' => 3,
'timeout' => 30,
],
],
],
---
### Handling Failed Jobs
Failed SMS jobs should be logged and optionally retried via a fallback provider:
```php
<?php
namespace App\Jobs;
use App\Notifications\SmsFailed;
use Illuminate\Support\Facades\Log;
class HandleFailedSms
{
public function handle(object $event): void
{
Log::error('SMS failed to send', [
'notification' => get_class($event->notification),
'notifiable' => $event->notifiable->id ?? null,
'exception' => $event->exception->getMessage(),
]);
// Optionally retry via fallback gateway
}
}
---
Register the handler in `AppServiceProvider`:
```php
use Illuminate\Notifications\Events\NotificationFailed;
public function boot(): void
{
Event::listen(
NotificationFailed::class,
HandleFailedSms::class
);
}
---
For more details, read our guide on [Laravel SMS notifications using queues](/blog/laravel-sms-notifications-queues).
## OTP Verification via SMS
One-time passwords sent via SMS remain the most common phone verification method.
### Generating and Sending OTP
```php
<?php
namespace App\Services;
use Illuminate\Support\Facades\Cache;
class OtpService
{
public function generate(string $identifier, int $length = 6, int $ttl = 300): string
{
$code = (string) random_int(10 ** ($length - 1), (10 ** $length) - 1);
Cache::put("otp:{$identifier}", [
'code' => $code,
'attempts' => 0,
], $ttl);
return $code;
}
public function verify(string $identifier, string $code): bool
{
$stored = Cache::get("otp:{$identifier}");
if (! $stored || $stored['attempts'] >= 5) {
return false;
}
Cache::increment("otp:{$identifier}.attempts");
if (! hash_equals($stored['code'], $code)) {
return false;
}
Cache::forget("otp:{$identifier}");
return true;
}
}
---
### Rate Limiting OTP Requests
```php
use Illuminate\Support\Facades\RateLimiter;
$key = "otp-request:{$user->id}";
if (RateLimiter::tooManyAttempts($key, 3)) {
$seconds = RateLimiter::availableIn($key);
return back()->withErrors(['phone' => "Try again in {$seconds} seconds."]);
}
RateLimiter::hit($key, 60);
---
See the complete implementation in our [Laravel OTP verification via SMS guide](/blog/laravel-otp-verification-sms).
## Bulk SMS Sending
Sending SMS to thousands of recipients requires careful batching and rate limiting to avoid API throttling.
### Chunked Dispatch
```php
User::where('subscribed', true)->chunk(200, function ($users) {
foreach ($users as $user) {
$user->notify(new PromotionalSms($user, 'Big sale this weekend!'));
}
});
---
### Using Laravel Batches
```php
use Illuminate\Bus\Batch;
use Illuminate\Support\Facades\Bus;
$batch = Bus::batch(
User::where('subscribed', true)->map(fn ($user) => new SendPromoSms($user))
)->then(function (Batch $batch) {
Log::info('Bulk SMS campaign completed: ' . $batch->totalJobs);
})->catch(function (Batch $batch) {
Log::error('Bulk SMS campaign had failures', [
'failed' => $batch->failedJobs,
]);
})->dispatch();
---
### Rate Limiting API Calls
Wrap your provider calls with Laravel's rate limiter:
```php
use Illuminate\Support\Facades\RateLimiter;
$executed = RateLimiter::attempt(
'sms-api',
$perSecond = 10,
function () use ($user, $message) {
// Send SMS via provider
}
);
---
## Two-Factor Authentication with SMS
Laravel Fortify and Jetstream provide two-factor authentication, but you can build a custom 2FA flow with SMS:
```php
<?php
namespace App\Actions;
use App\Notifications\TwoFactorCode;
use Illuminate\Support\Facades\Cache;
class SendTwoFactorCode
{
public function __invoke($user): void
{
$code = (string) random_int(100000, 999999);
Cache::put("2fa:{$user->id}", [
'code' => $code,
'expires_at' => now()->addMinutes(10),
], 600);
$user->notify(new TwoFactorCode($code));
}
}
---
## Webhook Handling for Delivery Reports
Most SMS providers send delivery receipts via webhook. Handle them with a dedicated route:
```php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\SmsWebhookController;
Route::post('/webhooks/twilio/status', [SmsWebhookController::class, 'twilioStatus'])
->name('webhooks.twilio.status');
---
Controller example:
```php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class SmsWebhookController extends Controller
{
public function twilioStatus(Request $request): void
{
$request->validate([
'MessageSid' => 'required|string',
'MessageStatus' => 'required|string',
'To' => 'required|string',
]);
Log::info('SMS delivery update', $request->only([
'MessageSid', 'MessageStatus', 'To', 'ErrorCode',
]));
// Update your database delivery status
}
}
---
## Best Practices & Error Handling
### Logging
Always log SMS sent/failed events for debugging and auditing:
```php
use Illuminate\Support\Facades\Log;
Log::channel('sms')->info('SMS sent', [
'to' => $phone,
'message_id' => $response->sid,
'provider' => 'twilio',
]);
---
### Retry Logic with Exponential Backoff
```php
public function retryUntil(): \DateTime
{
return now()->addMinutes(5);
}
public function backoff(): array
{
return [2, 5, 10, 30];
}
---
### Fallback Gateway
Switch providers on failure:
```php
try {
$this->twilio->send($to, $message);
} catch (TwilioException $e) {
Log::warning('Twilio failed, falling back to Vonage', [
'error' => $e->getMessage(),
]);
$this->vonage->send($to, $message);
}
---
### Environment-Specific Configuration
Use different SMS drivers per environment:
```env
# Production
SMS_DRIVER=twilio
# Staging
SMS_DRIVER=log
# Testing
SMS_DRIVER=null
---
## Conclusion
Integrating SMS into Laravel applications is straightforward thanks to the framework's notification system, queue infrastructure, and extensive package ecosystem. By following the patterns outlined in this guide, you can build a reliable, scalable SMS system that handles everything from simple alerts to complex bulk campaigns.
Start with a single [notification class](/blog/how-to-send-sms-in-laravel), add [queue support](/blog/laravel-sms-notifications-queues) for performance, implement [OTP verification](/blog/laravel-otp-verification-sms) for security flows, and choose the right [SMS API](/blog/best-sms-apis-for-laravel) for your needs.
## Related Articles
- [How to Send SMS in Laravel (Step-by-Step Guide)](/blog/how-to-send-sms-in-laravel)
- [Laravel SMS Notifications Using Queues (Performance Guide)](/blog/laravel-sms-notifications-queues)
- [Laravel OTP Verification via SMS (Complete Guide)](/blog/laravel-otp-verification-sms)
- [Best SMS APIs for Laravel in 2026](/blog/best-sms-apis-for-laravel)