Laravel SMS Notifications Using Queues (Performance Guide)

Laravel SMS Notifications Using Queues (Performance Guide)
Laravel SMS Team
Laravel SMS Team
June 19, 2026

Laravel SMS Notifications Using Queues (Performance Guide)

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.

Why Queue SMS Notifications?

  • Faster response times — your HTTP response returns immediately; SMS sends in the background
  • Retry resilience — failed sends automatically retry without affecting the user
  • Bulk processing — thousands of SMS can be processed sequentially or in parallel by workers
  • Rate limiting — queues naturally throttle outbound API calls
  • Monitoring — track delivery success/failure via Horizon or failed job tables

Setting Up the Queue Driver

Database Driver (Development)

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)