Transactional SMS in Laravel (Order Confirmations, Alerts & More)

Transactional SMS in Laravel (Order Confirmations, Alerts & More)
Laravel SMS Team
Laravel SMS Team
June 23, 2026

Transactional SMS in Laravel (Order Confirmations, Alerts & More)

Transactional SMS messages are automated, triggered communications sent to individual users based on specific actions or events. Unlike marketing blasts, transactional SMS is expected, anticipated, and often critical to the user experience. Think order confirmations, shipping updates, password resets, appointment reminders, and two-factor authentication codes.

This guide walks you through implementing transactional SMS in Laravel using notifications, queues, and event-driven architecture.

What is Transactional SMS vs Marketing SMS?

The distinction matters legally and practically:

AspectTransactional SMSMarketing SMS
ConsentExisting business relationship assumedRequires explicit opt-in
ContentAccount activity, order updates, securityPromotions, sales, newsletters
FrequencyTriggered by user actionScheduled campaigns
Opt-outTypically not required by lawMust include opt-out
PriorityHigh — delivered ASAPLower — batched delivery

Transactional messages enjoy higher delivery rates and faster throughput because carriers recognize them as time-sensitive.

Building an Order Confirmation Notification

Laravel’s notification system is the ideal foundation for transactional SMS. Start by creating a notification:

php artisan make:notification OrderConfirmation
---

Configure it to send via SMS:

```php
<?php

namespace App\Notifications;

use App\Models\Order;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;

class OrderConfirmation extends Notification
{
    use Queueable;

    public function __construct(
        public Order $order
    ) {}

    public function via(object $notifiable): array
    {
        // Prefer SMS, fall back to email
        return $notifiable->phone ? ['sms'] : ['mail'];
    }

    public function toSms(object $notifiable): string
    {
        return sprintf(
            'Order #%s confirmed! Your %s will ship to %s. Total: $%s. Track at %s/tracking/%s',
            $this->order->order_number,
            $this->order->items->count() . ' item(s)',
            $this->order->shipping_address,
            number_format($this->order->total, 2),
            config('app.url'),
            $this->order->tracking_number
        );
    }
}
---

Send it from your controller after order placement:

```php
<?php

namespace App\Http\Controllers;

use App\Models\Order;
use App\Notifications\OrderConfirmation;
use Illuminate\Http\Request;

class OrderController extends Controller
{
    public function store(Request $request)
    {
        $order = Order::create($request->validated());

        // Process payment...

        // Send transactional SMS
        $order->user->notify(new OrderConfirmation($order));

        return redirect()->route('orders.show', $order);
    }
}
---

## Shipping Status Updates

Keep customers informed as their order progresses. Create a dedicated notification:

```php
<?php

namespace App\Notifications;

use App\Models\Shipment;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;

class ShippingUpdate extends Notification
{
    use Queueable;

    public function __construct(
        public Shipment $shipment,
        public string $status,
        public ?string $location = null
    ) {}

    public function via(object $notifiable): array
    {
        $preferences = $notifiable->notificationPreferences()
            ->forType('shipping_updates')
            ->first();

        return $preferences?->channel === 'sms' || !$preferences
            ? ['sms']
            : [$preferences->channel];
    }

    public function toSms(object $notifiable): string
    {
        $messages = [
            'shipped' => "Your order has shipped! Tracking: {$this->shipment->tracking_number}. Estimated delivery: {$this->shipment->estimated_delivery->format('M d')}.",
            'in_transit' => "Your package is in transit. Current location: {$this->location}. ETA: {$this->shipment->estimated_delivery->format('M d')}.",
            'out_for_delivery' => "Your package is out for delivery today! Expect it by {$this->shipment->delivery_window}.",
            'delivered' => "Your package was delivered! Thank you for your order.",
            'exception' => "There's a delay with your shipment. Current status: {$this->shipment->exception_detail}. We'll update you soon.",
        ];

        return $messages[$this->status] ?? "Shipping update: {$this->status}{$this->shipment->tracking_number}";
    }
}
---

Trigger updates from your shipping carrier webhook:

```php
<?php

namespace App\Http\Controllers\Webhooks;

use App\Models\Shipment;
use App\Notifications\ShippingUpdate;
use Illuminate\Http\Request;

class ShippingWebhookController extends Controller
{
    public function __invoke(Request $request)
    {
        $event = $request->input('event');
        $tracking = $request->input('tracking_number');

        $shipment = Shipment::where('tracking_number', $tracking)->firstOrFail();

        $shipment->order->user->notify(
            new ShippingUpdate($shipment, $event, $request->input('location'))
        );

        return response('OK');
    }
}
---

## Appointment Reminders

Transactional SMS drastically reduces no-shows for appointment-based businesses:

