vresh/twilio-bundle
A Symfony bundle that integrates the Twilio SDK, providing configurable services for sending SMS, making voice calls, and accessing Twilio APIs directly from your Laravel or Symfony application.
Read this first: it is not a Laravel package. vresh/twilio-bundle is a Symfony 2/3 bundle — it ships a Bundle class, DependencyInjection/Extension, and a YAML config tree. There is no ServiceProvider, no extra.laravel block, and no auto-discovery. Release 1.0.2 pins twilio/sdk to 4.* explicitly, which turns the bundle's "ancient SDK" problem from a side effect into a hard constraint: it now deliberately refuses SDK 5/6/7. In a Laravel app that pin will fail composer require against anything modern (SDK 4.x pulls PHP 5.5-era constraints and old Guzzle, conflicting with Laravel's Guzzle 6/7 and the HTTP client). Don't install it.
What you should actually do day-to-day:
composer require twilio/sdk (latest 7.x) in the Laravel app.Twilio\Rest\Client, register it in the container." That is ~40 lines you will reimplement in a service provider.If you are, unusually, working inside a Symfony 2.8/3.x app that already uses it:
composer require vresh/twilio-bundle # resolves twilio/sdk 4.* only
// app/AppKernel.php
$bundles[] = new Vresh\TwilioBundle\VreshTwilioBundle();
# app/config/config.yml — verify key names in DependencyInjection/Configuration.php,
# they have varied between releases (sid/token vs account_sid/auth_token)
vresh_twilio:
sid: "%twilio_sid%"
token: "%twilio_token%"
Then confirm the service id rather than guessing — it is typically vresh_twilio or vresh_twilio.wrapper:
app/console debug:container | grep -i twilio
First real use case (Symfony side): inject the wrapper and send one SMS; from there it is just the (4.x) Twilio SDK underneath — note that SDK 4.x resource accessors differ from the docs on twilio.com today, which assume 5.x+.
Laravel equivalent — your starting point:
// config/services.php
'twilio' => [
'sid' => env('TWILIO_SID'),
'token' => env('TWILIO_TOKEN'),
'from' => env('TWILIO_FROM'),
],
// app/Providers/TwilioServiceProvider.php
public function register(): void
{
$this->app->singleton(TwilioClient::class, fn () => new TwilioClient(
config('services.twilio.sid'),
config('services.twilio.token'),
));
}
Singleton client, injected. The bundle's only structural idea is "one client per container, credentials from config." Mirror it with $this->app->singleton(...) and constructor-inject TwilioClient into controllers/jobs. Never new TwilioClient(...) inline — you lose config overrides and testability.
Facade only if you must. This bundle-style usage ($this->get('vresh_twilio')->account->messages->create(...)) works, but in Laravel prefer a thin domain service over the SDK so call sites don't depend on Twilio's fluent API — and so the 4.x-vs-7.x resource-accessor divergence never leaks into your app:
class SmsSender
{
public function __construct(private TwilioClient $client) {}
public function send(string $to, string $body): string
{
$msg = $this->client->messages->create($to, [
'from' => config('services.twilio.from'),
'body' => $body,
'statusCallback' => route('twilio.status'),
]);
return $msg->sid;
}
}
Queue the send. SMS is an outbound HTTP call to a third party; it belongs in a job, not a request cycle. The bundle gives you nothing here (no events, no async) — in Laravel use ShouldQueue, $tries = 3, backoff(), and treat Twilio\Exceptions\RestException codes 20429 (rate limit) and 21610 (recipient opted out) as non-retryable.
Notifications channel. If you're on Laravel Notifications, write a custom channel wrapping SmsSender so via() returns ['sms'] alongside mail/database. This is the pattern you'd use 90% of the time; the bundle has no analogue.
Webhooks are your job. Inbound SMS and delivery-status callbacks land on a route the bundle does not provide. Add a route outside web middleware CSRF, and validate the X-Twilio-Signature header:
$validator = new \Twilio\Security\RequestValidator(config('services.twilio.token'));
$validator->validate($request->header('X-Twilio-Signature'), $request->fullUrl(), $request->post());
Do this before touching the payload, and make the handler idempotent on MessageSid (Twilio retries). If you are stuck on the pinned SDK 4.x, Twilio\Security\RequestValidator may not exist — fall back to Services_Twilio::validateRequest() or hand-roll the HMAC-SHA1 of URL + sorted params, which is ~10 lines.
Multi-tenant / subaccounts. The bundle is hardwired to a single SID — do not extend it. Instantiate new TwilioClient($subSid, $subToken) per tenant inside a resolver and never store those in the singleton.
Testing. Bind a fake behind your own SmsSender interface, or point the SDK at Twilio's test credentials. Http::fake() will not intercept the Twilio SDK — it uses its own Guzzle/PSR-18 client. Either inject a mock TwilioClient (it's a concrete class; wrap it in an interface first) or use the SDK's Twilio\Http\Client option to swap the transport.
composer.json constraint: twilio/sdk: 4.*. It is a deliberate BC-break avoidance — the author pinned to 4.x rather than port to 5.x — which means the bundle is now frozen out of every SDK feature since 2015: Verify, Conversations, Messaging Services v2, A2P 10DLC helpers, and the v6 constructor changes. Treat it as documentation, not code.4.*, any app that also needs twilio/sdk ^5|^6|^7 gets an unresolvable conflict, and SDK 4.x's old Guzzle conflicts with Laravel's HTTP client stack. Do not try to --ignore-platform-reqs past this; you'll end up with two incompatible HTTP abstractions.$container->get('vresh_twilio') from a controller will throw. Inject by type/argument instead, and check debug:container --show-private.DependencyInjection/Configuration.php in the tagged release you pulled — sid/token vs account_sid/auth_token and whether from is even part of the tree has changed. A wrong key silently yields a client with null credentials and a confusing 401 at call time, not at boot.Twilio\Exceptions\RestException (has getStatusCode() and getDetails()['code']) and TwilioException (transport/network). Map Twilio error codes to your own domain exceptions in SmsSender so the rest of the app never imports Twilio\.parameters.yml, which historically got committed to VCS. In Laravel keep SID/token in env() only, read through config(), and remember config:cache means env() outside config files returns null — a classic "works locally, broken in prod" here.from matters. Use an E.164 number or a Messaging Service SID (MessagingServiceSid parameter — not available in the 4.x SDK the bundle pins) — trial accounts can only send to verified numbers, which is the #1 "it silently does nothing" report.body over 160 GSM-7 chars is split and billed per segment; non-GSM characters (emoji, curly quotes) drop to 70 chars/segment. Compute segments before sending if cost matters.$msg->sid + $msg->status on send and reconcile via the status callback. Without statusCallback you will never learn about undelivered/failed.How can I help you explore Laravel packages today?