To send an SMS in Laravel 13 with Twilio, install the twilio/sdk package, store your Account SID, Auth Token and sender number in .env, and call $client->messages->create() from a service class. This guide shows the full setup, how to validate phone numbers, how to handle Twilio errors, and what limits apply to trial accounts.
Targets: Laravel 13 (PHP 8.3 to 8.5) and the official Twilio PHP SDK.
Step 1: Create a Twilio account and get credentials
In the Twilio Console you need three values: the Account SID, the Auth Token and a Twilio phone number that can send SMS (or a Messaging Service SID). Trial accounts can only message phone numbers that you have verified in the console, and trial messages carry a prefix that says the message was sent from a trial account.
Step 2: Install the SDK
composer require twilio/sdkStep 3: Add the settings
TWILIO_SID=your-account-sid
TWILIO_AUTH_TOKEN=your-auth-token
TWILIO_FROM=+15005550006'twilio' => [
'sid' => env('TWILIO_SID'),
'token' => env('TWILIO_AUTH_TOKEN'),
'from' => env('TWILIO_FROM'),
],Add the array to config/services.php. The number above is a placeholder in E.164 format; use your own Twilio number. Never commit the Auth Token.
Step 4: Create the SMS service
<?php
namespace App\Services;
use RuntimeException;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Client;
class SmsService
{
public function __construct(private ?Client $client = null)
{
$this->client ??= new Client(
config('services.twilio.sid'),
config('services.twilio.token')
);
}
public function send(string $to, string $body): string
{
try {
$message = $this->client->messages->create($to, [
'from' => config('services.twilio.from'),
'body' => $body,
]);
} catch (TwilioException $e) {
report($e);
throw new RuntimeException('The SMS could not be sent.');
}
return $message->sid;
}
}The method returns the message SID, which you can store to look the message up later. Taking the client through the constructor lets tests pass a fake client.
Step 5: Validate the phone number
Twilio expects numbers in E.164 format: a plus sign, the country code and the number, up to 15 digits in total. Validate that before calling the API:
<?php
namespace App\Http\Controllers;
use App\Services\SmsService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use RuntimeException;
class SmsController extends Controller
{
public function send(Request $request, SmsService $sms): JsonResponse
{
$data = $request->validate([
'to' => ['required', 'regex:/^\+[1-9][0-9]{7,14}$/'],
'message' => ['required', 'string', 'max:500'],
]);
try {
$sid = $sms->send($data['to'], $data['message']);
} catch (RuntimeException $e) {
return response()->json(['message' => $e->getMessage()], 502);
}
return response()->json(['sid' => $sid]);
}
}Route::post('/sms/send', [SmsController::class, 'send'])->middleware(['auth', 'throttle:5,1']);A throttle matters here because every SMS costs money and attackers abuse open SMS endpoints.
Step 6: Send a test message
php artisan tinkerapp(\App\Services\SmsService::class)->send('+923001234567', 'Hello from Laravel 13');Use a verified number if you are on a trial. The call returns a SID that starts with SM.
How do I test it without sending SMS?
Bind a fake service in the container so the controller never reaches Twilio:
$this->mock(\App\Services\SmsService::class, function ($mock) {
$mock->shouldReceive('send')->once()->with('+923001234567', 'Hi')->andReturn('SM123');
});
$this->actingAs(\App\Models\User::factory()->create())
->postJson('/sms/send', ['to' => '+923001234567', 'message' => 'Hi'])
->assertOk()
->assertJson(['sid' => 'SM123']);Should I send SMS from a queue?
For anything that is not an instant one-off, yes. Dispatch a queued job so a slow Twilio response does not hold up the web request and failed jobs can retry. The job calls the same SmsService.
Common mistakes
- Number without the plus sign or country code: Twilio rejects it. Validate E.164.
- Unverified recipient on a trial account: the request fails until the number is verified or the account is upgraded.
- Sender number not SMS-capable: pick a number that supports SMS in the destination country.
- Leaking the Auth Token: keep it in
.envand rotate it if it was ever exposed. - No rate limit: an open endpoint can burn your balance quickly.
FAQ
Can I use a Messaging Service instead of a number?
Yes. Pass messagingServiceSid in the options instead of from.
How do I know if the SMS was delivered?
The create call only confirms that Twilio accepted the message. For delivery status, configure a status callback URL or look up the message by SID.
Is there a Laravel notification channel for Twilio?
There is a community notification channel, but the SDK approach above has fewer dependencies and works the same way for simple cases.



