expertsystemsau / transmitsms-laravel-client
Laravel notification channel and integration for the TransmitSMS API
Installs: 19
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/expertsystemsau/transmitsms-laravel-client
Requires
- php: ^8.2
- expertsystemsau/transmitsms-php-client: ^1.0
- illuminate/notifications: ^10.0||^11.0||^12.0
- illuminate/support: ^10.0||^11.0||^12.0
- saloonphp/laravel-plugin: ^3.0
Requires (Dev)
- mockery/mockery: ^1.6
- orchestra/testbench: ^9.0||^10.0
- phpunit/phpunit: ^11.0
README
Laravel notification channel and integration for the TransmitSMS API.
Installation
composer require expertsystemsau/transmitsms-laravel
Publish the configuration file:
php artisan vendor:publish --tag="transmitsms-config"
Configuration
Add your credentials to your .env file:
TRANSMITSMS_API_KEY=your-api-key TRANSMITSMS_API_SECRET=your-api-secret TRANSMITSMS_FROM=YourSenderID
Usage
Facade
use ExpertSystems\TransmitSms\Laravel\Facades\TransmitSms; // Send an SMS TransmitSms::sendSms('+61400000000', 'Hello from Laravel!'); // Get account balance $balance = TransmitSms::getBalance();
Notifications
Create a notification that uses the TransmitSMS channel:
use Illuminate\Notifications\Notification; use ExpertSystems\TransmitSms\Laravel\Notifications\TransmitSmsMessage; class OrderShipped extends Notification { public function via($notifiable): array { return ['transmitsms']; } public function toTransmitSms($notifiable): TransmitSmsMessage { return TransmitSmsMessage::create('Your order has been shipped!') ->from('MyStore'); } }
Add the routeNotificationForTransmitsms method to your notifiable model:
class User extends Authenticatable { use Notifiable; public function routeNotificationForTransmitsms($notification): ?string { return $this->phone_number; } }
Then send notifications:
$user->notify(new OrderShipped());
DLR & Reply Callbacks
The package provides automatic handling for DLR (Delivery Receipt), Reply, and Link Hit callbacks. When you send an SMS, you can specify a job to be dispatched when a callback is received.
Quick Start
use App\Jobs\UpdateOrderSmsStatusJob; use App\Jobs\ProcessCustomerReplyJob; use ExpertSystems\TransmitSms\Laravel\Notifications\TransmitSmsMessage; class OrderShipped extends Notification { public function __construct(public Order $order) {} public function via($notifiable): array { return ['transmitsms']; } public function toTransmitSms($notifiable): TransmitSmsMessage { return TransmitSmsMessage::create("Your order #{$this->order->id} has shipped!") ->from('MYSTORE') ->onDlr(UpdateOrderSmsStatusJob::class, [ 'order_id' => $this->order->id, ]) ->onReply(ProcessCustomerReplyJob::class, [ 'order_id' => $this->order->id, 'customer_id' => $notifiable->id, ]); } }
Creating Handler Jobs
DLR Handler Job:
namespace App\Jobs; use App\Models\Order; use ExpertSystems\TransmitSms\Data\DlrCallbackData; use ExpertSystems\TransmitSms\Laravel\Contracts\HandlesDlrCallback; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Queue\InteractsWithQueue; class UpdateOrderSmsStatusJob implements HandlesDlrCallback, ShouldQueue { use InteractsWithQueue, Queueable; public function __construct( public DlrCallbackData $dlr, public array $context, ) {} public function handle(): void { $order = Order::find($this->context['order_id']); $order->update([ 'sms_status' => $this->dlr->status, 'sms_delivered_at' => $this->dlr->isDelivered() ? now()->parse($this->dlr->datetime) : null, ]); if ($this->dlr->isFailed()) { // Handle failure - maybe send email instead Log::warning('SMS delivery failed', [ 'order_id' => $order->id, 'error' => $this->dlr->errorDescription, ]); } } }
Reply Handler Job:
namespace App\Jobs; use App\Models\SmsConversation; use ExpertSystems\TransmitSms\Data\ReplyCallbackData; use ExpertSystems\TransmitSms\Laravel\Contracts\HandlesReplyCallback; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; class ProcessCustomerReplyJob implements HandlesReplyCallback, ShouldQueue { use Queueable; public function __construct( public ReplyCallbackData $reply, public array $context, ) {} public function handle(): void { SmsConversation::create([ 'order_id' => $this->context['order_id'], 'customer_id' => $this->context['customer_id'], 'direction' => 'inbound', 'message' => $this->reply->message, 'mobile' => $this->reply->mobile, 'received_at' => $this->reply->receivedAt, ]); } }
Link Hit Handler Job:
namespace App\Jobs; use ExpertSystems\TransmitSms\Data\LinkHitCallbackData; use ExpertSystems\TransmitSms\Laravel\Contracts\HandlesLinkHitCallback; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; class TrackLinkClickJob implements HandlesLinkHitCallback, ShouldQueue { use Queueable; public function __construct( public LinkHitCallbackData $linkHit, public array $context, ) {} public function handle(): void { LinkClick::create([ 'campaign_id' => $this->context['campaign_id'], 'mobile' => $this->linkHit->mobile, 'url' => $this->linkHit->url, 'clicked_at' => $this->linkHit->clickedAt, ]); } }
Global Event Listeners
In addition to per-message handlers, you can listen to events for all callbacks:
// App\Providers\EventServiceProvider.php use ExpertSystems\TransmitSms\Laravel\Events\DlrReceived; use ExpertSystems\TransmitSms\Laravel\Events\ReplyReceived; use ExpertSystems\TransmitSms\Laravel\Events\LinkHitReceived; protected $listen = [ DlrReceived::class => [ \App\Listeners\LogDlrCallback::class, ], ReplyReceived::class => [ \App\Listeners\LogReplyCallback::class, ], LinkHitReceived::class => [ \App\Listeners\LogLinkHitCallback::class, ], ];
Example listener:
namespace App\Listeners; use ExpertSystems\TransmitSms\Laravel\Events\DlrReceived; use Illuminate\Support\Facades\Log; class LogDlrCallback { public function handle(DlrReceived $event): void { Log::info('DLR callback received', [ 'message_id' => $event->dlr->messageId, 'mobile' => $event->dlr->mobile, 'status' => $event->dlr->status, 'context' => $event->context, ]); } }
Webhook Configuration
The webhook routes are automatically registered. You can customize them in config/transmitsms.php:
'webhooks' => [ // Enable/disable webhook routes 'enabled' => env('TRANSMITSMS_WEBHOOKS_ENABLED', true), // Route prefix (e.g., /webhooks/transmitsms/dlr) 'prefix' => env('TRANSMITSMS_WEBHOOKS_PREFIX', 'webhooks/transmitsms'), // Middleware for webhook routes 'middleware' => ['api'], // Custom signing key (defaults to APP_KEY) 'signing_key' => env('TRANSMITSMS_SIGNING_KEY'), // DLR callback settings 'dlr' => [ 'enabled' => true, 'path' => 'dlr', 'queue' => env('TRANSMITSMS_DLR_QUEUE', 'default'), ], // Reply callback settings 'reply' => [ 'enabled' => true, 'path' => 'reply', 'queue' => env('TRANSMITSMS_REPLY_QUEUE', 'default'), ], // Link hits callback settings 'link_hits' => [ 'enabled' => true, 'path' => 'link-hits', 'queue' => env('TRANSMITSMS_LINK_HITS_QUEUE', 'default'), ], ],
Callback Data Objects
DlrCallbackData properties:
| Property | Type | Description |
|---|---|---|
messageId |
int |
The message ID |
mobile |
string |
Recipient phone number |
status |
string |
Status: delivered, failed, pending |
datetime |
?string |
Delivery timestamp |
senderId |
?string |
Sender ID used |
errorCode |
?string |
Error code if failed |
errorDescription |
?string |
Error description |
Helper methods: isDelivered(), isFailed(), isPending()
ReplyCallbackData properties:
| Property | Type | Description |
|---|---|---|
messageId |
int |
Original message ID |
mobile |
string |
Sender phone number |
message |
string |
Reply message text |
receivedAt |
string |
Timestamp when received |
responseId |
?int |
Reply ID |
longcode |
?string |
Number replied to |
firstName |
?string |
Sender first name |
lastName |
?string |
Sender last name |
LinkHitCallbackData properties:
| Property | Type | Description |
|---|---|---|
messageId |
int |
Message ID |
mobile |
string |
Recipient phone number |
url |
string |
URL that was clicked |
clickedAt |
string |
Click timestamp |
userAgent |
?string |
Browser user agent |
ipAddress |
?string |
IP address |
How It Works
-
Sending: When you use
onDlr(),onReply(), oronLinkHit(), the package builds a signed callback URL containing your handler class and context data. -
Receiving: When TransmitSMS calls the webhook, the package:
- Verifies the HMAC signature
- Parses the callback data into a DTO
- Dispatches a global event (for logging/monitoring)
- Dispatches your handler job with the data and context
-
Security: The callback URL includes an HMAC signature to prevent tampering. Only callbacks with valid signatures are processed.
┌─────────────────────────────────────────────────────────────────────┐
│ Your App │
│ ──────── │
│ TransmitSmsMessage::create('Hello') │
│ ->onDlr(MyJob::class, ['id' => 1]) │
│ │ │
│ ▼ │
│ Package builds signed callback URL │
│ https://app.com/webhooks/transmitsms/dlr?h=...&c=...&s=... │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ TransmitSMS │
│ ─────────── │
│ Sends SMS → Receives DLR → Calls your webhook URL │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Your App (Webhook) │
│ ───────────────── │
│ WebhookController: │
│ 1. Verify signature ✓ │
│ 2. Parse DlrCallbackData │
│ 3. Dispatch DlrReceived event │
│ 4. Dispatch MyJob with data + context │
└─────────────────────────────────────────────────────────────────────┘
License
The MIT License (MIT). Please see License File for more information.