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

Apn Laravel Package

laravel-notification-channels/apn

Send push notifications to iOS, macOS, and watchOS from Laravel using Apple's APN service. This notification channel supports token and certificate authentication, HTTP/2, custom payloads, and easy integration with Laravel's notification system.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit. The package is a thin, idiomatic Laravel notification channel — it slots into the framework's existing via() / toApn() contract rather than introducing a new subsystem. Architecturally that is the right shape: push becomes one channel among mail, database, and SMS in the notification layer you already own, and the vendor code stays behind an interface (the Notification class) instead of leaking into product code. The integration surface is small and well-bounded: a service provider, a config file, an ApnMessage payload builder, and a client that speaks APNs HTTP/2 with JWT provider-token auth.

Integration feasibility. Low-to-moderate. The prerequisites are concrete and enumerable: a .p8 key, Key ID, Team ID, bundle ID (apns-topic), and a sandbox/production flag from the Apple Developer portal. The smallest viable spike is a single on-demand notification from tinker against one device token — if it returns without throwing, credentials, topic, and endpoint are correct. That is a one-to-two-day task, not a sprint. The real feasibility question is not the package, it's the environment: you need a shared cache store (Redis/Memcached/database) to get the intended benefit of the 20-minute provider-token cache. A single-node file cache is functionally correct but forfeits token reuse across processes.

Technical risk. Ranked:

  1. Token cache key isolation (highest severity). v6.4.0 caches the provider token for 20 minutes. If the cache key does not isolate by environment, Team/Key ID, and bundle/topic, a token minted for one app is reused for another and produces intermittent 403 InvalidProviderToken — intermittent because it only fails inside the TTL window. This is the single biggest thing to verify in source before running two apps off one cache.
  2. Credential-bearing client was previously serialized into the cache. v6.4.0 fixes this, which is a genuine security improvement worth citing in sign-off. Teams upgrading from pre-6.4 must flush the cache namespace and remove any application-level client caching they added themselves.
  3. Credential rotation has a 20-minute stale window. Rotating the .p8 or Key ID without flushing the token cache means the revoked key keeps working until TTL expiry — an hour of confusing debugging.
  4. Self-supported dependency. 201 stars, zero dependents, effectively one maintainer. MIT and narrow-scope, so the blast radius of abandonment is small, but assume you are the support organization.
  5. Payload and delivery semantics. 4KB ceiling; PayloadTooLarge on fat custom payloads. And "sent successfully" ≠ "shown" — permission denial and Focus mode mean push cannot be the sole mechanism for state the app depends on.

Key questions.

  • What Laravel and PHP versions are actually running in production, and is Laravel 13 on the roadmap? (v6.4.0 is explicitly the Laravel 13 cache-compliance fix.)
  • Do we have, or will we provision, a shared cache store? If not, the caching argument for this package weakens considerably.
  • How many iOS/macOS apps and environments (stage/prod, sandbox/production) will share a deployment? This determines whether the global config is sufficient or we need per-app client construction.
  • Who owns the Apple Developer portal credentials, and what is the rotation runbook?
  • Is the requirement genuinely transactional/alert-style push to known tokens, or is it drifting toward campaign management?
  • Do we already have a device_tokens table, or are we starting from a single users.device_token column?

Integration Approach

Stack fit. Strong, conditional on Laravel/PHP. The value proposition here is framework integration — a dedicated push provider is the correct comparison if the backend is not Laravel. Within Laravel, the channel convention means push rides an existing notification epic rather than gating a release on a new subsystem.

Migration path. Two distinct paths:

  • Greenfield push: publish config, wire credentials, add ApnChannel::class to via(), implement routeNotificationForApn() and toApn(), smoke-test on-demand, then queue. Roughly one sprint including device-token plumbing and testing.
  • Existing push integration: migrate senders to the notification abstraction behind a toApn() method, keep one integration test that actually hits the sandbox, and retire the old client only after parity is confirmed. Retrofitting per-tenant credentials after the token cache is in play is where the 403 bugs live — decide multi-app architecture early, not after.

Compatibility. The package supports both .p8 token auth and certificate-based setups, and both sandbox and production endpoints. The compatibility risk is concentrated in Laravel version behavior: the pre-6.4 pattern of caching the whole client breaks under Laravel 13's stricter cache controls, and config:cache in production is a real footgun — only env() calls inside config/apn.php survive the cache. Any env('APN_KEY_ID') read elsewhere returns null in production while working fine locally. Pin ^6.4.

Sequencing.

  1. Spike (week 1): credentials in config, single on-demand notification to one sandbox device. Confirms endpoint, topic, and key handling.
  2. Data model (week 1–2): introduce a device_tokens table keyed by user, not a single column. You will outgrow a single column within a sprint, and you need rows to soft-delete when APNs reports Unregistered.
  3. Cache and environment hardening (week 2): confirm shared cache store, verify cache key composition against source, namespace per app, and validate config:cache behavior in staging.
  4. First transactional use case (week 2–3): one notification class (order shipped, payment failed, or security event) behind ShouldQueue, on its own queue.
  5. Error handling (week 3): catch CouldNotSendNotification in failed(); soft-delete on Unregistered/BadDeviceToken, retry with backoff on 429/503/TooManyProviderTokenUpdates.
  6. Second use case and breadth (week 4+): categories and custom keys for deep links and actionable pushes; silent/data-only pushes as best-effort only.

Operational Impact

Maintenance. The package itself is narrow and stable — APNs is a fixed protocol and the payload API is small. Maintenance burden concentrates in three places: Apple credential lifecycle (rotation, .p8 handling), device-token hygiene (pruning stale tokens), and Laravel framework upgrades (v6.4.0 was a Laravel 13 compliance change, not a feature). Treat credentials as an owned secret with a runbook, not a config detail. The .p8 is downloadable exactly once — store it outside the webroot at a durable path and reference the path, never inline the contents, because newlines get eaten and produce a 403 with no useful error.

Support. Low external support surface: MIT license, narrow scope, effectively one maintainer, zero dependents. This is not a vendor relationship. Budget for the possibility that a future Laravel release requires a patch you must write or fund. The mitigating factor is that the abstraction boundary is cheap to maintain — keep vendor classes out of product code via a base PushNotification or ApnMessage factory, and a package swap becomes a one-file change. Do not let product requirements creep toward segmentation, orchestration, A/B testing, or send-time optimization; this package sends pushes and will not grow into campaign tooling.

Scaling. The throughput story is the token cache. Ten thousand fan-out sends sharing one minted provider token instead of re-authenticating per send is the difference between a clean run and 429 TooManyProviderTokenUpdates. Two hard rules follow: resolve the client from the container rather than constructing it per send, and give push its own queue so a slow APNs HTTP/2 response cannot starve mail or webhooks. Set worker --timeout below the queue's retry_after. Also enforce the 4KB payload ceiling server-side — truncate body copy, send identifiers rather than serialized objects.

Failure modes. Enumerated by likelihood and consequence:

Failure Detection Response
Mangled .p8 (newlines eaten) 403 on every send, no useful error Store file path, not contents
Unregistered / BadDeviceToken Exception from channel Soft-delete token row, mark job success — never retry
Sandbox token sent to prod endpoint BadDeviceToken Bind APN_PRODUCTION to environment; never copy dev tokens into fixtures
Cross-app token cache collision Intermittent 403 inside 20-min window Namespace cache store per app; verify key composition in source
Rotation without cache flush Revoked key still works for 20 min Flush token cache as a step in the rotation runbook
429 / 503 / TooManyProviderTokenUpdates Exception Retry with backoff
config:cache nulls env outside config file Works locally, fails in prod Confine all env() reads to config/apn.php
PayloadTooLarge 4KB rejection Truncate body, send IDs

Note the observability gap: setId() gives you an apns-id for log correlation, but it is not proof of display — permission denial and Focus mode mean push delivery is not a business-logic guarantee.

Ramp-up. Shallow. A Laravel-fluent engineer can produce a working transactional push in a day given valid credentials. The non-obvious learning is operational rather than syntactic: token cache behavior, error-class handling on the queue, and the sandbox/production token distinction. Mitigate by pairing the first integration with one real-device end-to-end verification in staging — Notification::fake() will happily validate a payload Apple rejects, and simulators cannot be pointed at your real APNs key. Budget half a day of iOS-side work too: ->category('MESSAGE') must match a UNNotificationCategory registered in the app, and deep-link handling needs client-side plumbing for custom payload keys.

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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource