Skip to content

Track marketing notification opens - #6067

Open
peachbits wants to merge 3 commits into
developfrom
matthew/marketing-push-tracking
Open

Track marketing notification opens#6067
peachbits wants to merge 3 commits into
developfrom
matthew/marketing-push-tracking

Conversation

@peachbits

@peachbits peachbits commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

CHANGELOG

Does this branch warrant an entry to the CHANGELOG?

  • Yes
  • No

Dependencies

none

Requirements

If you have made any visual changes to the GUI. Make sure you have:

  • Tested on iOS device
  • Tested on Android device
  • Tested on small-screen device (iPod Touch)
  • Tested on large-screen device (tablet)

No visual changes — this is push-payload parsing plus navigation/analytics, no new UI.

Description

Recognizes a marketing push notification and, when the user opens the app from it, reports the campaign open to analytics and navigates to an optional deep link.

  • parsePushMessage now recognizes a { type: 'marketing', campaignId, url? } FCM data payload and returns a new MarketingLink deep-link type. An unrecognized or malformed url degrades to track-only.
  • On open (background tap or cold start), the deep-link handler dispatches a Marketing_Notification_Opened PostHog event carrying the campaignId, then navigates to the optional deep link through the existing handler.
  • Adds a PushMessageParser unit test.

Verified with the unit test and end-to-end on a real Android device against a local push server: the open was tracked in PostHog and the deep link navigated.

The sending side lives in edge-push-server (companion PR EdgeApp/edge-push-server#78). This PR is the receiving side and is safe to merge on its own — it only acts on notifications that carry the marketing payload.


Note

Medium Risk
Push URLs can drive navigation for all campaign recipients; the allow-list mitigates operator error or compromised send keys, but expanding destinations or parser gaps could still route users to sensitive flows.

Overview
Marketing push notifications (type: marketing with campaignId and optional url) are now recognized when the user opens the app from the notification.

parsePushMessage returns a new MarketingLink deep-link type. Optional URLs are parsed through the existing deep-link parser; only allow-listed campaign destinations (home, wallets, earn, markets, swap, buy/sell quotes, promotions, fund-account modal) trigger navigation—sensitive or unlisted targets (recovery, edge-login, arbitrary scenes, provider returns) are track-only. Wrapped deep.edge.app URLs are judged by their inner destination; malformed URLs still record the open.

On handle, DeepLinkingActions logs Marketing_Notification_Opened to analytics with campaignId, then navigates via the shared link handler when a permitted link was parsed. PushMessageParser.test.ts covers destinations, refusals, attribution wrappers, and existing price-change payloads.

Reviewed by Cursor Bugbot for commit 1cdb570. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread src/util/PushMessageParser.ts Outdated
Recognize a marketing push payload (type 'marketing' with a campaignId and
optional deep-link url) in parsePushMessage. When the user opens the app from
one, report a Marketing_Notification_Opened event carrying the campaignId to
analytics, and navigate to the deep link when present. Unrecognized or
malformed urls degrade to track-only. Includes a parser unit test.
@peachbits
peachbits force-pushed the matthew/marketing-push-tracking branch from b4eafc8 to 5d8b895 Compare August 14, 2026 17:40
Comment thread src/util/PushMessageParser.ts
Comment thread src/util/PushMessageParser.ts Outdated
@peachbits
peachbits force-pushed the matthew/marketing-push-tracking branch from f149ce7 to b8a1392 Compare August 14, 2026 18:56
Comment thread src/util/PushMessageParser.ts Outdated
Comment thread src/util/PushMessageParser.ts Outdated
@peachbits
peachbits force-pushed the matthew/marketing-push-tracking branch from b8a1392 to ff38b7b Compare August 14, 2026 22:15
const MARKETING_SCENES: ReadonlySet<string> = new Set([
'edgeTabs',
'walletsTab'
])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scene allow-list ignores nested screens

Medium Severity

isAllowedMarketingLink only checks a scene's sceneName, then handleLink passes the full query into navigate. An allowed edge://scene/edgeTabs URL can therefore set screen to devTab or extraTab. Those tabs are hidden in the menu (ENV.DEV_TAB / missing extraTab config) but remain registered, so a campaign can open the production debug scene or mount ExtraTabScene, which throws when config.extraTab is missing.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ff38b7b. Configure here.

@peachbits
peachbits force-pushed the matthew/marketing-push-tracking branch from ff38b7b to 83177c4 Compare August 14, 2026 22:34

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 83177c4. Configure here.

Comment thread src/util/PushMessageParser.ts
A campaign URL exists to take the user to the place a campaign is
talking about, so the filter names those places: the tab bar and its
tabs, the wallet list, swap, the buy/sell quote scenes, earn, markets,
promo-code activation, and the fund-account prompt. A URL pointing
anywhere else — account actions like password recovery or edge login,
payment flows, provider returns, scenes nobody listed — tracks the open
without navigating.

An allow-list fails closed: a new link type or scene stays unreachable
from a push until someone adds it here, which is the right default for
a URL that reaches every recipient of a campaign at once. Sends are
already gated behind a marketer API key, so this guards operator error
and key compromise, not outside attackers.

A deep.edge.app attribution wrapper is judged by the destination it
carries, so wrapping cannot smuggle a refused link through, and a
campaign that asks for attribution keeps it.
@peachbits
peachbits force-pushed the matthew/marketing-push-tracking branch from 83177c4 to 1cdb570 Compare August 14, 2026 22:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant