Sending SMS synchronously during an HTTP request adds 200-1000ms of latency. When you have multiple recipients or a slow provider response, this quickly degrades the user experience. Queueing SMS notifications solves this problem by deferring delivery to a background worker.
php artisan queue:table
php artisan migrate
---
Set your `.env`:
```env
QUEUE_CONNECTION=database
---
### Redis Driver (Production)
```bash
composer require predis/predis
---
```env
QUEUE_CONNECTION=redis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
---
## Making Notifications Queueable
Laravel makes queueing trivial. Implement `ShouldQueue` on any notification:
```php
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;
class AccountAlert extends Notification implements ShouldQueue
{
use Queueable;
public function __construct(
public string $message
) {
$this->onQueue('sms');
$this->onConnection('redis');
}
public function via(object $notifiable): array
{
return ['sms'];
}
}
---
The `ShouldQueue` interface tells Laravel to push the notification to the queue instead of sending it immediately. The `Queueable` trait provides helper methods to configure the queue name and connection.
### Configuring Retries and Timeouts
```php
class AccountAlert extends Notification implements ShouldQueue
{
use Queueable;
public int $tries = 5;
public int $maxExceptions = 3;
public function retryUntil(): \DateTime
{
return now()->addMinutes(10);
}
public function backoff(): array
{
return [2, 5, 10, 30, 60];
}
}
---
## Running Queue Workers
```bash
php artisan queue:work redis --queue=sms --tries=3 --delay=5
---
For production, use Supervisor to keep workers alive. Example `/etc/supervisor/conf.d/laravel-sms-worker.conf`:
```ini
[program:laravel-sms-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/artisan queue:work redis --queue=sms --sleep=3 --tries=3 --timeout=30
autostart=true
autorestart=true
numprocs=3
startretries=3
user=forge
redirect_stderr=true
stdout_logfile=/var/www/storage/logs/worker.log
---
## Laravel Horizon for SMS Queues
Horizon provides a beautiful dashboard and configuration for Redis queues.
### Installation
```bash
composer require laravel/horizon
php artisan horizon:install
---
### Configuration
Configure a dedicated SMS queue in `config/horizon.php`:
```php
'environments' => [
'production' => [
'sms-high-priority' => [
'connection' => 'redis',
'queue' => ['sms-high'],
'balance' => 'auto',
'minProcesses' => 1,
'maxProcesses' => 10,
'tries' => 3,
'timeout' => 30,
'nice' => 0,
],
'sms-bulk' => [
'connection' => 'redis',
'queue' => ['sms-bulk'],
'balance' => 'simple',
'processes' => 5,
'tries' => 1,
'timeout' => 60,
'nice' => 19,
],
],
],
---
### Running Horizon
```bash
php artisan horizon
---
### Monitoring via Supervisor
```ini
[program:laravel-horizon]
command=php /var/www/artisan horizon
user=forge
autostart=true
autorestart=true
startretries=3
stdout_logfile=/var/www/storage/logs/horizon.log
---
### Horizon Dashboard
Access `/horizon` in your browser to view:
- Queue throughput (jobs per minute)
- Failed jobs with full stack traces
- Job wait times per queue
- Worker count and status
- Recent job history with payload inspection
## Handling Failed SMS Jobs
### Inspecting Failures
```bash
php artisan queue:failed
---
### Retrying Failed Jobs
```bash
php artisan queue:retry all
---
### Custom Failure Handler
Register a listener in `AppServiceProvider`:
```php
<?php
namespace App\Providers;
use App\Jobs\HandleFailedSmsNotification;
use Illuminate\Notifications\Events\NotificationFailed;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
Event::listen(function (NotificationFailed $event) {
logger()->error('SMS notification failed', [
'notification' => get_class($event->notification),
'notifiable' => $event->notifiable?->id,
'channel' => $event->channel,
'exception' => $event->exception?->getMessage(),
]);
if ($event->channel === 'twilio') {
// Fall back to Vonage
$event->notifiable->notify(
(clone $event->notification)->viaFallback()
);
}
});
}
}
---
### The Failed Jobs Table
When all retries are exhausted, Laravel records the job in the `failed_jobs` table:
```sql
SELECT * FROM failed_jobs WHERE queue = 'sms' ORDER BY failed_at DESC;
---
## Monitoring Queue Performance
### Key Metrics
| Metric | What It Measures | Healthy Range |
|---|---|---|
| **Wait time** | Time a job waits before processing | < 5 seconds |
| **Throughput** | Jobs processed per minute | Depends on worker count |
| **Fail rate** | Percentage of failed jobs | < 1% |
| **Process time** | Average job duration | < 10 seconds per SMS |
### Horizon Metrics
Horizon exposes metrics via its API for external monitoring:
```bash
curl -u horizon:secret https://your-app.com/horizon/api/jobs
---
### Alerting
Use Laravel's notification system with failed job events to alert your team:
```php
use Illuminate\Queue\Events\JobFailed;
use Illuminate\Support\Facades\Event;
Event::listen(function (JobFailed $event) {
if ($event->job->getQueue() === 'sms') {
Notification::route('slack', config('services.slack.sms_alerts'))
->notify(new QueueFailedAlert($event));
}
});
---
## Performance Checklist
- [ ] Use Redis queue driver in production
- [ ] Implement `ShouldQueue` on all SMS notifications
- [ ] Configure dedicated Horizon workers per queue
- [ ] Set appropriate `tries` and `backoff` values
- [ ] Monitor failed jobs daily
- [ ] Use Supervisor or equivalent for process management
- [ ] Log queue metrics for observability
## Common Pitfalls
### Forgetting the Queue Worker
The most common mistake: queueing a notification but not running a worker.
```bash
# Don't forget this!
php artisan queue:work redis --queue=sms
---
### Memory Leaks in Workers
Long-running workers can accumulate memory. Restart periodically:
```bash
php artisan horizon:terminate
---
## Conclusion
Queueing SMS notifications is essential for production Laravel applications. With `ShouldQueue`, Horizon, and proper failure handling, you can send thousands of SMS without impacting HTTP response times, while maintaining full visibility into delivery status.
For a complete walkthrough of creating your first SMS notification, see [how to send SMS in Laravel](/blog/how-to-send-sms-in-laravel).
## Related Articles
- [How to Send SMS in Laravel (Step-by-Step Guide)](/blog/how-to-send-sms-in-laravel)
- [Ultimate Guide to Laravel SMS Integration (2026)](/blog/ultimate-guide-laravel-sms-integration)
- [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)