laravel-notification-channels/expo
Laravel notification channel for sending push notifications through Expo. Add Expo push support to your Laravel app using familiar notification classes, with token handling and customizable message payloads.
Architecture fit. The package slots into Laravel’s native notification abstraction as a custom channel driver (ExpoChannel implementing send()), driven by the Notifiable trait and via() array. For a product already using Laravel notifications for mail/database/broadcast, this is the lowest-friction path to mobile push: no new domain service, no parallel delivery framework, and push becomes another entry in the same notification class. It complements broadcast (online users) by covering offline/backgrounded users. Release 2.5.0 does not alter this — it is a dependency-constraint release, not an architectural one.
Integration feasibility. High, and marginally improved by 2.5.0. Composer install with service-provider auto-discovery, a routeNotificationForExpo() method on the Notifiable model, and one line in via(). A working prototype is a sub-day effort for a Laravel-fluent engineer. The real work is not the channel — it is token lifecycle (registration, refresh, invalidation) on the client and in storage. The Guzzle 8 allowance removes a plausible install-time conflict for newer Laravel stacks that pull Guzzle 8 transitively; previously that conflict could only be resolved by pinning Guzzle or forking. It does not change the channel's behavior, payload handling, or error semantics.
Technical risk. Moderate-to-high on ecosystem signals, low on code surface:
laravel-notification-channels family, so bus factor and release cadence are the dominant risks. 2.5.0 is a mild positive on cadence — a maintainer is present and shipping — but a single dependency-range bump is not evidence of a support policy or a roadmap.composer.json.^7.0|^8.0 or similar) is an installability claim, not a tested-compatibility claim. Guzzle 8 may carry breaking changes in request options, handlers, or PSR-18 wiring; if CI does not exercise both majors, treat Guzzle 8 as untested until verified.Key questions.
composer.json, and is there a stated support policy for the next major?DeviceNotRegistered / InvalidCredentials errors surfaced — exception, return value, or silence?channelId, badge, sound, data payloads?Stack fit. Laravel 9–12 on PHP 8.1+, queued notification classes (ShouldQueue) on existing workers, Expo-managed or bare React Native apps using expo-notifications. The channel is transport-only, so it composes cleanly with database (in-app inbox/audit) and mail (fallback) in the same notification. With 2.5.0, the package no longer forces a Guzzle downgrade on applications already on Guzzle 8 — relevant if other SDKs in the same app require the newer major.
Migration path. If push is currently ad-hoc cURL against Expo or direct FCM/APNs:
expo_push_token column rather than overloading a generic push_token.If the app is bare RN with raw FCM/APNs tokens, note that Expo’s push service only accepts ExponentPushToken[...]; token conversion via the Expo SDK is a prerequisite, not a channel concern.
Compatibility. Verify payload key mapping against the current Expo push API (title, body, data, sound, badge, channelId, priority, ttl). Android 8+ requires a notification channel to be created client-side or notifications silently don’t display. Confirm the 4KB payload ceiling is enforced or at least documented. Add one item specific to 2.5.0: resolve composer.json in a scratch project on both Guzzle majors and confirm the resolved graph installs cleanly; do not assume a widened constraint means both paths are exercised in CI.
Sequencing. (1) Spike in staging with a single device; (2) ship client token registration/refresh plus server-side storage and uniqueness constraints; (3) add the notification class and channel; (4) build receipt polling and token-pruning jobs; (5) percentage rollout with delivery metrics; (6) decommission the legacy sender. Steps 2 and 4 are the schedule risk, not step 3 — and 2.5.0 does nothing to reduce them.
Maintenance. Small code footprint and a shallow dependency tree mean low ongoing upkeep, but you own the risk of upstream drift. 2.5.0 is a small positive maintenance signal and slightly widens the set of compatible host applications, which reduces the odds of a forced fork for dependency reasons. It does not reduce the abandonment risk itself: pin the version, watch releases, and budget for a fork if the package stalls — forking a channel driver is a days-not-weeks task, which materially lowers that risk.
Support. Community support is thin at this adoption level; internal documentation and a runbook are effectively mandatory. For API-level behavior (rate limits, receipt semantics), Expo’s own docs are the authoritative source, not the package README. Treat the 2.5.0 changelog line as the entire description of the release — there is no stated compatibility or testing policy to fall back on.
Scaling. Expo’s push service absorbs fan-out, but you inherit its limits: ~100 messages per request and per-project request-rate caps (~600/s). Batch sends, keep them queued, and tune worker concurrency so a campaign doesn’t starve transactional notifications. Index the token table on user ID and token; prune aggressively. Unchanged by 2.5.0.
Failure modes to instrument.
DeviceNotRegistered / expired tokens → silent non-delivery unless receipts are polled; drives token-table bloat and wasted quota.exp.host → retries without idempotency can duplicate sends.channelId → accepted by the API, invisible to the user.Ramp-up. Roughly half a day for a Laravel developer to ship the happy path; add one to two weeks of engineering for token lifecycle, receipt handling, pruning, and observability — that is where the actual product risk and cost sit. QA requires physical iOS and Android devices; simulators do not validate push end-to-end. 2.5.0 does not change these estimates.
How can I help you explore Laravel packages today?