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/sdk 

Step 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 tinker 
app(\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 .env and 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.

References

Originally published on yasiramin.dev.