```php
<?php

namespace App\Notifications;

use App\Models\Appointment;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;

class AppointmentReminder extends Notification
{
    use Queueable;

    public function __construct(
        public Appointment $appointment,
        public string $reminderType = 'upcoming' // 'upcoming', 'followup', 'rescheduled'
    ) {}

    public function via(object $notifiable): array
    {
        return ['sms'];
    }

    public function toSms(object $notifiable): string
    {
        $timezone = $notifiable->timezone ?? 'UTC';
        $localTime = $this->appointment->starts_at->setTimezone($timezone);

        $messages = [
            'upcoming' => "Reminder: Your appointment is {$localTime->diffForHumans()} on {$localTime->format('D, M j g:i A')}. Reply C to confirm or R to reschedule.",
            'followup' => "How was your appointment? Reply with a rating (1-5) or visit " . config('app.url') . "/feedback/{$this->appointment->id} to leave feedback.",
            'rescheduled' => "Your appointment has been rescheduled to {$localTime->format('D, M j g:i A')}. Reply C to confirm.",
        ];

        return $messages[$this->reminderType];
    }
}
---

## Using Laravel Notifications for Transactional Messages

Laravel's notification system provides everything you need for transactional SMS:

```php
<?php

namespace App\Notifications;

use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\VonageMessage;

class TwoFactorCode extends Notification
{
    public function __construct(
        public string $code
    ) {}

    public function via(object $notifiable): array
    {
        return ['vonage'];
    }

    public function toVonage(object $notifiable): VonageMessage
    {
        return (new VonageMessage)
            ->content("Your verification code is: {$this->code}. It expires in 10 minutes.")
            ->from(config('services.vonage.sms_from'));
    }
}
---

You can route to different providers based on the notification type:

```php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable;

    public function routeNotificationForSms(): ?string
    {
        return $this->phone;
    }

    public function routeNotificationForVonage(): ?string
    {
        // Use Vonage specifically for security-critical messages
        return $this->phone;
    }
}
---

## Personalization with Order Data

Transactional messages perform best when they include specific, relevant data:

```php
<?php

namespace App\Notifications;

use App\Models\Order;

class OrderShipped extends Notification
{
    public function __construct(
        public Order $order
    ) {}

    public function toSms(object $notifiable): string
    {
        return collect([
            "📦 Your order #{$this->order->order_number} has shipped!",
            "",
            "Items:",
            ...$this->order->items->map(fn($item) => "  • {$item->quantity}x {$item->name}"),
            "",
            "Tracking: {$this->order->tracking_url}",
            "Est. delivery: {$this->order->estimated_delivery->format('D, M j')}",
            "",
            "Thank you for shopping with us!",
        ])->implode("\n");
    }
}
---

## Delivery Guarantees for Transactional SMS

Transactional SMS requires high delivery reliability. Implement these strategies:

**Queue with high-priority channel:**

```php
<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;

class CriticalAlert extends Notification implements ShouldQueue
{
    use Queueable;

    public function __construct()
    {
        // High-priority queue for transactional messages
        $this->onQueue('critical');
    }
}
---

**Monitor delivery status:**

```php
<?php

namespace App\Console\Commands;

use App\Models\NotificationLog;
use Illuminate\Console\Command;

class RetryFailedTransactional extends Command
{
    protected $signature = 'sms:retry-failed {--hours=1}';
    protected $description = 'Retry failed transactional SMS messages';

    public function handle()
    {
        $failed = NotificationLog::query()
            ->where('status', 'failed')
            ->where('type', 'transactional')
            ->where('created_at', '>=', now()->subHours($this->option('hours')))
            ->get();

        foreach ($failed as $log) {
            $log->retry();
            $this->info("Retried notification #{$log->id}");
        }
    }
}
---

## Best Practices

1. **Always queue transactional notifications** — never send SMS synchronously in request lifecycle
2. **Include clear sender identification** — use your business name as the sender ID
3. **Respect quiet hours** — avoid sending between 9 PM and 8 AM unless critical
4. **Provide next-step context** — tracking links, appointment links, support numbers
5. **Log everything** — store message IDs, statuses, and timestamps for audit trails

## Testing Transactional SMS

```php
<?php

namespace Tests\Feature;

use App\Models\Order;
use App\Notifications\OrderConfirmation;
use Illuminate\Support\Facades\Notification;
use Tests\TestCase;

class TransactionalSmsTest extends TestCase
{
    public function test_order_confirmation_sends_sms()
    {
        Notification::fake();

        $order = Order::factory()->create();
        $order->user->notify(new OrderConfirmation($order));

        Notification::assertSentTo(
            $order->user,
            OrderConfirmation::class,
            function ($notification) use ($order) {
                return $notification->order->is($order);
            }
        );
    }
}
---

## Conclusion

Transactional SMS is one of the highest-ROI features you can add to a Laravel application. Whether you're running an ecommerce store, a SaaS platform, or a service business, automated SMS notifications improve customer experience and reduce support load.

Use the [Laravel SMS API](/services/laravel-sms-api) to get started with transactional messaging, or explore our [enterprise SMS solution](/services/enterprise-sms-solution-laravel) for high-volume needs.

For more on customer notifications, see our guide on [building a notification software platform](/services/laravel-customer-notification-software).