Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Twilio Notifier Laravel Package

symfony/twilio-notifier

Send SMS messages via Twilio from your Symfony app with the Twilio Notifier bridge. Configure it using a simple DSN (twilio://SID:TOKEN@default?from=FROM) and attach extra options like webhook URLs to individual messages through the TwilioOptions class.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Symfony Notifier bridge, so in Laravel you are not pulling in a Laravel notification channel — you are running symfony/notifier standalone and letting this bridge supply the Twilio transport. Treat it as a small, framework-agnostic SMS library.

Install:

composer require symfony/twilio-notifier symfony/notifier

Add the credential to .env (the DSN is the whole config — there is no config/twilio.php in this package):

TWILIO_DSN="twilio://SID:TOKEN@default?from=+15551234567"

Wire it once in a service provider. Transport::fromDsn() auto-discovers the Twilio factory from the installed bridge:

// app/Providers/TwilioNotifierServiceProvider.php
public function register(): void
{
    $this->app->singleton(\Symfony\Component\Notifier\Texter::class, function ($app) {
        $transport = \Symfony\Component\Notifier\Transport::fromDsn(
            config('services.twilio.dsn'),
            null,                 // Symfony EventDispatcher (optional)
            null,                 // Symfony HttpClient (defaults to HttpClient::create())
            $app->make(\Psr\Log\LoggerInterface::class),
        );

        return new \Symfony\Component\Notifier\Texter($transport);
    });
}

First use case — an SMS from a queued job or listener:

$sms = new SmsMessage('+14155550123', 'Your order #1042 has shipped.');
$texter->send($sms);

Look here first, in order: TwilioTransportFactory (to see exactly which DSN params are accepted), TwilioTransport (how the API call is built and which exceptions are thrown), then TwilioOptions (the per-message knobs). Skip the Symfony framework docs — you only need the Notifier component's Texter/SmsMessage surface.

Implementation Patterns

Bind an interface, not Texter. The single highest-value move for day-to-day use is to hide Texter behind your own contract so tests, jobs, and console commands never touch Symfony classes:

interface SmsSender { public function send(string $to, string $body, ?TwilioOptions $options = null): void; }

Implement it with the Texter, and swap in a null/logging implementation in testing.

Per-message options are fluent and transport-specific. Options are attached to the message, not to the transport, so you set them at the call site:

use Symfony\Component\Notifier\Bridge\Twilio\TwilioOptions;

$sms = new SmsMessage($to, $body);
$sms->options(
    (new TwilioOptions())->webhookUrl(route('webhooks.twilio.status'))
);
$texter->send($sms);

Options must be a TwilioOptions instance — a generic MessageOptionsInterface from another bridge will be rejected at send time.

Queue at the Laravel layer, not the Symfony layer. The transport is synchronous and has no retry, so wrap the send in a job with $tries, backoff(), and ShouldBeUnique where relevant. Do not try to reuse Symfony Messenger for this.

final class SendSms implements ShouldQueue
{
    use Queueable;
    public int $tries = 3;
    public array $backoff = [10, 60, 300];

    public function handle(SmsSender $sms): void { $sms->send($this->to, $this->body); }
}

Model notifications as plain classes/Markdown, not SmsMessage. Keep SmsMessage construction inside the adapter. Your app code should pass a domain object or rendered string; the adapter handles E.164 normalisation, truncation, and options. That keeps the Symfony dependency to one file.

Inbound/status webhooks. The bridge ships a TwilioRequestParser used to validate and parse callbacks. In Laravel, expose a route, exclude it from CSRF in bootstrap/app.php ($middleware->validateCsrfTokens(except: ['webhooks/twilio/*'])), and validate the HMAC signature before trusting any field. Pass the secret from config — do not derive it at request time.

Events. If you want observability, pass an event dispatcher into Transport::fromDsn() and listen for the Notifier's sent/failed message events. Laravel's dispatcher is not PSR-14 compliant, so either construct a symfony/event-dispatcher instance for this purpose only, or — simpler — log from your adapter around $texter->send().

Gotchas and Tips

  • Laravel already has a Twilio notification channel. If all you need is "send an SMS from a Notification class", laravel-notification-channels/twilio is far less friction. Reach for this bridge when you specifically want the Notifier abstraction (multiple transports behind one DSN-driven interface, TwilioOptions, or an existing Symfony codebase).
  • The from query param is mandatory. A DSN without ?from= fails at transport construction, not at send time — so a boot-time resolution (see the singleton above) surfaces misconfiguration early, which is what you want.
  • URL-encode credentials. SIDs and tokens are usually alphanumeric, but if your token ever contains @, /, or :, the DSN must be percent-encoded or the parser will split it wrong.
  • default in the DSN is just the transport name. It is not a Twilio concept; it means "unnamed transport". Multi-transport setups use notifier://twilio://... style routing, which you almost certainly don't need in a Laravel app.
  • Webhook secret is now strict. Since the CVE-2026-47212 fixes (v6.4.40 / v7.4.12 / v8.0.12 / v8.1.0), the signature check rejects missing or empty secrets, and v8.1.7 additionally rejects an empty secret in the parser. Environments that silently "worked" with a blank secret will start hard-failing. Validate this in a config/boot check, not on the first inbound callback.
  • Signature validation is URL-sensitive. Twilio signs the exact URL it posted to. Behind a load balancer, misconfigured TrustProxies or a forced-scheme mismatch produces valid requests that fail validation. Log the URL you validated against when debugging.
  • Exceptions are TransportExceptionInterface, not Laravel's Http exceptions. Catch Symfony\Component\Notifier\Exception\TransportExceptionInterface (and the more specific FailedMessageException) and rethrow as a queued-job failure so retries apply. Rate limits and trial-account rejections both land here.
  • The HTTP client is Symfony's, not Laravel's. Http::fake() will not intercept these calls. For tests, either fake your SmsSender interface (recommended) or inject MockHttpClient from symfony/http-client into Transport::fromDsn().
  • PHP/version constraints. The 8.x line requires PHP ≥ 8.4; 7.4 requires 8.2+. Pin a range that matches your runtime and stay on the patched line — most releases on this bridge are "no significant changes", so upgrades are usually safe and the security patches are the reason to stay current.
  • Watch long messages and cost. Twilio segments anything past 160 GSM-7 chars (70 for UCS-2/emoji). Truncate or template in the adapter; a single emoji in an otherwise ASCII message can triple the segment count.
  • Extension points are thin on purpose. There is no Laravel-side config to customise; if you need a different retry policy, proxy, or region-specific endpoint, decorate your SmsSender implementation or supply a custom HttpClientInterface — don't fork the bridge.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
andydefer/laravel-notification
adachsoft/cli-process-tester
jhaskell13/laraflow
sebastienheyd/boilerplate-media-manager
ccast/tagixo-primix
pretendtrue/laravel-builder
sawmainek/laravel-scout-tntsearch-driver
tomshaw/laravel-calendar-table
dibakar/laravel-ownership
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo
stevenay/myanfont
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc