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.
.p8 provider-token minting, JWT signing, refresh cadence, and Apple's throttling rules. This package reduces that to a toApn() method on a notification class the team already writes. A sprint of specialized, permanently-owned infrastructure becomes an MIT dependency you can staff behind.429 TooManyProviderTokenUpdates risk. That is the throughput argument for adopting this over a hand-rolled integration that mints a token per send..p8) and certificate auth, plus sandbox/production endpoints, matter if the product ships a family of iOS apps or a stage/prod split. Decide this early—retrofitting per-app credentials after the token cache is in play is a known source of intermittent 403 InvalidProviderToken bugs.Adopt when:
via()/toApn() beside channels you already run.Look elsewhere / reconsider when:
.p8 or Key ID without flushing the token cache leaves a ~20-minute stale window where a revoked key still works.^6.4, keep the channel behind your own notification abstraction, and know what you'd do if it went unmaintained. If vendor SLAs are a procurement requirement, this fails that test.users.device_token column within a sprint—multiple devices, reinstalls, iPad + iPhone. If there's no plan for a device-token table and a Unregistered/BadDeviceToken cleanup path, the feature ships broken and stays broken.To executives: Native iOS and macOS push can ship inside our existing notification system instead of as a standalone infrastructure project. That converts a multi-week, specialist-owned build into a dependency plus payload work, and it slots into a roadmap we already have rather than adding a new one. The recent release also fixes how Apple credentials are handled—they no longer sit in our cache store, and the auth token is now reused for 20 minutes the way Apple intends, which is both a security and a scale story. The trade-off to accept consciously: this is a small open-source package with essentially one maintainer, so we treat it as self-supported, keep it behind our own abstraction, and pin the version. It sends notifications. It does not run campaigns, segment audiences, or report on delivery—if we want that, it's a separate buy decision.
To engineering: It's an idiomatic Laravel notification channel—ApnChannel in via(), toApn() returning an ApnMessage, config published from the provider. Supports .p8 JWT and certificate auth, bundle ID as apns-topic, sandbox/prod switch. Queue it by default with its own queue and a worker timeout under your retry_after; resolve the client from the container so the 20-minute token cache is actually shared. Handle CouldNotSendNotification in failed(): soft-delete token rows on Unregistered/BadDeviceToken and don't retry, back off on 429/503/TooManyProviderTokenUpdates. Beware env() outside config/apn.php under config:cache, store the .p8 as a file path with intact BEGIN/END lines and trailing newline, and don't hit Apple in tests—Notification::fake() plus one real sandbox device check.
To security/review: Credentials have moved out of the cache backend, and the .p8 lives outside the webroot, referenced by path rather than inlined in env. Rotation is a runbook step: rotate the key and flush the token cache in the same change, or the old key stays live for the TTL window.
To the iOS team: Categories must match UNNotificationCategory registrations in-app; deep-link identifiers go in the custom payload, not stuffed into body. Treat silent/data-only pushes (contentAvailable) as best-effort—iOS budgets them and drops them under low power, so nothing the app depends on should ride on them alone. And "sent successfully" is not "shown": permission denial and Focus mode are invisible to the server. The apns-id we set is for log correlation, not proof of delivery—don't let product logic assume otherwise.
How can I help you explore Laravel packages today?