Skip to main content

Prerequisites

  • An Apple Developer account
  • An APNs authentication key, downloaded as a .p8 file
  • Your team identifier and the app bundle ID you send to
  • A physical iOS device, since simulators do not receive push

Setup

1

Create an APNs key

In your Apple Developer Account, open Certificates → Keys and add a key. Enable Apple Push Notifications service (APNs), register it, and download the .p8 file.
2

Enter your credentials

Open the and fill in four fields:
  • Key Id, the identifier of the APNs key.
  • Key, the full contents of the downloaded .p8 file.
  • Team Id, your Apple Developer team identifier.
  • Topic (App Bundle Id), the bundle ID of the app you are sending to.
Then set the two toggles:
  • Send to Production, off by default. Off targets Apple’s sandbox, which a local development build needs. On targets production, for your App Store app. Getting this wrong is the usual cause of BadEnvironmentKeyInToken.
  • Attach Mutable Content, on by default. It adds "mutable-content": 1, which is what lets Courier’s run before the alert appears. The extension is what reports a delivery while your app is closed. Turn this off and delivery only registers in the foreground. See .
APNs provider configuration showing the Send to Production and Attach Mutable Content toggles

The APNs provider configuration with the Send to Production and Attach Mutable Content toggles.

3

Install the provider

Click “Install Provider” or “Save”.

Profile requirements

Device tokens live on the user profile rather than on the send, so one profile can hold tokens for several devices and every push provider reads them from the same place. covers the model. APNs addresses the device by token, so the profile you send to needs an apn object. Store the token once with , which merges into the profile and creates it if it does not exist:
apn.token holds a single device token.
A Courier mobile SDK writes that object for you once you extend CourierDelegate, so you send to a user_id and never handle a token. APNs is Apple-only, so that means the , , and SDKs. The Android SDK uses instead. For a one-off with no stored profile, pass it inline instead: "to": { "apn": { "token": "YOUR_APNS_TOKEN" } }.

Set up push notifications

Wire up the SDK, sync tokens, and send your first push.

Send by user id

The call in every language, and the rest of the profile object.

Send to a recipient

Courier reads apn off the saved profile, so preferences apply and the value can change without touching this code.

Overrides

covers the two levels and which one wins. lists the fields every push provider takes. An APNs override sits at providers.apn.override. body changes the notification, and config swaps the credentials the send authenticates with.

Body overrides

Courier compiles the reserved aps dictionary from the message content and these fields. For a push that carries data but shows no alert, see .

Config overrides

Each of these replaces the integration’s own credentials and settings, for one send.
override.body.aps replaces the aps dictionary, it does not merge. The rendered alert, sound, badge, category, thread-id, and content-available are all discarded unless you restate them. Use the individual fields above to change one and keep the rest.
An aps override leaves the rest of the request alone. expiry, topic, apns-priority, apns-collapse-id, and your own payload keys are headers or sit outside aps, and mutable content is applied afterward.

Override the payload

The apn override applies whenever APNs is the provider that runs.
Custom data, deep links on tap, and silent pushes are the same call with a different payload. covers each, including where APNs and FCM differ. Custom data not reaching the device? covers the one template setting that stops it.

Troubleshooting

BadDeviceToken

APNs returns BadDeviceToken when the token does not match any registered device for the environment you target (production vs. sandbox). Common causes:
  • Wrong environment: a token registered against the sandbox environment fails in production, and vice versa. Check the Send to Production toggle in the .
  • Stale token: the user uninstalled and reinstalled the app, generating a new token. Update the stored token via the when your app receives a new registration token.
  • Token never registered: the token was never added to a Courier user profile. Your app must call the registration step on every launch.

BadEnvironmentKeyInToken

The key that signed the APNs JWT belongs to a different team or bundle ID than the token. Check that the Team ID and Key ID in your Courier APNs configuration match the app whose tokens you send.

Invalid device tokens

Courier records rejected tokens so later sends skip them. This applies only to managed tokens, the ones a Courier Mobile SDK syncs. Tokens you pass in apn.token or apn.tokens are used as sent.
  • BadDeviceToken, Unregistered, DeviceTokenNotForTopic, and MissingDeviceToken mark that token failed until the device registers a fresh one.
  • The status is scoped to one token and user. Other users, tokens, and channels are unaffected.
  • Managed tokens not refreshed in 60 days go stale and are skipped. The SDKs refresh on app launch.
If a user stops receiving push notifications, confirm their device has registered a current token. Reinstalling the app generates a new one. Confirm your app calls the Courier SDK’s token registration on every launch.

Provider details

Courier recommends routing to the channel. Naming this key in routing.channels instead is supported, and sends through just this provider.

Send to a specific provider

When that is worth doing, and what you give up: failover, channel priority, and providers you add later.