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

Notification Api Sms Notifier Bundle Laravel Package

fagforbundet/notification-api-sms-notifier-bundle

Symfony bundle for sending SMS notifications through the Notification API. Integrates with Symfony Notifier to dispatch text messages from your application, with configurable API credentials and delivery settings.

View on GitHub
Deep Wiki
Context7

Getting Started

Reality check first. This is a Symfony bundle, not a Laravel package. There is no composer require-and-go path for a Laravel app. Before you invest an hour, decide which of these you're doing:

  1. Full adoption — pull symfony/notifier + this bundle into Laravel (rarely worth it; see Gotchas).
  2. Surgical reuse — copy the transport class into your app behind your own SmsSender port (recommended).
  3. Reference implementation — read it, don't install it; write your own in ~1 day.

Where to look first (in order):

  • composer.json — PHP version, symfony/notifier, symfony/http-client constraints. Run this against your app: composer require fagforbundet/notification-api-sms-notifier-bundle --dry-run and watch resolution.
  • src/ — you need three things: the TransportInterface implementation (does the actual HTTP POST and returns SentMessage/FailedMessage), the transport factory (DSN parsing), and the DependencyInjection/*Extension (registers services).
  • README — look for the DSN format (notification-api+sms://...?) and which config keys the extension reads. That DSN shape is the real "API" you'd be depending on.
  • Any Services/*.yaml — how the transport gets wired.

First use case — pick the smallest possible one. Don't start with OTP or member-broadcast. Start with an internal, low-stakes, single-recipient send (a dev "hello world" to your own phone) to validate:

  • credentials work,
  • payload shape matches the vendor's expectation,
  • you get back a message id you can log.

In a Laravel app, the fastest path is a throwaway Artisan command:

// app/Console/Commands/SmsSmokeTest.php
public function handle()
{
    $sender = app(\App\Support\Sms\SmsSender::class);
    $result = $sender->send('+4712345678', 'smoke test');
    $this->line(json_encode($result)); // id, status, raw response
}

Wire SmsSender to a class that directly instantiates (or forks) the transport — not the bundle's DI container. You want to see one real request/response before touching notification channels.

Implementation Patterns

Pattern 1 — Wrap, don't wire (recommended default). Define an internal contract, then hide this package behind it. This is the single most valuable pattern here:

interface SmsSender {
    public function send(string $to, string $body, array $opts = []): SmsResult;
}

final class SmsResult {
    public function __construct(
        public readonly bool $ok,
        public readonly ?string $messageId,
        public readonly ?string $status,
        public readonly array $raw,
    ) {}
}

Implement SmsSender once with the package's transport (forked or required), once with a NullSmsSender/fake for tests. Every app call site — notification channel, service, job — talks to SmsSender only. When the vendor changes or the package rots, you change one class.

Pattern 2 — Laravel notification channel. The package gives you a Symfony transport; Laravel wants a channel. Bridge them:

final class SmsChannel {
    public function __construct(private SmsSender $sender) {}

    public function send(object $notifiable, Notification $notification): void {
        if (!method_exists($notification, 'toSms')) return;
        $msg = $notification->toSms($notifiable);
        $result = $this->sender->send(
            $notifiable->routeNotificationFor('sms'),
            $msg->body,
            $msg->options ?? [],
        );
        if (!$result->ok) {
            // decide: throw, log, or dead-letter — see Gotchas
            throw new SmsDeliveryException($result->raw);
        }
    }
}

Then register it on Notification::extend('sms', ...) in a service provider and call $user->notify(new OrderReady($order)) as normal. This is the day-to-day surface most developers actually touch — they write toSms() and never see the transport.

Pattern 3 — Fork the transport, keep the shape. Copy the transport class into app/Support/Sms/Transport/ behind the SmsSender port. Keep its internal structure (one concern: map message → provider payload → HTTP → map response → SmsResult). Record the upstream commit SHA in a README note so you can diff later. This eliminates supply-chain risk while preserving the only thing that's actually hard to get right — the vendor payload mapping.

Workflows that matter day-to-day:

  • Queue it. Never call the transport inline in a request cycle. dispatch(new SendSmsJob($to, $body)) on a dedicated queue with tries = 3 and exponential backoff. SMS gateways rate-limit; a queued worker with a RateLimited middleware is where you enforce that.
  • Config over bundle conventions. In Laravel, credentials go in config/services.php and .env. Ignore the bundle's YAML extension entirely if you fork — you don't want two config systems to reconcile.
  • Log the id, never the body. Store messageId/status on the notification record. Redact phone numbers and message content from logs (PII).

Integration tips:

  • Pin the vendor transport contract with a contract test using Http::fake(): assert the outgoing request shape (URL, auth header, JSON body) and that a 200 with an id → ok=true, a 4xx → ok=false with the response captured in raw.
  • Keep raw on SmsResult but treat it as opaque diagnostics — dashboards should read ok/status, not reach into vendor JSON.
  • If you must do full adoption (Path 1), bootstrap a tiny Symfony container only in one test to verify the bundle wires — don't run it in production.

Gotchas and Tips

The big one: framework gravity. Adding symfony/notifier + symfony/http-client to a Laravel app means you're now pinning Symfony components that Laravel itself pins. composer update can produce resolution conflicts or force you onto Laravel versions you didn't intend. Always --dry-run first. In practice, forking the ~1–2 classes into app code is less work than keeping the dependency graph healthy long-term.

"Sent" is not "delivered." A thin transport returns SentMessage on a 2xx — that means accepted by the vendor API, not delivered to a handset. If your product needs delivery receipts, opt-outs, or keyword handling, this package likely stops at "accepted." Verify from the vendor's docs, not the package. Don't build a UI that shows "Delivered" off a SentMessage.

Error mapping is probably shallow. Inspect the transport's catch blocks. Expect it to either let exceptions bubble or return FailedMessage with limited detail. Neither maps vendor error codes (invalid number vs. rate-limited vs. insufficient credit) to actionable outcomes. Add your own mapping in SmsSender so SmsResult carries a stable error enum; callers should branch on that, not on raw HTTP status.

DSN parsing is your coupling point. The transport is built from a DSN, and the DSN string encodes host/credentials/from. If you fork, you now own that parsing — write it as a small value object with validation and a clear error on malformed config. Config quirks here bite you at deploy time, not test time. Add a php artisan sms:diag command that prints the parsed config (secrets masked) and does a single dry send.

No releases/CI means no safety net. Check git tag and the Actions tab before you depend on anything. With 0 stars and a single-org origin, assume you own it forever. Budget a named owner and a quarterly "does upstream still exist / did the vendor API change" review. MIT means forking is free — so fork proactively into app/Support/Sms/ and stop treating upstream as a dependency.

Testing pitfalls. Http::fake() in Laravel won't intercept a Symfony HttpClient transport — if you keep the Symfony HTTP client, fake at a different layer (MockHttpClient at the Symfony layer) or, better, test your SmsSender adapter and fake it for everything above. Don't try to fake the vendor API through the Symfony stack from Laravel tests; it's brittle and slow.

Extension points that actually matter. The transport class is the seam. Everything else — the bundle, the extension, the factory — is Symfony wiring you'll likely discard. If you want to add idempotency keys, retries with jitter, or per-country sender IDs, do it around SmsSender (a decorator or the job layer), not by patching the transport. Keep the transport a thin, boring HTTP mapper.

Debugging checklist when a send fails: (1) sms:diag — is the DSN parsed and is from set? (2) curl the vendor endpoint by hand with the same credentials to isolate package vs. vendor. (3) Log the raw response on failure only (PII: scrub the body/number first). (4) Confirm the queue worker has the env vars — a common failure is credentials present in web but missing in the worker container.

Compliance is a config decision, not a code one. GDPR/data-processor review, consent tracking for marketing vs. transactional, and retention rules for phone numbers all sit above this package. Build consent checks into the SmsSender decorator so no call site can accidentally send marketing SMS without a recorded opt-in — the transport doesn't know or care.

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.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums