simperfit/twilio-bundle
A Symfony bundle that integrates the Twilio PHP SDK, wiring Twilio client configuration and services into the container so applications can send SMS messages and make voice calls through Twilio with minimal setup.
Framework mismatch first. simperfit/twilio-bundle is a Symfony bundle, not a Laravel package. It does not ship a ServiceProvider, a facade, a publishable config file, or any php artisan integration. In a Laravel codebase your realistic options are:
twilio/sdk directly (recommended — see below).If you are working in an actual Symfony app (the only place this bundle fits):
composer require simperfit/twilio-bundle twilio/sdk
Register the bundle in config/bundles.php:
Vresh\TwilioBundle\VreshTwilioBundle::class => ['all' => true],
Configure credentials in config/packages/vresh_twilio.yaml:
vresh_twilio:
sid: '%env(TWILIO_SID)%'
token: '%env(TWILIO_TOKEN)%'
Add TWILIO_SID / TWILIO_TOKEN to .env, then inspect the container and see what you actually got registered:
bin/console debug:container | grep -i twilio
bin/console debug:container vresh_twilio
First use case — send an SMS. The bundle's job is to hand you a configured Twilio\Rest\Client wrapper. Inject the wrapper service and call through it (the wrapper typically proxies to the underlying client; run debug:container to confirm the exact accessor in the version you pulled):
use Vresh\TwilioBundle\Service\TwilioWrapper;
class Notifier
{
public function __construct(private TwilioWrapper $twilio) {}
public function sendSms(string $to, string $body): string
{
$message = $this->twilio->getTwilio()->messages->create($to, [
'from' => '%env(TWILIO_FROM)%',
'body' => $body,
]);
return $message->sid;
}
}
The Laravel equivalent you should actually write (no bundle involved):
composer require twilio/sdk
// app/Services/TwilioClient.php
namespace App\Services;
use Twilio\Rest\Client;
class TwilioClient
{
private Client $client;
public function __construct()
{
$this->client = new Client(
config('services.twilio.sid'),
config('services.twilio.token')
);
}
public function sendSms(string $to, string $body): string
{
return $this->client->messages->create($to, [
'from' => config('services.twilio.from'),
'body' => $body,
])->sid;
}
}
Bind it in AppServiceProvider::register() as a singleton and type-hint TwilioClient wherever needed. That is the whole feature set the bundle gives you, minus the Symfony ceremony.
Constructor injection via a thin wrapper. Do not scatter new Client(...) calls through your code. In Symfony, inject TwilioWrapper; in Laravel, inject your TwilioClient singleton. Either way the rest of your code should never see sid/token.
Outbound messaging service. Wrap sending in a domain service with a stable signature (sendSms, sendWhatsApp, placeCall) so you can swap providers later. Return the Twilio sid and persist it — you will need it for status callbacks and support tickets.
class SmsService
{
public function __construct(private TwilioClient $twilio) {}
public function send(string $to, string $body, array $meta = []): string
{
$sid = $this->twilio->sendSms($to, $body);
MessageLog::create([
'sid' => $sid,
'to' => $to,
'payload' => $meta,
'status' => 'queued',
]);
return $sid;
}
}
Inbound webhooks and status callbacks. The bundle does not generate webhook routes for you — you still write the controller. For inbound SMS, Twilio POSTs form-encoded data; respond with TwiML:
Route::post('/twilio/sms', function (Request $request) {
// $request->input('From'), ->input('Body'), ->input('MessageSid')
return response(
'<?xml version="1.0" encoding="UTF-8"?>'
. '<Response><Message>Thanks!</Message></Response>',
200
)->header('Content-Type', 'text/xml');
});
If you use the Twilio SDK's TwiML builder, that is from twilio/sdk, not from this bundle — import it directly.
Status callback processing pattern. Expose a second endpoint for MessageStatusCallback, verify the X-Twilio-Signature header (Twilio SDK ships RequestValidator), then update your MessageLog row keyed by MessageSid. Make this idempotent — Twilio retries.
Testing pattern. Never hit the Twilio API in tests. Mock your own TwilioClient/wrapper in the container and assert on the arguments passed to sendSms. Because the bundle's service name is stable (vresh_twilio in Symfony, debug:container to confirm), you can swap it in services_test.yaml:
# config/packages/test/vresh_twilio.yaml
services:
Vresh\TwilioBundle\Service\TwilioWrapper:
class: App\Tests\Double\FakeTwilioWrapper
In Laravel, $this->mock(TwilioClient::class) in a feature test does the same job with less configuration.
Laravel integration tips if you insist on bridging from a Symfony service. Put Twilio behind an HTTP boundary: Laravel queues a job → job POSTs to your Symfony service's /sms endpoint → Symfony service uses the bundle. Do not try to load the Symfony bundle inside Laravel; the DependencyInjection, EventDispatcher, and Kernel contracts it depends on are not present.
You are not the target audience. This package is a Symfony bundle. In a Laravel app there is nothing to vendor:publish, no facade, no config/twilio.php. Any tutorial showing php artisan steps for this package is wrong.
Maintenance risk is severe. One star, zero dependents, and the last release was 2022-11-22. Treat it as effectively unmaintained:
twilio/sdk major version circa 2022; if composer require twilio/sdk resolves a newer major, the bundle's constructor/signature assumptions may break.vendor/simperfit/twilio-bundle/src/ is your documentation.The bundle is a thin wrapper, not a client. It does not add retries, rate limiting, queueing, phone-number validation, or template management. Every production concern is yours:
555-1234. Normalize to +15551234567 before sending, and validate up front rather than eating a Twilio\Exceptions\RestException at 2 a.m.Retry-After header. Wrap sends in a queued job with ->backoff([10, 30, 60]); the bundle gives you no help here.Credential and config quirks.
vresh_twilio (not twilio), so the obvious key name will silently fail validation. Run bin/console config:dump-reference vresh_twilio to see the real schema..env, bin/console cache:clear is mandatory. A stale container is the most common "it says the SID is null" cause.%env(...)% and let Symfony's env var processors handle types; do not hard-code the auth token in the YAML..env in a running process requires a restart, not just a config reload.Debugging checklist.
bin/console debug:container vresh_twilio — confirm the service exists and inspect its class.bin/console debug:config vresh_twilio — confirm sid and token resolved to non-null values (they may be redacted; check length).Twilio\Exceptions\TwilioException / RestException and log $e->getCode(), $e->getMessage(), and the Twilio status — the numeric code (e.g. 21211 invalid To) is far more useful than the message.text/xml) and that the response body is valid TwiML — a 200 with HTML body will silently do nothing.Signature validation is not optional for webhooks. Use Twilio\Security\RequestValidator from twilio/sdk with your auth token and the full request URL. Behind a proxy the URL Twilio signed differs from Laravel's $request->url() — use url()->current() carefully or reconstruct from X-Forwarded-*, or every callback rejects.
Extension points are limited. The bundle exposes essentially one service. Customization means either (a) decorating that service in services.yaml, or (b) skipping the bundle and instantiating Twilio\Rest\Client yourself, which in a Symfony app is a five-line service definition. For a Laravel developer, option (b) is the whole recommendation: you get the same client, a maintained SDK, and no Symfony bridge to maintain.
How can I help you explore Laravel packages today?