Goal
Wire up Apple Push Notification service (APNs) and Firebase Cloud Messaging v1 (FCM) credentials so notifications dispatched through mam_notification_send_message actually reach iOS and Android devices.
Prerequisites
mam-maininstalled and activated- A signed Apple Developer Program membership (for the
.p8key) - A Firebase project (for the FCM v1 service account)
- Bundle id for iOS (
ios_pn_app_bundle_id) and package name for Android (android_pn_app_bundle_id) already set in Mobile App Manager → Publish Your App - (Optional) A separate VoIP APNs
.p12cert if your app uses CallKit for incoming-call wakeups
Steps
1. Issue an APNs .p8 key in Apple Developer
In Apple Developer → Keys, create a new key with Apple Push Notifications service (APNs) enabled. Apple gives you:
- A
.p8file (download immediately — Apple won’t let you re-download later) - A 10-character key id
- Your team id (10-character; visible in your account)
2. Issue a Firebase service-account JSON
In Firebase Console → Project Settings → Service accounts → Generate new private key. Firebase gives you a JSON file with the service-account credentials. The file must include the messaging.send scope.
3. Open the push admin
Mobile App Manager → App Settings → Push Notifications.
4. Configure APNs
Fill in:
- APNs
.p8key — paste the contents of the.p8file - APNs key id (
ios_pn_key_id) - APNs team id (
ios_pn_team_id) - iOS bundle id (
ios_pn_app_bundle_id) — must match the bundle id you submit to App Store Connect
The credentials are read by MAM_IosCredentials. The provider token is provisioned and cached by MAM_ApnsTokenProvider.
5. (Optional) Configure VoIP APNs
If your app uses CallKit-style incoming-call wakeups, you need a separate VoIP cert (Apple treats VoIP as a different push topic). Upload it under the VoIP section. VoIP pushes are sent by MAM_IosVoipPushSender.
⚠️ VoIP has a strict 5-second iOS deadline — the app must call CallKit within ~5 seconds of receiving the push or iOS kills the process and bans further VoIP pushes for that bundle. The payload builder enforces required fields.
6. Configure FCM
Paste the Firebase service-account JSON. The credentials are read by MAM_AndroidCredentials; FCM v1 JWTs are signed by MAM_FirebaseJwtSigner.
7. Verify with a test send
Mobile App Manager → Notifications → Test Send. Pick a notification type (the bundled mam-push-notification is the simplest), pick a recipient (yourself if you’ve installed the Previewer), check the push channel, and send.
Status surfaces in Mobile App Manager → Notifications → Outbox Viewer. A successful send shows the per-token result; failures land in MAM_PushResult->errors with the APNs/FCM error code (e.g., BadDeviceToken).
Where credentials are stored
| Asset | Storage |
|---|---|
APNs .p8 key |
WordPress option (path or contents — varies by site) |
| APNs key id | Option (ios_pn_key_id) |
| APNs team id | Option (ios_pn_team_id) |
| iOS bundle id | Option (ios_pn_app_bundle_id) |
| Firebase service-account JSON | WordPress option |
| FCM project id | Option |
| Per-user iOS APNs token | usermeta key mam_app_ios_pn_token |
| Per-user FCM token | usermeta key mam_app_android_pn_token |
| Per-user VoIP token | usermeta key mam_ios_voip_token |
Tokens are written to usermeta by the mobile app on first launch via the user-roles AJAX endpoints. They’re read at send time by MAM_PushTokenRepository.
⚠️ Credentials are stored as plaintext today. Encryption-at-rest is a tracked hardening task. If your customer’s WordPress admin is shared with low-trust users, treat this as a known exposure.
Sending a push notification programmatically
do_action( 'mam_notification_send_message', array(
'message_type' => 'mam-push-notification',
'recipient_id' => $user_id,
'subject' => 'Your order shipped',
'message' => 'Tracking: ' . $tracking_number,
'send_now' => true,
) );
The dispatcher resolves mam-push-notification from the mam_notification_list registry, checks per-channel opt-in, and routes through MAM_Pn_Sender. See Hook: mam_notification_send_message.
Verification
- The test send completes without an error in the outbox
- The Previewer app receives the notification (open
Settings → Notificationson iOS / Android to confirm push permissions are granted) - A failed send shows a structured error (e.g.,
BadDeviceToken) inMAM_PushResult->errors— not a generic 500
Gotchas
- APNs vs APNs VoIP require separate certificates and separate tokens. A standard APNs token won’t receive VoIP pushes.
- FCM v1, not legacy. The Android sender uses the FCM v1 OAuth2 service-account JWT path. The deprecated server-key API isn’t supported.
- Token churn is normal. APNs tokens change when the user reinstalls or restores from backup. The
BadDeviceTokenerror inMAM_PushResult->errorsmeans “drop this stored token; the device will re-register.” - APNs HTTP/2 connection reuse isn’t pooled across requests today. High-volume sends (>100/request) pay TCP setup cost per send. Tracked for performance work.
Related articles
- Recipe: Build an app end-to-end
- Notifications overview
- Hook: mam_notification_send_message
- Hook: mam_notification_send_pn
- Hook: mam_notification_list
- Frozen public contracts reference
Metadata
| Field | Value |
|---|---|
| Article type | Recipe (Admin) |
| Plugin slug | mam-main |
| Applies to plugin version | 2.1.11+ |
| Category | Building Your App |
| Audience | WordPress admin |
| Estimated time | 30 minutes (excluding Apple/Google credential issuance) |
| Last verified | 2026-05-02 |
