Engagement
Area: 10 Engagement · 7 items · Source: PADI Mobile Scope Alignment v-ceb35f4d
Subject: the unified PADI app — Expo React Native. Every verdict below is about what that app can build, not about what today's apps do.
What this card covers, and what it does not
The area holds 12 items — 2 carried forward from web, 7 in the Q1 2027 Delta lane, 3 post Q1 2027.
This card is the seven Q1 Delta items —
COMM-01,ENG-01,ENG-02,ENG-03,ENG-04,ENG-05,ENG-07.
MYP-07(Email communication preferences) andPREF-02(Manage contact preferences) are carried forward from web and analysed in full in Logged In Experience, where they are recorded as one capability under two identifiers. Count them once across the two sub-tasks.Post Q1 2027, named and not analysed:
ENG-08(dive-log reminder / interactive notifications),ENG-09(content / articles / blog surface in-app),ENG-10(behavioural nudges). Two of the three are contradicted by the backlog — see the lane table below.
ENG-06does not exist in the artefact — and unlike the study's three earlier ID gaps, this one is not benign. See The finding to lead with.
Not one of the seven items carries a requirement. No detail drawer, no Impact, no Effort. Everything below is written against a title and a flag.
The finding to lead with
The artefact promises an in-app inbox, has no item for it, and the backlog funds it for Q1.
The area blurb reads, verbatim: "Email and lifecycle messaging are carried forward. The delta is push as a channel and the in-app inbox."
Searching the artefact for inbox returns that sentence and nothing else. No ENG- item is the inbox. The IDs run 01–05, then 07–10; ENG-06 is missing, and the inbox is the capability missing with it.
Jira has it. PAM-49 In-App Message Centre / Inbox, epic PAM-12 Engagement, fix version Q1 - Release 2027, with a written requirement:
"I want every push notification also delivered into a persistent in-app inbox… The same notification is also saved into the in-app inbox… Diver opens the inbox at any time to see a list of past notifications."
So a funded Q1 story sits in no feasibility sub-task, because the scope artefact has no line for it. Anyone scoping this area from the artefact alone will miss it.
The good news, established below: it is not greenfield. Two working implementations already exist in the estate.
And the artefact and the backlog disagree on three items, in both directions
| Item | Artefact lane | Jira story | Jira lane |
|---|---|---|---|
ENG-07 Dive buddy notifications |
Q1 2027 | PAM-50 |
Later - Release 2027 |
ENG-08 Dive log reminders |
post Q1 | PAM-77 |
Q1 - Release 2027 |
ENG-10 Behavioural nudges |
post Q1 | PAM-75 |
Q1 - Release 2027 |
Learning's mismatches all ran one way, which reads as a stale flag. These run both ways, which does not. One of the two records is wrong on three of this area's ten items.
A fourth notification item sits outside the Engagement epic entirely — PAM-66 Cert-renewal / expiry reminders, under PAM-6 Certifications, at Q1.
How to read the evidence in this card
The unified app is a new client. It inherits no code from the apps below; they are evidence that an endpoint answers a real client, never a head start.
Citations name repositories, never folders. Paths are relative to the repository root. Nothing under .claude/worktrees/ is cited.
This area has three mobile surfaces, not two — and that is the whole story
Every other card in this study reads two mobile apps that behave alike. Engagement does not:
| Surface | Push stack | Device registry | Inbox | Preferences |
|---|---|---|---|---|
padiww/ios-padi-app · padiww/android-padi-app |
Salesforce Marketing Cloud SDK | none at PADI — token goes to the vendor | disabled | none — hands off to web |
diviac/travel-rn (PADI Adventures) |
Firebase Cloud Messaging | POST account/devices/ |
GET account/messages/ |
5 categories × 3 channels |
PadiWW/padi.mobile.app (the unified app) |
nothing | nothing | nothing | nothing |
Reading only the PADI app would put five of these seven items in the gap column. Adventures is a PADI-owned React Native app that has already built most of this area — on a different backend, a different push provider and a different auth realm. That is the single most useful fact in this card, and it is also the trap: existing in Adventures is not available to the unified app without a realm decision.
The realm is the catch. the API inventory §7 records Adventures as travel.padi.com/api/mobile/v1 (Django), Authorization: JWT not Bearer, no refresh token, session in unencrypted AsyncStorage. Everything below that cites diviac/travel-rn inherits that caveat.
COMM-01 — Communication channels other than email
Flags: PRK · Status: Existing — mobile (Adventures) · Feasibility: push yes; SMS unverified as a sending channel
What the unified app has to deliver
Reaching a diver by something other than email.
What it can rely on
Push is real and shipping on both mobile surfaces, by two different routes — see ENG-01.
SMS is offered as a choice but never observed as a channel. diviac/travel-rn app/components/form/NotificationsPreferencesForm.js renders an SMS column across all five preference categories, so the Adventures data model has an SMS flag per category. But searching all five PADI web repositories and both PADI mobile apps for a sending integration — Twilio, an SMS gateway, a message-send endpoint — returns nothing. The only sms matches are Apple Developer 2FA references in padiww/ios-padi-app fastlane/Fastfile, which are CI plumbing.
A checkbox is not a channel. What is established: Adventures stores an SMS preference. What is not: that anything sends one.
What it has to build
Nothing, for push. For SMS, the question is not client work at all.
What blocks it
SCOPE-43 — which channels does PADI commit to platform-wide? The answer decides whether ENG-02 has three channels to arbitrate between or two.
ENG-01 — Push notifications (app-native, interactive)
Flags: Now · Status: Existing — mobile, by two different routes · Feasibility: delivery is solved; "interactive" is not, and the registry is realm-bound
What the unified app has to deliver
Rich push with action buttons, images and a deep link straight to the relevant screen. PAM-44 states it plainly:
"I want to receive rich push notifications with action buttons, images, and direct links to the relevant screen… Diver taps an action button directly within the notification."
What it can rely on
Route 1 — the PADI apps, on Salesforce Marketing Cloud. padiww/ios-padi-app Podfile:24 vendors MarketingCloudSDK 9.0.3; padiww/android-padi-app gradle/libs.versions.toml:64 pins 8.1.5, with a dedicated Maven repository at build.gradle.kts:20. Firebase is present in both apps but only Analytics, Crashlytics, Performance and Dynamic Links — there is no Firebase Messaging artifact in either.
The device token never reaches PADI. padiww/ios-padi-app PADI/data/repository/impl/PushNotificationHandlerImpl.swift:175-178 hands it to SFMCSdk.mp.setDeviceToken; padiww/android-padi-app app/src/main/java/com/duns/padiapp/util/AppUtils.kt:90-99 reads pushMessageManager.pushToken and calls setProfileId. Diver identity binds inside the vendor SDK via setContactKey (PADI/data/repository/impl/SessionRepositoryImpl.swift:243), never through a PADI endpoint.
So on the PADI realm there is no device-registration API to reuse.
Route 2 — Adventures, on Firebase, with a registry. diviac/travel-rn package.json carries @react-native-firebase/messaging ^21.6.1, react-native-push-notification ^8.1.1 and @react-native-community/push-notification-ios ^1.11.0. app/containers/App.js:59-84 requests permission, calls messaging().registerDeviceForRemoteMessages() and fetches the token — and app/containers/AuthWrapper.js:34-47 registers it against the Adventures backend:
accountDevices.create({ registrationId: fcmToken.data, name, deviceId: getUniqueId() })
against endpoint: 'account/devices/' (app/containers/AuthWrapper.js:93). The same flow patches installationDatetime onto the account with the comment "required for some push notifications".
Foreground messages are rendered locally — app/containers/App.js:99-107 calls PushNotification.localNotification from the messaging().onMessage handler, onto a channel created at app/containers/Configure.js:13-19 and named "PADI Bookings notifications".
A PADI-owned device registry therefore exists. It is simply not on the PADI realm.
What it has to build
The interactive half, everywhere. Neither PADI app defines a single UNNotificationCategory or UNNotificationAction (iOS) or NotificationCompat.Action (Android). Android has exactly two notification channels, both generic — "UrlNotification" / "PADI Notification" (padiww/android-padi-app app/src/main/java/com/duns/padiapp/PadiApplication.kt:212-224) and the SDK default. Tap-to-open a URL is the whole of today's behaviour.
Rich, actionable notifications are routine work in Expo. The platform question is the real one, and it is not the app's to answer.
What blocks it
PADI-32 — which push platform does the unified app use? The three answers have very different shapes: stay on Marketing Cloud and inherit its registry; adopt the Adventures FCM stack and its account/devices/ registry along with the Travel realm; or have PADI build a first-party device registry. Nothing in the app can be estimated until this is settled.
A note on the Marketing Cloud configuration. The SFMC tenant settings are compiled into the shipped binaries. That is a security finding, it is already recorded as INT-3, and it is handled on that track rather than here. This card describes the architecture only.
ENG-02 — Channel-strategy policy (push-first, email/SMS by exception)
Flags: PRK · Status: Requirement only · Feasibility: not a build
What the unified app has to deliver
Nothing. This item produces a written policy — when a message goes by push, when it falls back to email, when SMS is warranted — and no code.
It is the same shape as FAM-08 on PAM-156: a title that reads like a feature and resolves to a decision. Giving it a feasibility verdict would be manufacturing one.
What blocks it
Its own premise. "Push-first, email/SMS by exception" presumes three working channels. Email works. Push works, on two incompatible stacks. SMS has never been observed sending anything (COMM-01). A policy arbitrating between three channels cannot be written until it is known how many there are.
It also cannot be written before ENG-03: a channel strategy that users can override needs a per-channel preference model, and PADI's does not have a channel dimension at all.
Sequence it after SCOPE-43 and ENG-03, not before.
ENG-03 — Push notification preferences / granular opt-in
Flags: PRK · Status: Gap on the PADI realm · Existing on Adventures · Feasibility: the model exists, on the wrong side of an auth boundary
What the unified app has to deliver
Per-notification-type opt-in, so a diver can take booking alerts and decline promotions.
What it can rely on
PADI's preference centre is topic-only. There is no channel dimension. The same API is called identically from three web front ends — PadiWW/account.padi.com src/api/auth/index.js:3,35-57, PadiWW/learning.padi.com src/store/modules/auth/actions.js:259,289,317 and PadiWW/pro.padi.com src/store/modules/auth/actions.js:274,310,335-337 — and the payload is a flat set of booleans: padiCommunications, environmentConservation, diveAbroad, events, localDiving, diveCentricTrips, thirdParty, communicationLanguage.
Every one is an interest topic, and every label reads "email me…". There is no channel field anywhere in any payload. A second endpoint handles Salesforce recipient topics with numeric SFMC preference IDs (PadiWW/pro.padi.com src/components/preferences/PreferencesForms.vue:158-166), and it is topic-keyed too.
Adventures has exactly the model this item describes. diviac/travel-rn app/components/form/NotificationsPreferencesForm.js:200 posts to /account/preferences/notification/, and the form is a 5 × 3 matrix of booleans named preferences.{category}{Channel}:
| Category | Description in the UI | Channels |
|---|---|---|
accountNotifications |
bookings, vouchers and cancellations | Email · Sms · Push |
reminders |
cart activity, payment, reviews, referrals | Email · Sms · Push |
divingOpportunities |
diving near you when you travel | Email · Sms · Push |
adventuresPromotionsUpdates |
— | Email · Sms · Push |
travelInspirationalNews |
— | Email · Sms · Push |
What it has to build
This is not re-plumbing an existing per-channel field — PADI has none. The work is a new preference model on the PADI realm, with Adventures as a proven reference design rather than a reusable API.
Three constraints carry over from what exists:
- Neither PADI app reads or writes a preference natively. Android's only route is a jump page that opens the web preference centre in an external browser —
padiww/android-padi-appapp/src/main/java/com/duns/padiapp/domain/BaseAppSettingsIpm.kt:252-256, reached from the delete-account flow atcompose/deleteaccount/DeleteAccountScreen.kt:45-51. - Some communications are non-optional and that is encoded in the UI, not the data.
PadiWW/pro.padi.comsrc/components/preferences/ProPreferences.vue:8-24renders training bulletins, quality-management notices and renewal notifications as statically checked items with opt-out unavailable. The transactional-versus-marketing split exists only as hard-coded markup. - Consent and preference are the same payload on web.
thirdParty— consent to share data with partners — sits beside marketing topics in one flat object (src/components/preferences/DiverPreferences.vue:69-92). Mobile keeps legal consent architecturally separate (p/personalization/api/Personalization/attributes/{key}, China PIPL). Whichever model the unified app adopts inherits one of these two positions, which bears directly onMYP-09andPREF-03.
What blocks it
SCOPE-44 — does the unified app adopt the Adventures model and its realm, or does PADI extend the preference centre with a channel dimension? And SCOPE-45 — is the transactional/marketing split moved into the data model, where it belongs, rather than being re-hard-coded in a second client?
ENG-04 — Trip / booking / next-dive reminders
Flags: Now · Status: Delivery exists — Gap is the trigger · Feasibility: narrower than it first appears
What the unified app has to deliver
A reminder before a trip, a booking, or a next dive.
What it can rely on — more than the PADI apps suggest
Read only the PADI apps and this looks like a total gap. Adventures shows otherwise: the entire delivery chain already exists and runs in production.
- Push transport — FCM (
diviac/travel-rnapp/containers/App.js:59-84) - A device registry —
POST account/devices/(app/containers/AuthWrapper.js:34-47,93) - A notification channel named for the purpose — "PADI Bookings notifications" (
app/containers/Configure.js:13-19) - A Reminders preference category the diver can toggle per channel (
app/components/form/NotificationsPreferencesForm.js:38-43,145-147) - A persistent message store the app reads back —
GET account/messages/(app/screens/NotificationsScreen.js:83-84)
So a PADI-owned app already sends reminders to registered devices, honours a per-channel preference, and keeps a record. This item is not starting from zero.
What it has to build — and this is the real gap
Nothing schedules anything, on any surface. Searched across all three mobile codebases:
- No
UNCalendarNotificationTriggerorUNTimeIntervalNotificationTriggerinpadiww/ios-padi-app - No
AlarmManager, and no WorkManager job that posts a notification, inpadiww/android-padi-app— the existing workers do data sync only - No
localNotificationScheduleindiviac/travel-rneither. Its onelocalNotificationcall renders a foreground FCM message (app/containers/App.js:99-107); it does not schedule a future one.
And the triggers that do exist are event-driven, not date-driven. Adventures' two booking-related categories describe "bookings, vouchers and cancellations" and "cart activity, payment, reviews, referrals" — these fire when something happens. A reminder fires because a date is approaching, which nothing observed does.
So the valid gap is precise: not delivery, not preferences, not the registry — the scheduler and the date-proximity trigger. Two ways to close it, and they are very different items:
| Approach | Where it runs | What it needs |
|---|---|---|
| Local, from cached data | The device | Nothing from PADI. The app already holds trips and dives; schedule a local notification against the stored date. Works offline. |
| Server-scheduled campaign | PADI | A service that knows every diver's trip and dive dates and emits on a schedule. Not observed anywhere. |
13-post-q1-certifications-ecard.md reached exactly this fork for certification-expiry reminders and recorded the same conclusion: local-notification-from-a-cached-date is the cheap form, buildable with nothing new from PADI. The same answer applies here and should be offered explicitly.
What blocks it
SCOPE-46 — local or server-scheduled? The local form is cheap, offline-capable and unblocked; the server form is a platform PADI does not have.
One dependency travels with the trip half: booking data is TRV-04, on the Travel realm, inside the deferred PAM-158.
ENG-05 — Push notifications for Club membership users
Flags: PRK · Status: Gap — mobile · Feasibility: blocked on the same platform question as ENG-01, and unverifiable on PADI's side
What the unified app has to deliver
Club-member-targeted push — renewal notices being the obvious case.
What it can rely on
The audience data is served. Club membership status comes from GET /c/club/member/subscription, analysed in full as MYP-05 in Logged In Experience, which also records renewalStatus and autoRenewEnabled as available, and the target-state reference adds GET /pros/renewals/info. Knowing who to notify, and when a renewal is due, is not the problem.
What it has to build
Everything between knowing and telling.
Club renewal push is buildable only if interactive notifications and device-token registration exist. On the PADI realm neither does — ENG-01 establishes there is no PADI device registry and no interactive notification anywhere in the PADI apps. So this item inherits ENG-01's blocker entirely and adds nothing it can solve on its own.
There is also no Club-specific notification channel, topic or category anywhere. All PADI-app push arrives on one generic channel. Segmenting by membership is not a client capability; it is a campaign-tool capability.
And the campaign tool cannot be inspected. Targeting would be configured in Salesforce Marketing Cloud, and Axelerant has no SFMC access. So the segment structure, the available audience attributes and whether membership status is even synced into SFMC are all unverifiable from here. This card can state what the app would need; it cannot state what SFMC already holds.
What blocks it
PADI-32 (the push platform decision) first — nothing here moves before it. Then PADI-33: is Club membership status available as a segmentation attribute in Marketing Cloud, and can Axelerant see the configuration? Without an answer, any estimate covers the app's share only and silently omits the part that actually sends the message.
ENG-07 — Dive buddy notifications
Flags: PRK · Status: Gap — the data model comes first · Feasibility: far larger than the title implies
What the unified app has to deliver
Notifying a diver about something a buddy did.
What it can rely on
Almost nothing, because a buddy is not an entity. On both platforms a buddy is a single free-text string on a dive-log entry:
padiww/ios-padi-appPADI/model/view/LogbookExperience.swift:44-57—buddiesdecoded as a string, exposed bygetBuddies() -> String?, entered through one text field (PADI/feature/logbook/experience/view/ExperienceViewController.swift:20,28)padiww/android-padi-appapp/src/main/java/com/duns/padiapp/compose/logbook/createlogbook/CreateLogbookViewModel.kt:216—buddy: String? = "", with a matching single field inExperienceScreen.kt:41-134
There is no buddy list, no connection or friend model, no identity linkage between a buddy name and a PADI account — searched across both PADI apps, diviac/travel-rn, padiww/padi-aware-flutter and every POC. PadiWW/padi.mobile.app src/features/dives/types.ts:4 mentions a buddy only in a comment deferring the real model to PAM-51.
A typed name is not a person. There is nobody to notify.
What it has to build
A social graph: buddy identity resolution, an invitation or confirmation flow, a relationship store, privacy rules about what a buddy may see, and only then a notification. The notification is the last and smallest part of this item, and estimating it as a notification feature would understate it by an order of magnitude.
What blocks it
SCOPE-47 — is ENG-07 in scope as a social feature, or only as notifications over a buddy model someone else delivers? And the lane disagreement is sharpest here: the artefact places it in Q1 2027 while PAM-50 sits at Later - Release 2027. The backlog looks right.
The in-app inbox — ENG-06, which does not exist
The artefact's missing item has two working implementations in the estate, plus a third that is switched off. This is the cheapest surprise in the card.
| Implementation | Where | Evidence |
|---|---|---|
| AWS AppSync GraphQL — query, mark-read, delete, live subscriptions | PadiWW/learning.padi.com, PadiWW/pro.padi.com |
src/components/notification/GlobalNotification.vue, src/components/notification/Drawer.vue; schema duplicated at PadiWW/pro.padi.com src/graphql/header/index.js:3-49 |
| REST message list, paginated | diviac/travel-rn |
app/screens/NotificationsScreen.js:83-84 — GET account/messages/ |
| Salesforce MobilePush inbox — deliberately disabled | padiww/ios-padi-app |
PADI/data/repository/impl/PushNotificationHandlerImpl.swift:32 — let inbox = false, applied at :47 |
So PAM-49 is a new client over an existing service, not a new backend — and there is a real design choice between PADI's AppSync store, the Adventures REST store, and re-enabling the vendor's.
That choice should be made deliberately, because it is the same choice ENG-01 faces about push transport, and answering them separately would produce an app whose notifications and notification history live in different systems.
Summary
| Item | Status | Feasibility |
|---|---|---|
COMM-01 Channels other than email |
Existing — mobile (Adventures) | Push yes; SMS stored but never observed sending |
ENG-01 Push notifications (interactive) |
Existing — mobile, two routes | Delivery solved; interactive unbuilt; registry is realm-bound |
ENG-02 Channel-strategy policy |
Requirement only | Not a build. Blocked by its own premise |
ENG-03 Granular opt-in |
Gap (PADI) · Existing (Adventures) | PADI's model has no channel dimension at all |
ENG-04 Trip / booking / next-dive reminders |
Delivery exists · Gap is the trigger | Nothing schedules; local form is cheap and unblocked |
ENG-05 Club-member push |
Gap — mobile | Inherits ENG-01; SFMC configuration unverifiable |
ENG-07 Dive buddy notifications |
Gap — data model first | Buddy is a text field; the social graph is the item |
Counted as distinct capabilities: 6. ENG-05 has no independent build — it is ENG-01 plus a targeting rule that lives in the campaign tool. Estimating it separately double-counts ENG-01.
The headline for estimation. Two of the seven are flagged Now. One of those two (ENG-01) cannot be estimated until PADI chooses a push platform, and the other (ENG-04) is cheap in its local form and a platform build in its server form. Neither confirmed item has a single number.
And reading only the PADI apps would have been wrong five times. Adventures has push, a device registry, an inbox and a full per-channel preference model. It is the most engagement-complete app PADI owns, and it is the one this study had least reason to open.
What has to be answered
PADI owns these:
PADI-32— Which push platform does the unified app use: Salesforce Marketing Cloud (the PADI apps' stack, no PADI-side registry), Firebase with the Adventuresaccount/devices/registry, or a first-party device registry PADI builds? BlocksENG-01andENG-05, and nothing in either moves before it.PADI-33— Is Club membership status available as a segmentation attribute in Marketing Cloud, and can Axelerant see the configuration? Without SFMC access the sending half ofENG-05cannot be verified at all.
Scope owns these:
SCOPE-43— Which channels does PADI commit to platform-wide? SMS is offered in the Adventures UI and sends nothing anywhere.ENG-02cannot be written until this is answered.SCOPE-44— Does the app adopt the Adventures preference model and its Travel realm, or does PADI add a channel dimension to its own preference centre?SCOPE-45— Is the transactional-versus-marketing split moved into the preference data model, instead of being hard-coded in the UI a second time?SCOPE-46— Are reminders local (cheap, offline, nothing from PADI) or server-scheduled (a platform that does not exist)?SCOPE-47— IsENG-07in scope as a social feature, or only as notifications over a buddy model delivered elsewhere?SCOPE-48— Which inbox doesPAM-49build on: PADI's AppSync store, the Adventures REST store, or Salesforce's disabled one? Answer it together withPADI-32.
And three that are bookkeeping, not engineering
- The artefact promises an in-app inbox and has no item for it.
PAM-49is funded for Q1 and appears in no feasibility sub-task. Either addENG-06or accept that the artefact is not the scope of record. - Three items disagree with the backlog, in both directions —
ENG-07(artefact Q1 / Jira Later),ENG-08andENG-10(artefact post-Q1 / Jira Q1). PAM-66Cert-renewal reminders sits under the Certifications epic, not Engagement, while being the same capability asENG-04's server-scheduled form.