SMS Gateway Integration for Laravel

Connect Any SMS Provider to Your Laravel Application

Laravel SMS Gateway Integration Service

Connect Any SMS Provider to Your Laravel Application

Choosing the right SMS gateway is critical, but integrating it correctly is where most projects struggle. Our gateway integration service ensures your Laravel application is connected to your SMS provider with optimal performance, proper error handling, and production-ready configuration.

Gateway Integration Expertise

We have deep experience integrating every major SMS provider into Laravel applications. Our integrations follow a consistent, maintainable pattern that makes it easy to switch providers later.

Universal Adapter Pattern

<?php

namespace App\Services\Sms\Contracts;

use App\Models\SmsMessage;

interface SmsProvider
{
    public function send(string $to, string $from, string $body): SmsMessage;
    public function sendBulk(array $messages): array;
    public function getStatus(string $messageId): array;
    public function getBalance(): float;
    public function getName(): string;
}
---

```php
<?php

namespace App\Services\Sms;

use App\Models\SmsMessage;
use App\Services\Sms\Contracts\SmsProvider;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Support\Facades\Http;

class TwilioProvider implements SmsProvider
{
    public function __construct(
        protected string $accountSid,
        protected string $authToken,
        protected string $from
    ) {}

    public function send(string $to, string $from, string $body): SmsMessage
    {
        $response = Http::withBasicAuth($this->accountSid, $this->authToken)
            ->asForm()
            ->post("https://api.twilio.com/2010-04-01/Accounts/{$this->accountSid}/Messages.json", [
                'To' => $to,
                'From' => $from ?: $this->from,
                'Body' => $body,
                'StatusCallback' => route('webhooks.sms.twilio'),
            ]);

        $response->throw();

        return SmsMessage::create([
            'provider' => 'twilio',
            'provider_message_id' => $response->json('sid'),
            'from' => $response->json('from'),
            'to' => $response->json('to'),
            'body' => $response->json('body'),
            'status' => $response->json('status'),
            'segments' => $response->json('num_segments', 1),
            'cost' => $response->json('price'),
            'sent_at' => now(),
        ]);
    }

    public function sendBulk(array $messages): array
    {
        $results = [];
        foreach ($messages as $message) {
            $results[] = $this->send(
                $message['to'],
                $message['from'] ?? $this->from,
                $message['body']
            );
        }
        return $results;
    }

    public function getStatus(string $messageId): array
    {
        $response = Http::withBasicAuth($this->accountSid, $this->authToken)
            ->get("https://api.twilio.com/2010-04-01/Accounts/{$this->accountSid}/Messages/{$messageId}.json");

        return $response->json();
    }

    public function getBalance(): float
    {
        $response = Http::withBasicAuth($this->accountSid, $this->authToken)
            ->get("https://api.twilio.com/2010-04-01/Accounts/{$this->accountSid}/Balance.json");

        return (float) ($response->json('balance') ?? 0);
    }

    public function getName(): string
    {
        return 'twilio';
    }
}
---

## Supported Providers

| Provider | Experience Level | Typical Integration Time |
|----------|-----------------|------------------------|
| **Twilio** | 50+ integrations | 1-2 days |
| **Vonage (Nexmo)** | 30+ integrations | 1-2 days |
| **AWS SNS** | 25+ integrations | 2-3 days |
| **Plivo** | 15+ integrations | 1-2 days |
| **MessageBird** | 10+ integrations | 1-2 days |
| **Sinch** | 10+ integrations | 2-3 days |
| **Infobip** | 20+ integrations | 2-3 days |
| **Clickatell** | 8+ integrations | 1-2 days |
| **Custom SMPP** | 12+ integrations | 3-5 days |
| **Any REST/SOAP API** | 100+ integrations | Varies |

## Migration Between Gateways

Switching providers is a common need — pricing changes, feature requirements, or consolidation. We handle the entire migration process:

```php
<?php

namespace App\Services\Sms;

use App\Services\Sms\Contracts\SmsProvider;

class SmsManager
{
    public function __construct(
        protected SmsProvider $primary,
        protected ?SmsProvider $fallback = null
    ) {}

    public function send(string $to, string $from, string $body): SmsMessage
    {
        try {
            return $this->primary->send($to, $from, $body);
        } catch (\Exception $e) {
            if ($this->fallback) {
                logger()->warning('Primary SMS provider failed, using fallback', [
                    'provider' => $this->primary->getName(),
                    'error' => $e->getMessage(),
                ]);

                return $this->fallback->send($to, $from, $body);
            }

            throw $e;
        }
    }

    public function switchProvider(SmsProvider $newProvider): void
    {
        // Graceful provider switching with message routing
        $this->primary = $newProvider;

        logger()->info('SMS provider switched', [
            'new_provider' => $newProvider->getName(),
        ]);
    }
}
---

## Webhook Setup

We configure and test webhook endpoints for delivery status, inbound messages, and error reporting:

