Laravel-native multi-provider SMS package with typed results, queues, events, and accurate message segmentation.
acolyte/sms-laravel is a Laravel package for laravel-native multi-provider sms package with typed results, queues, events, and accurate message segmentation..
It currently has 4 GitHub stars and 305 downloads on Packagist (latest version 2.0.1).
Install it with composer require acolyte/sms-laravel.
Discover more Laravel packages by acolyte
or browse all Laravel packages to compare alternatives.
Last updated
A Laravel-native multi-provider SMS gateway manager with driver switching, typed messages/results, queue support, events, provider failure handling, and accurate SMS segmentation.
Laravel SMS keeps provider transport small and replaceable. Applications send immutable SmsMessage objects through a Laravel-style manager and receive a predictable SmsResult; billing, persistence, and provider-specific payloads remain outside the core API.
Application
│
▼
Sms Facade / SmsManager ─────► SmsSending
│ ├► SmsSent
├── synchronous send └► SmsFailed
└── queued send
│
▼
SmsDriver
├── bundled provider driver
└── custom driver
│
▼
Laravel HTTP Client
SmsSending, SmsSent, and SmsFailed eventsrouteNotificationForSms() supportLaravel 13 combinations require a PHP version accepted by Laravel 13. See the CI matrix for the combinations continuously tested by this branch.
composer require acolyte/sms-laravel:^2.0
php artisan vendor:publish --tag=sms-config
Laravel discovers the service provider and Sms facade automatically.
Add provider-specific values to .env:
SMS_DRIVER=onnorokom
SMS_ONNOROKOM_URL=https://api2.onnorokomsms.com/sendsms.asmx
SMS_ONNOROKOM_API_KEY=replace-me
SMS_ONNOROKOM_SENDER=Acme
SMS_ONNOROKOM_CONNECT_TIMEOUT=3
SMS_ONNOROKOM_TIMEOUT=10
SMS_ONNOROKOM_RETRIES=2
SMS_ONNOROKOM_RETRY_DELAY=200
The bundled onnorokom driver implements the provider's published SOAP NumberSms contract. Its API key is XML-escaped inside the HTTPS request body and is never added to the request URL. The driver automatically selects the provider's TEXT or UCS mode using the segment calculator.
Do not disable TLS certificate verification. Confirm the provider endpoint has a valid, trusted certificate chain before production deployment; if it does not, contact the provider or select a custom driver.
use Acolyte\SmsLaravel\Data\SmsMessage;
use Acolyte\SmsLaravel\Facades\Sms;
$result = Sms::send(new SmsMessage(
to: '+49123456789',
body: 'Your verification code is 482913.',
sender: 'Acme',
clientReference: 'verification-8f32',
));
if (! $result->successful) {
report("SMS failed: {$result->errorCode}");
}
Whitespace is removed from recipient numbers. Use E.164-formatted numbers; the package intentionally does not guess country codes or maintain unreliable country-specific regular expressions.
SmsMessage accepts the recipient, body, and optional sender and client reference. Empty recipients, empty bodies, and invalid UTF-8 are rejected with InvalidArgumentException before a provider call.
$result = Sms::send(new SmsMessage('+49123456789', 'Hello from Laravel.'));
Pass a driver name to send() or resolve a driver directly:
$result = Sms::send($message, driver: 'backup');
$result = Sms::driver('backup')->send($message);
Implement the deliberately small contract and register the driver during application boot:
use Acolyte\SmsLaravel\Contracts\SmsDriver;
use Acolyte\SmsLaravel\Data\SmsMessage;
use Acolyte\SmsLaravel\Data\SmsResult;
use Acolyte\SmsLaravel\Facades\Sms;
final class BackupSmsDriver implements SmsDriver
{
public function send(SmsMessage $message): SmsResult
{
// Call the provider through an injected client.
return SmsResult::success(providerMessageId: 'provider-id');
}
}
Sms::extend('backup', fn ($app) => $app->make(BackupSmsDriver::class));
Custom drivers should return typed failures for expected provider outcomes and avoid putting credentials or complete raw payloads in metadata.
Every synchronous send returns SmsResult:
$result->successful; // bool
$result->providerMessageId; // ?string
$result->errorCode; // ?string
$result->errorMessage; // ?string
$result->metadata; // array<string, mixed>
Drivers can use SmsResult::success() and SmsResult::failure() to preserve those invariants.
use Acolyte\SmsLaravel\Support\SmsSegmentCalculator;
$segments = (new SmsSegmentCalculator)->calculate('Balance: 10€');
$segments->encoding; // GSM-7
$segments->characters; // visible Unicode code points
$segments->units; // encoded septets or UTF-16 code units
$segments->segments; // billable transport segments
$segments->unitsPerSegment;
GSM-7 messages use 160 septets for one part and 153 per concatenated part. Extension-table characters (^, {, }, \\, [, ], ~, |, €) consume two septets. Any unsupported GSM character selects UCS-2, with 70 UTF-16 code units for one part and 67 per concatenated part. Non-BMP characters such as emoji consume a UTF-16 surrogate pair (two units). An empty string calculates as zero segments, although SmsMessage does not allow an empty body.
Queueing serializes only the typed message and selected driver name. The worker resolves the driver when the job executes, so runtime driver configuration remains authoritative.
Sms::queue(new SmsMessage('+49123456789', 'Queued message'));
Sms::queue($message, driver: 'backup');
Configure SMS_QUEUE_CONNECTION, SMS_QUEUE, and SMS_QUEUE_TRIES as needed. Failed results throw a sanitized SmsSendingFailed exception so Laravel can apply the job's configured attempts and backoff (10, 60, and 300 seconds by default).
The manager dispatches SmsSending before transport. It then dispatches exactly one of SmsSent or SmsFailed. Events contain the message, driver name, typed result where applicable, and an immutable timestamp—never credentials.
use Acolyte\SmsLaravel\Events\SmsFailed;
use Illuminate\Support\Facades\Event;
Event::listen(SmsFailed::class, function (SmsFailed $event): void {
logger()->warning('SMS delivery failed', [
'driver' => $event->driver,
'code' => $event->result->errorCode,
]);
});
Avoid logging complete phone numbers or message bodies unless your privacy and retention policy explicitly permits it.
Return 'sms' from via() and implement SmsNotification. A notification can return a complete SmsMessage, or a body string that uses routeNotificationForSms() on the notifiable.
use Acolyte\SmsLaravel\Contracts\SmsNotification;
use Illuminate\Notifications\Notification;
final class InvoicePaid extends Notification implements SmsNotification
{
public function via(object $notifiable): array
{
return ['sms'];
}
public function toSms(object $notifiable): string
{
return 'We received your payment. Thank you.';
}
}
public function routeNotificationForSms(Notification $notification): string
{
return $this->phone_number;
}
Expected failures are represented as unsuccessful results. The bundled driver maps configuration errors, invalid requests, authentication failures, insufficient provider balance, rate limits, timeouts, network failures, server errors, malformed responses, and unknown provider errors to stable codes. HTTP status and provider code may appear in limited metadata; raw bodies and credentials do not.
Unexpected custom-driver exceptions are converted to driver_exception without copying exception details. Validate and monitor failures using SmsFailed rather than parsing provider strings.
Database logging is not required or bundled. Persist or observe sends with event listeners appropriate to your application. This keeps migrations, retention rules, encryption, and redaction under application control.
| Key | Environment variable | Default | Purpose |
| --- | --- | --- | --- |
| default | SMS_DRIVER | onnorokom | Default driver name |
| drivers.onnorokom.base_url | SMS_ONNOROKOM_URL | official SOAP endpoint | Provider service endpoint |
| drivers.onnorokom.api_key | SMS_ONNOROKOM_API_KEY | empty | Secret credential |
| drivers.onnorokom.sender | SMS_ONNOROKOM_SENDER | null | Default mask/sender |
| drivers.onnorokom.connect_timeout | SMS_ONNOROKOM_CONNECT_TIMEOUT | 3 | Connection timeout in seconds |
| drivers.onnorokom.timeout | SMS_ONNOROKOM_TIMEOUT | 10 | Total timeout in seconds |
| drivers.onnorokom.retries | SMS_ONNOROKOM_RETRIES | 2 | Transient retry count |
| drivers.onnorokom.retry_delay | SMS_ONNOROKOM_RETRY_DELAY | 200 | Retry delay in milliseconds |
| queue.connection | SMS_QUEUE_CONNECTION | null | Laravel queue connection |
| queue.queue | SMS_QUEUE | null | Queue name |
| queue.tries | SMS_QUEUE_TRIES | 3 | Job attempt limit |
composer format:test
composer analyse
composer test
composer audit
# or all checks:
composer check
No automated test makes a live provider request.
| Package | PHP | Laravel |
| --- | --- | --- |
| 1.x | legacy | legacy |
| 2.x | ^8.2 | ^12, ^13 |
Version 2 is a deliberate major-version redesign. The deprecated Acolyte\SmsLaravel\SMS::send(array) adapter remains for staged migration, but database balance checks, automatic log writes, controller routes, views, models, migrations, checkBalance(), and getUrl() were removed. Read UPGRADE.md before upgrading.
See CONTRIBUTING.md. Changes must pass formatting, level-8 analysis, tests, and dependency audit.
Please report vulnerabilities privately as described in SECURITY.md. Do not open public issues containing credentials, phone numbers, or SMS bodies.
See CHANGELOG.md.
Laravel SMS is open-source software licensed under the MIT license.