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.
Framing correction (important for scoping): despite the "Laravel/PHP package" label, symfony/twilio-notifier is not a Laravel package. It is a Symfony Notifier transport bridge — a small adapter that plugs into symfony/notifier's Texter/Transport abstractions. It ships no service provider, no facade, no config/*.php, and no Laravel notification channel. Any Laravel leverage decision is therefore a decision about adopting symfony/notifier inside Laravel, with this package as one transport among many.
Architecture fit
TransportFactory (DSN parsing), a Transport (HTTP call to Twilio), a TwilioOptions value object (e.g. webhookUrl), and a webhook signature parser. For a TPM this is good: small blast radius, few contracts to satisfy, easy to wrap behind our own interface.symfony/notifier are Composer-compatible — Laravel already depends on many Symfony components (http-foundation, console, process, mime, error-handler, routing). The conflict risk is low; the friction is idiomatic, not dependency-level: DSN-based transport config, Texter::send(), SmsMessage, and Symfony's event/messenger wiring instead of Laravel's Notification + queue.SmsSender interface of our own, with two interchangeable implementations: (a) a thin adapter over symfony/notifier + this bridge, (b) a native Laravel notification channel on the Twilio SDK. That keeps the vendor decision reversible.symfony/symfony monorepo, not organic adoption. Real usage, issues, and review all live in the monorepo. Do not treat the score as an abandonment signal; conversely, do not treat it as evidence of a healthy standalone community either.Integration feasibility
Texter in Laravel's container, build the DSN from env, send one SMS in a queued job) is a 1–3 day job for one engineer. The DSN contract is trivial to source from config: twilio://SID:TOKEN@default?from=FROM.symfony/notifier as a library inside Laravel and use this bridge for the Twilio transport. Fastest to SMS-in-production; costs us Symfony Messenger/event idioms we'd have to shim onto Laravel queues.TWILIO_* secret becomes a hard runtime failure, not a silent pass. That must be a boot-time config validation, documented for every environment including local and CI.from only — no messagingServiceSid. For US A2P/10DLC throughput and sender-pool failover we would need to extend the transport or bypass it. WhatsApp destination validation landed in 7.4/8.0; verify it matches our number formats before relying on it.Technical risk
| Risk | Severity | Mitigation |
|---|---|---|
| Symfony release-train coupling (majors ~2-yearly, minors ~6-monthly) | Medium | Pin to a minor line; wrap behind internal SmsSender interface; budget one upgrade per year |
| PHP 8.4 floor on v8 | Medium | Standardise on v7.4 line until the platform is on 8.4 |
| Idiom mismatch (Messenger/EventDispatcher vs Laravel queue/events) | Medium | Adapter layer; do not let Symfony\Component\Notifier types leak into domain code |
| Webhook secret misconfiguration → hard 403s after upgrade | Medium | Fail-fast config validation; secret in vault; alert on 4xx spike at webhook endpoint |
No messagingServiceSid; single from sender |
Medium | Custom transport extension if we need sender pools or 10DLC scale |
| Sponsor vacuum / maintainer concentration | Low-Medium | MIT + monorepo backing; accept that we may need to carry local patches |
Test scaffolding churn (TransportFactoryTestCase deprecated in 7.2) |
Low | Test our adapter contract, not Symfony's transport internals |
Key questions
Stack fit
| Concern | Fit with Laravel | Action |
|---|---|---|
| Composer dependencies | Good — Laravel already pulls Symfony components | Verify no symfony/notifier transitive conflict in a spike branch |
| Config/secrets | Good — DSN maps cleanly to config/services.php + env |
Store TWILIO_DSN in secret manager, never .env in repo |
| DI | Good — Texter is a plain service |
Bind SmsSender → SymfonyTexterAdapter; never inject Texter into domain code |
| Queues | Poor — Notifier assumes Symfony Messenger | Send from a Laravel job that calls our adapter; let Laravel own retries/backoff |
| Events | Poor — Symfony NotificationSentEvent ≠ Laravel events |
Emit our own SmsSent/SmsFailed events from the adapter |
| Testing | Fair — Symfony transport test case is deprecated | Contract tests against a fake SmsSender; Http::fake()-style stubs for Twilio |
| Webhooks | Conditional | Only if we consume callbacks; requires non-empty HMAC secret and route-level signature test |
Migration path
symfony/notifier + this bridge; bind a Texter from DSN; send one queued SMS in staging. Success criteria: send succeeds, queue retry behaves, secrets load from vault, no dependency conflicts.SmsSender + SmsMessage DTO in our codebase. Implement SymfonyTwilioSender. All call sites depend on the interface only.Compatibility
TransportFactoryTestCase (deprecated 7.2) — test our adapter, not their internals.symfony/symfony) for issues and fixes; this split repo is a mirror.Sequencing rationale Seam first, vendor second. If the spike fails or the interface proves leaky, we lose days, not a quarter — and step 2's interface survives either choice.
Maintenance
Support
Scaling
from-only DSN means no sender pool — plan for a transport extension before volume grows.Failure modes
| Failure | Symptom | Detection | Response |
|---|---|---|---|
| Bad SID/token | 401 from Twilio | Adapter error metric | Fail job, alert; secret rotation runbook |
| Rate limited (20429) | 429s, queue backlog | Error-code dashboard | Backoff via Laravel queue; request limit increase |
| Opt-out / unreachable recipient (21610 etc.) | Per-message rejects | Error classification in adapter | Suppress recipient, don't retry |
| Blank/invalid webhook secret | Hard rejection after recent releases | Webhook 4xx spike | Boot-time config validation; never deploy blank |
| DSN secret in logs | Credential leak | Log scanning | Redact DSN in exception paths; rotate |
| Symfony major drift | Upgrade breakage | CI on bump PR | Adapter isolates change; revert pin |
| Provider outage | Delivery delays | Delivery-rate SLO | Queue buffers; secondary provider only if abstraction exists |
Ramp-up
How can I help you explore Laravel packages today?