```php
<?php

namespace App\Http\Controllers\Webhooks;

use App\Models\SmsMessage;
use Illuminate\Http\Request;
use Illuminate\Support\Str;

class UniversalSmsWebhookController extends Controller
{
    public function __invoke(Request $request, string $provider)
    {
        $normalized = match ($provider) {
            'twilio' => $this->normalizeTwilio($request),
            'vonage' => $this->normalizeVonage($request),
            'aws-sns' => $this->normalizeAwsSns($request),
            default => throw new \InvalidArgumentException("Unknown provider: {$provider}"),
        };

        $message = SmsMessage::where(
            'provider_message_id',
            $normalized['message_id']
        )->first();

        if (!$message) {
            return response('OK');
        }

        $message->recordStatus(
            $normalized['status'],
            $normalized['error_code'] ?? null,
            $normalized['error_message'] ?? null,
        );

        return response('OK');
    }

    protected function normalizeTwilio(Request $request): array
    {
        return [
            'message_id' => $request->input('MessageSid'),
            'status' => $request->input('MessageStatus'),
            'error_code' => $request->input('ErrorCode'),
            'error_message' => $request->input('ErrorMessage'),
        ];
    }

    protected function normalizeVonage(Request $request): array
    {
        $statusMap = [
            'delivered' => 'delivered',
            'expired' => 'expired',
            'failed' => 'failed',
            'rejected' => 'failed',
            'accepted' => 'accepted',
            'buffered' => 'queued',
        ];

        return [
            'message_id' => $request->input('messageId'),
            'status' => $statusMap[$request->input('status')] ?? 'unknown',
            'error_code' => $request->input('err-code'),
            'error_message' => $request->input('err-text'),
        ];
    }

    protected function normalizeAwsSns(Request $request): array
    {
        $message = json_decode($request->getContent(), true);

        return [
            'message_id' => $message['messageId'] ?? $message['notification']['messageId'],
            'status' => $message['status'] ?? $message['notification']['status'],
            'error_code' => $message['errorCode'] ?? null,
            'error_message' => $message['errorMessage'] ?? null,
        ];
    }
}
---

## Testing & Quality Assurance

Our integration testing covers every edge case:

```php
<?php

namespace Tests\Unit\Sms;

use App\Services\Sms\TwilioProvider;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;

class TwilioProviderTest extends TestCase
{
    protected TwilioProvider $provider;

    protected function setUp(): void
    {
        parent::setUp();
        $this->provider = new TwilioProvider(
            accountSid: 'ACtest',
            authToken: 'test-token',
            from: '+1234567890'
        );
    }

    public function test_sends_sms_successfully()
    {
        Http::fake([
            'api.twilio.com/*' => Http::response([
                'sid' => 'SMtest123',
                'status' => 'queued',
                'from' => '+1234567890',
                'to' => '+0987654321',
                'body' => 'Test message',
                'num_segments' => 1,
                'price' => '0.0075',
            ], 201),
        ]);

        $message = $this->provider->send('+0987654321', null, 'Test message');

        $this->assertEquals('twilio', $message->provider);
        $this->assertEquals('queued', $message->status);
    }

    public function test_handles_api_error()
    {
        Http::fake([
            'api.twilio.com/*' => Http::response([
                'message' => 'The number is not a valid phone number',
                'status' => 400,
                'code' => 21211,
            ], 400),
        ]);

        $this->expectException(\Illuminate\Http\Client\RequestException::class);

        $this->provider->send('invalid', null, 'Test message');
    }

    public function test_checks_balance()
    {
        Http::fake([
            'api.twilio.com/*/Balance.json' => Http::response([
                'balance' => '45.50',
                'currency' => 'USD',
            ]),
        ]);

        $balance = $this->provider->getBalance();

        $this->assertEquals(45.50, $balance);
    }
}
---

## Delivery Optimization

We don't just integrate — we optimize:

- **Message concatenation** — Proper handling of long messages (segmentation)
- **Unicode support** — Automatic GSM/Unicode detection and encoding
- **Retry logic** — Exponential backoff with configurable max attempts
- **Rate limiting** — Provider-specific rate limit compliance
- **Fallback routing** — Automatic failover to secondary provider

```php
<?php

namespace App\Services\Sms;

class SmsOptimizer
{
    public function prepareMessage(string $body): array
    {
        $encoding = $this->detectEncoding($body);
        $segments = $this->calculateSegments($body, $encoding);

        return [
            'body' => $body,
            'encoding' => $encoding,
            'segments' => $segments,
            'character_count' => strlen($body),
        ];
    }

    protected function detectEncoding(string $body): string
    {
        $gsmChars = '@£$¥èéùìòÇØøÅåΔ_ΦΓΛΩΠΨΣΘΞÆæßÉ !"#¤%&\'()*+,-./0123456789:;<=>?¡ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑܧ¿abcdefghijklmnopqrstuvwxyzäöñüà';

        for ($i = 0; $i < strlen($body); $i++) {
            if (!str_contains($gsmChars, $body[$i])) {
                return 'unicode';
            }
        }

        return 'gsm';
    }

    protected function calculateSegments(string $body, string $encoding): int
    {
        $maxLen = $encoding === 'gsm' ? 160 : 70;
        $multiMax = $encoding === 'gsm' ? 153 : 67;

        $len = strlen($body);
        if ($len <= $maxLen) {
            return 1;
        }

        return (int) ceil($len / $multiMax);
    }
}
---

## Delivery Optimization Results

| Optimization | Before | After |
|-------------|--------|-------|
| Delivery rate | 92% | 98.5% |
| Cost per message | $0.0065 | $0.0042 |
| Failed messages | 8% | 1.5% |
| Average delivery time | 8.2s | 2.1s |

## Hire Us

Need a production-grade SMS gateway integration for your Laravel application? We'll have you up and running in days, not weeks.

[Hire Us](/contact)

Already integrated but need to upgrade? Explore our [Laravel SMS API](/services/laravel-sms-api) for a managed solution, or our [enterprise SMS solution](/services/enterprise-sms-solution-laravel) for high-volume needs. Looking for something completely custom? See our [custom development services](/services/custom-laravel-sms-development).

Ready to Get Started?

Contact us today to discuss your Laravel SMS needs.

Hire Us