tomatophp/messagebird-notifications
Laravel notification channel for MessageBird, letting you send SMS notifications through MessageBird's API using the familiar Laravel notification system. Configure your credentials and start delivering messages to users in minutes.
Architecture fit. The package is a narrow adapter into Laravel's native Notifications abstraction: a service provider, a channel class invoked through via(), a message builder returned by toMessageBird(), and a config file for API key and originator. It fits cleanly if and only if Laravel Notifications is already the product's outbound messaging abstraction — then SMS becomes one more channel alongside mail and database, with no new dispatch pattern to teach the team. It is explicitly not a messaging platform layer: no multi-provider abstraction, no templating, no delivery receipts, no failover, no rate-limit management. Adopting it is a point commitment to Bird (formerly MessageBird) for the SMS channel, which is a procurement and commercial decision as much as a technical one.
Integration feasibility. Code volume is small (~200 lines of adapter), the install path is standard (composer require, publish config via --provider, two env keys), and the per-notification surface is two methods. Realistic engineering cost is 1–2 days to a verified first send, most of which is reading source rather than writing code — there is no meaningful documentation; the v1.0.0 changelog is a commit link. The package is therefore feasible but not self-explanatory: the source is the specification, and the first hour should be budgeted for reading, not coding. A credible internal build (channel class wrapping the official Bird SDK) is 1–2 days, so the "buy" case rests almost entirely on schedule pressure, not capability depth.
Technical risk. Ranked by likelihood × impact:
messagebird/php-rest-api will. The Bird rebrand moved namespaces and endpoints, and a major SDK bump is the realistic breakage point — not this package's releases.composer.json genuinely pins a 5.x framework range, this is not an integration task but a fork-or-rebuild decision, and at this code volume rebuilding is usually cheaper than backporting.env() calls in src/ work in development and return null the moment config:cache runs in production — a 2FA outage that only appears after deploy. Similarly, mixing 'messagebird' and MessageBirdChannel::class between via() and Notification::route() produces "driver not supported" or messages that report sent and never arrive.Key questions to resolve before committing.
laravel/framework and PHP ranges does composer.json actually declare, and what SDK version does the lock resolve?Channel::extend('messagebird', …) driver, or only a class binding? This determines the string convention for the whole codebase.toMessageBird() expect a message object or a string, and what is the builder's exact API?env() calls outside the config file? (grep -rn "env(" vendor/tomatophp/messagebird-notifications/src)Stack fit. Assumes a Laravel application using Notifications, a working queue worker (SMS must be queued), and E.164-normalised phone numbers on the Notifiable model. The phone normalisation belongs in a model mutator, not the notification — this is the one validation that cannot be skipped. Secrets should route through config/services.php and be read via config(), never env(), so config:cache is safe.
Build vs. buy decision gate. Run a half-day diligence spike before any sprint commitment: composer show for both the package and the SDK, read the service provider, read the channel and message classes, grep for env(. Three outcomes:
"tomatophp/messagebird-notifications": "1.0.0" — a caret on a single-maintainer package with no changelog buys nothing but surprise).Migration path. Do not rip out an existing SMS provider. Introduce the channel behind a feature flag, run it for a pilot cohort on one use case, and keep the incumbent path live until delivery is verified on real handsets in every target country. The provider-swap seam should be a decorator around the channel class, registered in via(), so replacing Bird later touches one binding rather than every notification class.
Sequencing.
config:clear, confirm values resolve through config().tinker against a non-production key, to a real handset. This is the smallest, highest-value, unambiguous verification.$tries / $backoff / $maxExceptions on queued notifications; idempotency guard (ShouldBeUnique or a sent_notifications dedupe table) at the call site, not in the channel.mail via array_filter so a Bird outage is not a login lockout.Compatibility notes. Keep SMS in its own single-channel notification class. Combining mail and SMS in one queued notification means a mail failure re-sends the SMS — a pure cost bug. Gate hard SMS requirements on country where sender-ID rules differ (an alphanumeric originator that delivers to a UK handset may be dropped for a US one). Assert message length in tests: 160/153 characters for GSM-7, 70/67 for UCS-2 — a single emoji flips the encoding and can double the bill.
Maintenance. We own this adapter. No SLA, no issue history, no third-party validation. Requirements: a named owner, exact version pinning, a committed lock file, and an integration test re-run on every Laravel and Bird SDK upgrade. Watch the SDK's releases, not this package's — that is where breakage originates. Extension preference order: publish and edit config → decorate the channel → extend the message builder → rebind in our own provider → fork. Never patch vendor/.
Support. Upstream support is effectively zero; the support relationship that matters is the commercial one with Bird, which is a separate line item and a separate negotiation. Internally we need a runbook for the highest-frequency failure symptom: "the notification says it sent but nothing arrived." That symptom has at least four causes — channel-name mismatch between via() and route(), unregistered or country-blocked sender ID, DND lists, or config:cache nulling an env() read — and on-call must be able to distinguish them without reading vendor source at 3am.
Scaling. Throughput is bounded by the Bird account's rate limits plus queue worker capacity, not by the package. The real scaling concern is unit economics: SMS is billed per segment per attempt, so message copy discipline and retry policy are cost controls, not just engineering hygiene. Keep transactional copy ASCII and under 160 characters and enforce it with a test. Treat the segment math as the cost model in any capacity or pricing discussion.
Failure modes.
200 from Bird means queued, not received. The package surfaces no delivery receipts, so 2FA UX must include a resend path and a timeout rather than a binary "sent" state.env() in package source breaking under config:cache; driver name mismatch; unnormalised phone numbers (07123456789) failing or misrouting.Ramp-up. An engineer new to the package should expect roughly one hour of source reading before the first line of application code, then about a day to a safely queued, idempotent, verified send. Two concepts must be taught explicitly because they are the top silent-failure sources: the channel-name convention (class name vs. registered driver string, applied consistently across via() and Notification::route()), and the permanent-vs-transient error split that decides whether a failed send costs one attempt or three. Product-side, the corresponding ramp-up is accepting that "sent" is not "delivered" and funding the resend and fallback UX that follows from it.
How can I help you explore Laravel packages today?