---
title: "Apple Push Notification service"
description: "Use Apple Push Notification service (APNs) for MainSDK push on iOS - signing key, bundle ID, environments, main.yaml, testing and swapping."
section: "integrations"
canonical: "https://mainsdk.dev/docs/integrations/apns"
updated: "2026-10-28"
---

# Apple Push Notification service

> Use Apple Push Notification service (APNs) for MainSDK push on iOS - signing key, bundle ID, environments, main.yaml, testing and swapping.

Canonical: https://mainsdk.dev/docs/integrations/apns · Updated: 2026-10-28

Apple Push Notification service (APNs) is the only way to deliver push notifications to iPhone and iPad apps. In a MainSDK app it is a push driver for [`notify`](https://mainsdk.dev/docs/interfaces/notify): when push is the channel a person is reached on and they use an Apple device, APNs delivers it. Pick the APNs driver when you send push directly, usually next to FCM for Android; with Expo, Expo talks to APNs for you.

## Interfaces it serves

### Notifications (`notify`)

- Status: Building for 0.3 · ships as a stable API
- Driver: `apns` (Apple Push Notification service)
- Docs: https://mainsdk.dev/docs/interfaces/notify.md

```yaml
notify:
  driver: apns
```

Swap to another provider: `whatsapp-cloud`, `bird`, `msg91`, `twilio`, `local`, `fcm`, `expo`, `resend`, `ses`, `onesignal`, `slack`, `pagerduty`, `opsgenie`, `webhook`.

## Before you start

1. **Join the Apple Developer Program** with the team that owns your app.
2. **Enable push notifications** for your app's identifier (bundle ID).
3. **Create an APNs authentication key.** Token-based authentication uses one signing key for all your apps. Note the key ID and your team ID; the key file can be downloaded only once.
4. **Know the two environments.** APNs has a development (sandbox) environment for builds from Xcode and a production environment for TestFlight and App Store builds. A device token works only in the environment it was issued for.

Store the signing key in your secrets manager, never in code or the app bundle.

## Configure

```yaml title="main.yaml"
notify:
  channels: [push, whatsapp, sms, email]
  push: { drivers: [fcm, apns] }   # or via: expo
```

How the APNs driver takes its key, key ID, team ID, bundle ID and environment is not in the spec yet [TBD]; the key will be a `secret:` reference such as `secret:APNS_AUTH_KEY` (suggested name). A literal key fails validation with `MAIN-CONFIG-0004`.

## Use it

```ts title="app/notifications/setup.ts"
import { notify } from "mainsdk/notify";

await notify.push.register();   // asks permission on iOS, stores the token

await notify(user, { template: "order.shipped", category: "transactional" });
```

## Local development and testing

- Test real pushes on a device with a development build, against the APNs sandbox.
- `createTestApp()` from `@mainsdk/testing` runs `notify` on its in-memory fake, and `chaos.on("notify", { outage: true })` simulates an outage.
- Until a push driver is configured, the push channel is skipped.

## What MainSDK adds on top

- Push only with the person's opt-in; opt-outs and quiet hours respected.
- No personal data in push payloads, which also keeps lock-screen previews free of it.
- A retry with the same idempotency key never sends twice.

See [Notifications](https://mainsdk.dev/docs/interfaces/notify).

## Limits and notes

- The APNs driver comes with the expo-adapter spec (phase 3, milestone 1.1).
- iOS asks the person for permission once; if they decline, push is skipped and `notify` tries the next channel.
- Where device tokens are stored is not in the spec yet.

## Swap to or from it

```yaml title="main.yaml"
notify:
  push: { drivers: [fcm, apns], via: expo }   # was { drivers: [fcm, apns] }
```

> **Warning:** APNs device tokens differ from Expo push tokens, so moving between direct APNs and Expo needs each device to register again. How MainSDK runs that move is [TBD].

## FAQ

**Why do pushes work from Xcode but not from TestFlight?** TestFlight and App Store builds use the production APNs environment, and tokens from a development build don't work there.

**Do I need APNs if I use Expo?** You still need an APNs key, but you give it to Expo and set `via: expo`.

## Next

- [Notifications](https://mainsdk.dev/docs/interfaces/notify)
- [Firebase Cloud Messaging](https://mainsdk.dev/docs/integrations/firebase-cloud-messaging) and [Expo](https://mainsdk.dev/docs/integrations/expo)
- [Swap a driver](https://mainsdk.dev/docs/guides/swap-a-driver)
