Laravel Integration Guide

Integrate RoyceBulkSMS API into your Laravel application. Send SMS notifications, OTP codes, order confirmations, and more with Laravel's elegant syntax.

Quick Setup

1

Environment Variables

Add your RoyceBulkSMS credentials to .env:

ROYCEBULKSMS_API_URL=https://roycebulksms.com/api/v1/sms-api ROYCEBULKSMS_API_KEY=your_api_key_here ROYCEBULKSMS_SENDER_ID=YourCompany
2

Configuration File

Create config/roycebulksms.php:

<?php return [ 'api_url' => env('ROYCEBULKSMS_API_URL', 'https://roycebulksms.com/api/v1/sms-api'), 'api_key' => env('ROYCEBULKSMS_API_KEY'), 'sender_id' => env('ROYCEBULKSMS_SENDER_ID', 'RoyceLTD'), 'default_country_code' => env('ROYCEBULKSMS_COUNTRY_CODE', '+254'), ];
3

Create Service Class

Create app/Services/RoyceBulkSMSService.php:

<?php namespace App\Services; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Cache; class RoyceBulkSMSService { protected $apiUrl; protected $apiKey; protected $senderId; public function __construct() { $this->apiUrl = config('roycebulksms.api_url'); $this->apiKey = config('roycebulksms.api_key'); $this->senderId = config('roycebulksms.sender_id'); } /** * Send single SMS */ public function sendSMS($phoneNumber, $message, $senderId = null, $callbackUrl = null) { try { $response = Http::withToken($this->apiKey) ->post("{$this->apiUrl}/send/", [ 'phone_number' => $this->formatPhoneNumber($phoneNumber), 'text_message' => $message, 'sender_id' => $senderId ?? $this->senderId, 'callback_url' => $callbackUrl, ]); if ($response->successful()) { $data = $response->json(); Log::info('SMS sent successfully', [ 'message_id' => $data['data']['message_id'] ?? null, 'recipient' => $phoneNumber ]); return $data; } Log::error('SMS sending failed', [ 'status' => $response->status(), 'response' => $response->body() ]); return [ 'success' => false, 'error' => $response->json()['error'] ?? 'Failed to send SMS' ]; } catch (\Exception $e) { Log::error('SMS service exception', [ 'message' => $e->getMessage() ]); return [ 'success' => false, 'error' => $e->getMessage() ]; } } /** * Send bulk SMS */ public function sendBulkSMS(array $phoneNumbers, $message, $senderId = null) { try { $response = Http::withToken($this->apiKey) ->post("{$this->apiUrl}/send-bulk/", [ 'phone_number' => array_map([$this, 'formatPhoneNumber'], $phoneNumbers), 'text_message' => $message, 'sender_id' => $senderId ?? $this->senderId, ]); if ($response->successful()) { return $response->json(); } return [ 'success' => false, 'error' => $response->json()['error'] ?? 'Failed to send bulk SMS' ]; } catch (\Exception $e) { return [ 'success' => false, 'error' => $e->getMessage() ]; } } /** * Check account balance */ public function checkBalance() { return Cache::remember('roycebulksms_balance', 300, function () { try { $response = Http::withToken($this->apiKey) ->get("{$this->apiUrl}/balance/"); if ($response->successful()) { return $response->json(); } return [ 'success' => false, 'error' => 'Failed to fetch balance' ]; } catch (\Exception $e) { return [ 'success' => false, 'error' => $e->getMessage() ]; } }); } /** * Get message status */ public function getMessageStatus($messageId) { try { $response = Http::withToken($this->apiKey) ->get("{$this->apiUrl}/messages/{$messageId}/"); if ($response->successful()) { return $response->json(); } return [ 'success' => false, 'error' => 'Failed to fetch message status' ]; } catch (\Exception $e) { return [ 'success' => false, 'error' => $e->getMessage() ]; } } /** * Format phone number with country code */ protected function formatPhoneNumber($phoneNumber) { // Remove any spaces, dashes, or parentheses $phoneNumber = preg_replace('/[\s\-\(\)]/', '', $phoneNumber); // Add country code if not present if (!str_starts_with($phoneNumber, '+')) { $phoneNumber = config('roycebulksms.default_country_code') . ltrim($phoneNumber, '0'); } return $phoneNumber; } /** * Check if sufficient balance */ public function hasSufficientBalance($requiredUnits) { $balance = $this->checkBalance(); if ($balance['success'] ?? false) { $currentBalance = $balance['data']['current_balance'] ?? 0; return $currentBalance >= $requiredUnits; } return false; } } ?>

Common Use Cases

OTP Verification

// In your controller or service use App\Services\RoyceBulkSMSService; use Illuminate\Support\Facades\Cache; public function sendOTP(Request $request, RoyceBulkSMSService $sms) { $phoneNumber = $request->phone_number; // Generate 6-digit OTP $otp = random_int(100000, 999999); // Store OTP in cache for 10 minutes Cache::put("otp_{$phoneNumber}", $otp, 600); // Send SMS $message = "Your verification code is: {$otp}. Valid for 10 minutes."; $result = $sms->sendSMS($phoneNumber, $message, 'Verify'); if ($result['success'] ?? false) { return response()->json([ 'message' => 'OTP sent successfully', 'expires_in' => 600 ]); } return response()->json([ 'error' => 'Failed to send OTP' ], 500); } public function verifyOTP(Request $request) { $phoneNumber = $request->phone_number; $inputOTP = $request->otp; $storedOTP = Cache::get("otp_{$phoneNumber}"); if ($storedOTP && $storedOTP == $inputOTP) { Cache::forget("otp_{$phoneNumber}"); return response()->json(['message' => 'OTP verified']); } return response()->json(['error' => 'Invalid or expired OTP'], 400); }

Order Confirmation

// In your OrderController use App\Services\RoyceBulkSMSService; public function confirmOrder(Order $order, RoyceBulkSMSService $sms) { // Process order... $order->update(['status' => 'confirmed']); // Send confirmation SMS $message = "Order #{$order->id} confirmed! " . "Total: KES {$order->total}. " . "Track at: " . route('orders.track', $order->id); $sms->sendSMS( $order->customer->phone, $message, 'MyShop' ); return response()->json([ 'message' => 'Order confirmed', 'order' => $order ]); }

Welcome Message

// In your RegisterController use App\Services\RoyceBulkSMSService; public function register(Request $request, RoyceBulkSMSService $sms) { // Validate and create user... $user = User::create($request->validated()); // Send welcome SMS $message = "Welcome to " . config('app.name') . "! " . "Your account has been created successfully. " . "Login at: " . url('/login'); $sms->sendSMS($user->phone, $message); return response()->json([ 'message' => 'Registration successful', 'user' => $user ]); }

Marketing Campaign

// In your CampaignController use App\Services\RoyceBulkSMSService; public function sendCampaign(Request $request, RoyceBulkSMSService $sms) { // Get active customers $customers = Customer::where('opted_in_sms', true) ->pluck('phone') ->toArray(); $message = $request->campaign_message; // Check balance before sending $requiredUnits = count($customers); if (!$sms->hasSufficientBalance($requiredUnits)) { return response()->json([ 'error' => 'Insufficient SMS balance' ], 400); } // Send bulk SMS $result = $sms->sendBulkSMS($customers, $message, 'Promo'); if ($result['success'] ?? false) { return response()->json([ 'message' => 'Campaign sent successfully', 'total_sent' => $result['data']['messages_queued'] ?? 0, 'batch_id' => $result['data']['batch_id'] ?? null ]); } return response()->json([ 'error' => 'Failed to send campaign' ], 500); }

Queue Integration

Create SMS Job

Run: php artisan make:job SendSMSJob

<?php namespace App\Jobs; use App\Services\RoyceBulkSMSService; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; class SendSMSJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public $phoneNumber; public $message; public $senderId; public function __construct($phoneNumber, $message, $senderId = null) { $this->phoneNumber = $phoneNumber; $this->message = $message; $this->senderId = $senderId; } public function handle(RoyceBulkSMSService $sms) { $sms->sendSMS( $this->phoneNumber, $this->message, $this->senderId ); } } ?>

Dispatch Job

use App\Jobs\SendSMSJob; // Dispatch immediately SendSMSJob::dispatch($phoneNumber, $message, $senderId); // Dispatch with delay SendSMSJob::dispatch($phoneNumber, $message, $senderId) ->delay(now()->addMinutes(5)); // Dispatch to specific queue SendSMSJob::dispatch($phoneNumber, $message, $senderId) ->onQueue('sms');

Laravel Notifications

Create Custom Channel

Create app/Channels/RoyceSMSChannel.php:

<?php namespace App\Channels; use App\Services\RoyceBulkSMSService; use Illuminate\Notifications\Notification; class RoyceSMSChannel { protected $smsService; public function __construct(RoyceBulkSMSService $smsService) { $this->smsService = $smsService; } public function send($notifiable, Notification $notification) { $message = $notification->toRoyceSMS($notifiable); return $this->smsService->sendSMS( $message['phone'], $message['message'], $message['sender_id'] ?? null ); } } ?>

Create Notification

Run: php artisan make:notification OrderShipped

<?php namespace App\Notifications; use App\Channels\RoyceSMSChannel; use Illuminate\Notifications\Notification; class OrderShipped extends Notification { protected $order; public function __construct($order) { $this->order = $order; } public function via($notifiable) { return [RoyceSMSChannel::class]; } public function toRoyceSMS($notifiable) { return [ 'phone' => $notifiable->phone, 'message' => "Your order #{$this->order->id} has been shipped! " . "Track at: " . route('orders.track', $this->order->id), 'sender_id' => 'MyShop' ]; } } ?>

Send Notification

use App\Notifications\OrderShipped; // Send to user $user->notify(new OrderShipped($order)); // Or use Notification facade Notification::send($users, new OrderShipped($order));

Event Listeners

Automatic SMS on Events

In app/Providers/EventServiceProvider.php:

use App\Events\OrderCreated; use App\Listeners\SendOrderConfirmationSMS; protected $listen = [ OrderCreated::class => [ SendOrderConfirmationSMS::class, ], ];

Create listener: php artisan make:listener SendOrderConfirmationSMS

<?php namespace App\Listeners; use App\Events\OrderCreated; use App\Services\RoyceBulkSMSService; class SendOrderConfirmationSMS { protected $smsService; public function __construct(RoyceBulkSMSService $smsService) { $this->smsService = $smsService; } public function handle(OrderCreated $event) { $order = $event->order; $message = "Thank you for your order #{$order->id}! " . "Total: KES {$order->total}. " . "We'll notify you when it ships."; $this->smsService->sendSMS( $order->customer->phone, $message, 'MyShop' ); } } ?>

Testing

Unit Test Example

<?php namespace Tests\Feature; use App\Services\RoyceBulkSMSService; use Illuminate\Support\Facades\Http; use Tests\TestCase; class SMSServiceTest extends TestCase { public function test_sends_sms_successfully() { Http::fake([ 'roycebulksms.com/*' => Http::response([ 'success' => true, 'data' => [ 'message_id' => 'test-123', 'status' => 'pending' ] ], 200) ]); $smsService = app(RoyceBulkSMSService::class); $result = $smsService->sendSMS('+254712345678', 'Test message'); $this->assertTrue($result['success']); $this->assertEquals('test-123', $result['data']['message_id']); } public function test_handles_api_errors() { Http::fake([ 'roycebulksms.com/*' => Http::response([ 'error' => 'Insufficient balance' ], 400) ]); $smsService = app(RoyceBulkSMSService::class); $result = $smsService->sendSMS('+254712345678', 'Test message'); $this->assertFalse($result['success']); $this->assertArrayHasKey('error', $result); } } ?>

Best Practices

Always Queue SMS

Never block HTTP requests. Queue all SMS sends to improve response times.

Validate Phone Numbers

Validate and format phone numbers before sending to reduce failures.

Log Everything

Log all SMS attempts for debugging and auditing purposes.

Handle Failures

Implement retry logic and graceful error handling for failed SMS.

Check Balance

Always check balance before bulk campaigns to prevent partial sends.

Use Webhooks

Implement webhook handlers for delivery status instead of polling.

Complete API Reference

For detailed information on all available endpoints, request/response formats, and error codes:

Need Help?

Our support team is ready to help with your Laravel integration.

[email protected]