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

Fake Sms Notifier Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Start with a decision, not an install. This is a Symfony Notifier bridge, not a Laravel package. There is no service provider, no via() channel, no facade. It only earns its place in a Laravel app if you are already running symfony/notifier (shared code with a Symfony app, a monorepo, or a deliberate multi-channel abstraction). If your only goal is "don't send real SMS in local," Laravel already ships the equivalent: the log notification channel, the mail channel, MAIL_MAILER=log, Mailpit, and Notification::fake(). Reach for this package when you want the same Notifier code path in dev and prod and just swap the transport.

Assuming you do want it:

composer require symfony/notifier symfony/fake-sms-notifier symfony/mailer

Pick the major by PHP version — v8.0 requires PHP >= 8.4, so most Laravel 10/11/12 apps want ^7.4. Pin to a patched release: >=6.4.30, >=7.3.8, or >=7.4.0, because of bug symfony/symfony#62406 (recipient phone numbers were being used as sender e-mail addresses). Also prefer releases containing #60885 (fake SMS transports moved to the contracts event dispatcher).

Look at these three things first, in order:

  1. The README DSN block — there are exactly two schemes, fakesms+email and fakesms+logger.
  2. vendor/symfony/fake-sms-notifier/FakeSmsTransportFactory.php — read the constructor; it tells you which services you must supply (mailer, logger, dispatcher). This is the whole integration surface.
  3. Your .env / config/services.php, where the DSN will live.

First use case — get a code out of your app without Twilio. Add to .env.local:

FAKE_SMS_DSN=fakesms+logger://default

and later, once you've wired a mailer, fakesms+email://[email protected]&[email protected]. The logger variant is the zero-friction option: it needs nothing but a PSR-3 logger, and the message body shows up in storage/logs/laravel.log.

Minimal working binding (Laravel 11/12 AppServiceProvider::register()):

use Symfony\Component\Mailer\Mailer;
use Symfony\Component\Mailer\Transport;
use Symfony\Component\Notifier\Bridge\FakeSms\FakeSmsTransportFactory;
use Symfony\Component\Notifier\Transport\Dsn;

$this->app->singleton('sms.transport', function ($app) {
    $factory = new FakeSmsTransportFactory(
        dispatcher: $app->make(\Psr\EventDispatcher\EventDispatcherInterface::class),
        mailer: new Mailer(Transport::fromDsn('smtp://127.0.0.1:1025')), // Mailpit
        logger: $app->make(\Psr\Log\LoggerInterface::class),
    );

    return $factory->create(Dsn::fromString(config('services.fake_sms.dsn')));
});

Then app('sms.transport')->send(new \Symfony\Component\Notifier\Message\SmsMessage('+15551234567', 'Your code is 1234'));.

Implementation Patterns

Wrap it behind one interface. Define App\Sms\SmsSender with send(string $phone, string $body): void, and bind either a FakeSmsSender (the Symfony transport) or a TwilioSmsSender. Your domain code never learns which is active, and swapping drivers never touches a notification class.

Per-environment DSN, one config key. Use FAKE_SMS_DSN in .env.local and .env.testing, a real provider DSN in production, and resolve both through a single config/services.php entry. This is the cleanest Laravel shape because it matches how you already handle MAIL_MAILER and DB_CONNECTION.

Use Transports if you need both, live. When you want to toggle at runtime rather than by env, register two transports and pick one explicitly:

use Symfony\Component\Notifier\Transport\Transports;

$transports = new Transports([
    'fakesms' => $fakeTransport,
    'twilio'  => $realTransport,
]);
$transports->send($smsMessage, $request->boolean('use_fake') ? 'fakesms' : 'twilio');

This is the pattern that makes "send myself a test SMS" a feature rather than a config deploy.

Bridging to Laravel Notifications. If your notifications already go through via(), write a thin custom channel rather than rewriting everything:

class FakeSmsChannel
{
    public function __construct(private SmsSender $sms) {}

    public function send($notifiable, Notification $notification): void
    {
        $this->sms->send($notifiable->routeNotificationFor('sms'), $notification->toSms($notifiable));
    }
}

Gate it in via() on app()->environment('local', 'testing'). Keep Notification::fake() in tests — it asserts on the notification, not the transport, so it composes fine with this.

Testing assertions. With fakesms+logger, assert on the log channel (Log::spy() plus shouldHaveReceived('info'), or a dedicated fakesms log file you tail). With fakesms+email, assert against Mailpit's API or swap in Symfony's NullTransport and inspect via an event listener. Do not try to use Laravel's Mail::fake() — see the gotcha below.

The honest fallback table. For most Laravel teams, the day-to-day answer is: log channel for "what would I have sent," Mailpit + mail channel for "how would it look," Notification::fake() for assertions. Use this package only when the Notifier abstraction is already load-bearing.

Gotchas and Tips

Laravel's Mailer is not Symfony's Mailer. FakeSmsTransportFactory wants Symfony\Component\Mailer\MailerInterface. Laravel binds Illuminate\Mail\Mailer. There is no automatic bridge, so Mail::fake() intercepts nothing, and MAIL_MAILER doesn't reach this transport. You must build a Symfony Mailer yourself from a Symfony DSN (pointing at Mailpit or smtp://). This single mismatch is where most integration attempts die; budget for it before you promise the feature.

Two event dispatchers. Newer releases use the PSR-14 contracts dispatcher (bug #60885), which Laravel's Illuminate\Events\Dispatcher satisfies — convenient. But if anything pins symfony/event-dispatcher, you get a second dispatcher and Notifier's MessageEvent never reaches Laravel listeners. Never assume Notifier events are observable from Laravel.

Missing to/from fails at boot, not at send. Both are required options for the email scheme and throw an IncompleteDsnException when the factory parses the DSN. That's good — fail fast — but it means a typo in .env takes down the container, not one request. Add a config test that asserts the DSN parses.

Pin past #62406. Below 6.4.30 / 7.3.8 / 7.4.0 / 8.0.0, the transport set the sender address to the recipient's phone number. You get invalid From: headers: mail bounced, spam-filtered, or silently dropped — and it looks like your code is broken. Check composer show symfony/fake-sms-notifier before debugging anything else.

Keep the host segment as default unless you genuinely run named mailer transports; the README's mailchimp example assumes a multi-transport mailer setup you probably don't have.

Don't let fake SMS land in a shared inbox. The phone number and message body are in the email — that's PII in a company-wide address. Mailpit locally, a dedicated log channel in CI, never a distribution list.

Production guard. Add an explicit check in a service provider: if app()->isProduction() and the fake DSN is configured, throw. Silent fake SMS in production is indistinguishable from a broken gateway until a customer complains they never got their OTP.

No delivery semantics. No rate limiting, no retries, no provider callbacks, getMessageId() is synthetic. It's a dev tool — don't write code that depends on its response shape, or you'll couple production behavior to a fixture.

Extension point worth knowing: subclass the transport and override doSend() to also push into an in-memory store. That gives you assertSmsSentTo('+1555…') semantics inside Pest/PHPUnit without parsing logs or mailboxes.

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.
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log