symfony/fake-sms-notifier
Fake SMS integration for Symfony Notifier that routes outgoing SMS as email or log messages during development. Configure via DSN with fakesms+email or fakesms+logger to test notifications without sending real texts.
Verdict up front: this is a development-time test seam, not a capability. It earns adoption only if symfony/notifier is already (or is deliberately becoming) the SMS dispatch path. For a pure Laravel codebase it means introducing a second notification abstraction purely to fake one — a net architectural cost with no production payoff.
Architecture fit
TransportFactoryInterface for two DSN schemes — fakesms+email:// (renders the SMS body as an email through symfony/mailer) and fakesms+logger:// (writes to a PSR-3 logger). It is not an SMS SDK and holds no vendor logic.Notification / Channel stack is a parallel abstraction that does not consume Notifier transports. The two systems share a conceptual model (channel/transport + message) and nothing else.symfony/mailer — plausible, DSN swap only. Pure Laravel — requires a bridge channel we own and maintain forever.Integration feasibility
.env conventions:
FAKE_SMS_DSN=fakesms+email://[email protected]&[email protected]
The DSN host (default, mailchimp, …) selects a named mailer transport, which is the useful knob for per-team QA routing.NotifierInterface::send() call and the same message objects, with only the DSN differing. That is stronger than Notification::fake(), which bypasses dispatch entirely and can let a broken notification class ship.Technical risk
symfony/symfony; its version tracks Symfony's, not its own. v8.x requires PHP ≥ 8.4. On a Laravel 11/12 app (PHP 8.2) you must consciously pin to the 7.2–7.4 line or you will drag the whole symfony/* set up a major.MessageEvent / SentMessageEvent / FailedMessageEvent on the contracts dispatcher; nothing forwards those into Laravel's event bus, so Laravel listeners will not observe fake SMS sends.Key questions
symfony/notifier in the production dependency graph today? If no, this is a gate — do not proceed past a spike.Notification::fake() plus Mailpit already sufficient for that failure? Usually it is.symfony/notifier version pin?Stack fit
composer require --dev symfony/fake-sms-notifier and a DSN. Done.symfony/mailer (e.g. via a shared package or a Symfony-based internal SDK): low friction; the email transport reuses the configured mailer, and Mailpit already captures the output.final class FakeSmsChannel
{
public function __construct(private NotifierInterface $notifier) {}
public function send(object $notifiable, Notification $notification): void
{
$message = $notification->toFakeSms($notifiable);
$this->notifier->send(new SmsMessage($notifiable->routeNotificationFor('sms'), $message));
}
}
Bind a Notifier built from FakeSmsTransportFactory in a service provider, register the channel, and gate it on app()->environment().Migration path
FAKE_SMS_DSN to .env and .env.testing only. Never to .env.example as a default that could leak into a deploy.Compatibility
symfony/* 6.4; Laravel 11/12 → 7.2–7.4. Treat ^8.0 as blocked until the app itself is on PHP 8.4.--dev; a fakesms DSN reaching production is a silent, total SMS outage.MailerInterface bindings if symfony/mailer and Laravel's mailer coexist.fakesms+logger so fake SMS does not pollute stack/daily:
'channels' => ['fakesms' => ['driver' => 'single', 'path' => storage_path('logs/fake_sms.log')]],
to/from; + in the scheme is the transport-variant separator and must not be escaped.Sequencing
composer require --dev symfony/fake-sms-notifier:<pinned-line> with an explicit symfony/notifier constraint..env / .env.testing; dedicated log channel.fakesms DSN.Maintenance
symfony/symfony, not in this repo — there is no separate backlog to watch.Support
Scaling
MAIL_MAILER=array, Mailpit as a sink, or a per-job DSN.Failure modes
| Scenario | Impact | Detection | Mitigation |
|---|---|---|---|
fakesms DSN present in production |
Total SMS outage, silent | Env-diff check in deploy pipeline | Hard fail in CI on fakesms:// in prod config |
| DSN typo / unknown mailer transport name | Send throws or drops at runtime | Smoke test on boot | Assert transport resolves in a health check |
| Adapter owned by no one | Breaks on Laravel/Symfony upgrade | Post-upgrade test failures | Named owner, unit tests on the channel |
| Phone-number PII in shared logs/inbox | Data-handling finding | Log review | Dedicated log channel, retention policy, pseudonymised recipients |
| Symfony 8 drift raises PHP floor | Composer resolution failure or runtime break | CI install failure | Pin symfony/* group, exclude 8.x until PHP 8.4 |
| Green tests, real-provider failure | False confidence shipped | Production incident | One sandbox parity job in CI |
| Email transport recursion (SMS-as-email triggers another SMS) | Mail loop | Mail volume anomaly | Disable notifier on the receiving mailer transport |
Ramp-up
--dev with a pinned constraint, add the two CI guardrails (no fakesms in prod, no real SMS DSN in non-prod), and record an explicit "we chose Notification::fake() + Mailpit instead" decision if the gate fails — that negative decision is the most likely correct outcome for a Laravel-only stack.How can I help you explore Laravel packages today?