laravel-notification-channels/touch-sms
A Laravel notification channel for sending SMS through Touch SMS. Connect the Touch SMS gateway to Laravel's notification system and deliver messages from any notification class using a dedicated channel and simple configuration. Useful for transactional messages and alerts.
The package is a standard Laravel notification channel, so the setup mirrors Vonage/Twilio-style channels you already know. As of 2.1.0 it supports Laravel 12.x, so you can use it on current projects without a version pin conflict.
1. Install and let package discovery register the provider:
composer require laravel-notification-channels/touch-sms
2. Add credentials. Either publish the package config or add a block to config/services.php (check the README for the exact key name — these channels usually read services.touch_sms.* or a dedicated config/touch-sms.php):
// config/services.php
'touch_sms' => [
'token' => env('TOUCH_SMS_TOKEN'),
'from' => env('TOUCH_SMS_FROM'), // sender ID / number registered with TouchSMS
],
TOUCH_SMS_TOKEN=your-api-token
TOUCH_SMS_FROM=YourBrand
3. Make your notifiable routable. The channel needs a recipient number, so add a routing method to the model:
public function routeNotificationForTouchSms(): ?string
{
return $this->phone; // E.164, e.g. +61412345678
}
4. First use case — a shipping or OTP notification:
use Illuminate\Notifications\Notification;
use NotificationChannels\TouchSms\TouchSmsChannel;
use NotificationChannels\TouchSms\TouchSmsMessage;
class OrderShipped extends Notification
{
public function via($notifiable): array
{
return [TouchSmsChannel::class];
}
public function toTouchSms($notifiable): TouchSmsMessage
{
return TouchSmsMessage::create()
->content("Order #{$this->order->id} is on its way.");
}
}
Trigger with $user->notify(new OrderShipped($order)); and confirm the send landed in the TouchSMS dashboard.
Where to look first: the README (fluent methods on TouchSmsMessage), then src/TouchSmsChannel.php (~100 lines, easy to read end-to-end) and the service provider. Because the package is young and low-star, reading the channel source is the fastest way to understand exactly which HTTP endpoint and payload shape it emits.
Fluent message construction. The message object is the natural unit of reuse — compose it in the notification's toTouchSms() method and keep string interpolation out of the channel:
public function toTouchSms($notifiable): TouchSmsMessage
{
return TouchSmsMessage::create()
->content($this->body)
->from(config('services.touch_sms.from')); // override per-message
}
Multi-channel fan-out. Pair SMS with mail/database so users can opt into SMS without losing the durable record:
public function via($notifiable): array
{
return $notifiable->sms_opt_in
? [TouchSmsChannel::class, 'mail', 'database']
: ['mail', 'database'];
}
Always queue SMS. Notifications should implement ShouldQueue — SMS APIs are the slowest, flakiest dependency in the request path:
class OrderShipped extends Notification implements ShouldQueue
{
public int $tries = 3;
public int $backoff = 30;
}
The channel itself typically does no retrying, so queue-level $tries/$backoff is your resilience story.
Testing without hitting TouchSMS. Fakes keep the suite fast:
Notification::fake();
$user->notify(new OrderShipped($order));
Notification::assertSentTo($user, OrderShipped::class);
// If you want to assert the payload, use the message object directly:
$message = (new OrderShipped($order))->toTouchSms($user);
$this->assertStringContainsString('Order #', $message->content);
If you need to assert on HTTP shape, Http::fake() and assert the URL/body rather than mocking the channel class.
Wrapping in a domain service. For OTPs, wrap in a small service so tokens, expiry, and rate-limiting live in your code, not in notifications:
class OtpService
{
public function send(User $user): void
{
$code = Str::random(6);
Cache::put("otp:{$user->id}", $code, now()->addMinutes(5));
$user->notify(new SendOtp($code));
}
}
Integration tips.
via() rather than a string alias — it's clearer and survives refactors.NotificationSent/NotificationFailed events.Phone number format is on you. The channel does not normalize numbers. Store and pass E.164 (+61412345678); mixing local (0412...) and international formats is the most common "message never arrived" bug.
Sender ID rules bite. Australian numbers have strict alphanumeric sender ID registration. An unregistered from may be rejected or silently rewritten to a shared shortcode. If you change TOUCH_SMS_FROM for a new brand, test in sandbox first.
config:cache freezes credentials. After changing .env, run php artisan config:clear in local dev. A stale cached config with the old token is an easy 90-minute debug session.
Segment math. A single message segment is ~160 GSM characters; anything longer is billed multiple times and may be split by the carrier. Trim notification bodies, or explicitly track length:
if (mb_strlen($content) > 160) { /* split or shorten */ }
Token-in-URL risk. TouchSMS's REST API historically accepts the token as a query parameter. If the package does this, the token can leak into HTTP client logs, Telescope, or exception messages. Turn off Http::globalOptions() debug logging for the TouchSMS host, and never dump request objects in exception handlers.
routeNotificationForTouchSms must exist. Forgetting it produces a NotificationException at dispatch time, not at registration — surprising in queue workers where the failure is silent until you check failed_jobs. Add a fallback:
public function routeNotificationForTouchSms()
{
return $this->phone ?: null; // channel skips if null in most implementations
}
Verify the channel's null handling in its source; if it throws instead of returning early, guard your via() accordingly.
Version pinning and framework support. The 2.1.0 release is a Laravel 12.x support bump — no API changes to the channel or message classes, so no migration work if you're already on 2.x. Pin to the current line so you get future Laravel compatibility fixes without surprise majors:
"laravel-notification-channels/touch-sms": "^2.1"
The package remains small (five stars, zero dependents), so still vendor-review TouchSmsChannel.php before upgrading. If your needs outgrow it (delivery receipts, batch sends, template IDs), swapping to a thin custom Http:: client is a ~50-line change — the channel interface is the only contract you need to preserve.
Extension points.
TouchSmsChannel and override the send method to add logging, metrics, or fallback providers.TouchSmsMessage construction in a factory/builder so all SMS copy lives in one place.Illuminate\Notifications\Events\NotificationFailed to alert on repeated TouchSMS failures — the channel's own error reporting is minimal.Debugging order of operations: (1) routeNotificationForTouchSms returns the right number, (2) token present via config('services.touch_sms.token') after config:cache, (3) sender ID registered for your destination country, (4) Http::fake a known-good payload against the real API with Http::withOptions(['debug' => true]) once.
How can I help you explore Laravel packages today?