From 8c4adad1b9af565acbdf2ea9f0ab2fc7ac08bc2a Mon Sep 17 00:00:00 2001 From: Alek99 Date: Fri, 29 May 2026 17:09:33 -0700 Subject: [PATCH] Use stable PostHog identity for newsletter signups --- .../blocks/telemetry/__init__.py | 3 +- .../blocks/telemetry/posthog.py | 102 ++++++++++++++---- .../src/reflex_site_shared/backend/signup.py | 4 + 3 files changed, 86 insertions(+), 23 deletions(-) diff --git a/packages/reflex-components-internal/src/reflex_components_internal/blocks/telemetry/__init__.py b/packages/reflex-components-internal/src/reflex_components_internal/blocks/telemetry/__init__.py index 0d56050939f..c29215579fd 100644 --- a/packages/reflex-components-internal/src/reflex_components_internal/blocks/telemetry/__init__.py +++ b/packages/reflex-components-internal/src/reflex_components_internal/blocks/telemetry/__init__.py @@ -5,7 +5,7 @@ from .default import get_default_telemetry_script from .google import get_google_analytics_trackers, gtag_report_conversion from .koala import get_koala_trackers -from .posthog import get_posthog_trackers +from .posthog import get_posthog_trackers, track_newsletter_posthog_subscription from .rb2b import get_rb2b_trackers from .unify import get_unify_trackers @@ -20,4 +20,5 @@ "get_unify_trackers", "gtag_report_conversion", "identify_common_room_user", + "track_newsletter_posthog_subscription", ] diff --git a/packages/reflex-components-internal/src/reflex_components_internal/blocks/telemetry/posthog.py b/packages/reflex-components-internal/src/reflex_components_internal/blocks/telemetry/posthog.py index 0a0bc7c67fa..07b56a0750e 100644 --- a/packages/reflex-components-internal/src/reflex_components_internal/blocks/telemetry/posthog.py +++ b/packages/reflex-components-internal/src/reflex_components_internal/blocks/telemetry/posthog.py @@ -43,12 +43,57 @@ def identify_posthog_user(user_id: str) -> rx.event.EventSpec: ) +def _capture_posthog_person_event( + event_name: str, + props: dict[str, Any], + person_keys: set[str], +) -> rx.event.EventSpec: + """Capture an event while keeping PII out of distinct_id. + + Returns: + Event that runs PostHog capture in the browser. + """ + props_json = json.dumps(props) + person_keys_json = json.dumps(sorted(person_keys)) + + return rx.call_script( + f""" + if (typeof posthog !== 'undefined') {{ + const props = {props_json}; + const personKeys = new Set({person_keys_json}); + const personProps = Object.fromEntries( + Object.entries(props).filter( + ([key, value]) => personKeys.has(key) && value !== undefined && value !== null && value !== '' + ) + ); + const canonicalDistinctId = props.user_uuid || props.user_id || props.contact_uuid || props.contact_id || props.distinct_id; + const eventProps = {{...props}}; + delete eventProps.user_uuid; + delete eventProps.user_id; + delete eventProps.contact_uuid; + delete eventProps.contact_id; + delete eventProps.distinct_id; + for (const key of personKeys) {{ + delete eventProps[key]; + }} + if (Object.keys(personProps).length > 0) {{ + eventProps.$set = personProps; + }} + if (canonicalDistinctId && canonicalDistinctId !== props.email) {{ + posthog.identify(String(canonicalDistinctId), personProps); + }} + posthog.capture('{event_name}', eventProps); + }} + """ + ) + + def _track_form_posthog( event_name: str, form_data: dict[str, Any], allowed_keys: set[str], ) -> rx.event.EventSpec: - """Identify the submitter and capture a form event in PostHog. + """Capture a form event in PostHog with person properties. Args: event_name: PostHog event name to capture. @@ -56,27 +101,10 @@ def _track_form_posthog( allowed_keys: Set of keys to include from form_data. Returns: - Event that runs PostHog identify and capture in the browser. + Event that runs PostHog capture in the browser. """ filtered = {k: v for k, v in form_data.items() if k in allowed_keys} - props_json = json.dumps(filtered) - - return rx.call_script( - f""" - if (typeof posthog !== 'undefined') {{ - const props = {props_json}; - const distinctId = props.email || ('anon_' + String(Date.now())); - posthog.identify(distinctId, {{ - email: props.email, - first_name: props.first_name, - last_name: props.last_name, - job_title: props.job_title, - company_name: props.company_name, - }}); - posthog.capture('{event_name}', props); - }} - """ - ) + return _capture_posthog_person_event(event_name, filtered, _PERSON_KEYS) _COMMON_KEYS = { @@ -85,6 +113,11 @@ def _track_form_posthog( "last_name", "job_title", "company_name", + "user_uuid", + "user_id", + "contact_uuid", + "contact_id", + "distinct_id", "number_of_employees", "how_did_you_hear_about_us", "interested_in", @@ -92,12 +125,20 @@ def _track_form_posthog( "technical_level", } +_PERSON_KEYS = { + "email", + "first_name", + "last_name", + "job_title", + "company_name", +} + def track_demo_form_posthog_submission(form_data: dict[str, Any]) -> rx.event.EventSpec: """Capture a demo_request event in PostHog. Returns: - Event that runs PostHog identify and capture in the browser. + Event that runs PostHog capture in the browser. """ return _track_form_posthog("demo_request", form_data, _COMMON_KEYS) @@ -108,13 +149,30 @@ def track_intro_form_posthog_submission( """Capture an intro_submit event in PostHog. Returns: - Event that runs PostHog identify and capture in the browser. + Event that runs PostHog capture in the browser. """ return _track_form_posthog( "intro_submit", form_data, _COMMON_KEYS | {"phone_number"} ) +def track_newsletter_posthog_subscription( + email: str | None, + contact_uuid: str | None = None, +) -> rx.event.EventSpec: + """Capture a newsletter subscription without using email as distinct_id. + + Returns: + Event that runs PostHog capture in the browser. + """ + props = {} + if email: + props["email"] = email + if contact_uuid: + props["contact_uuid"] = contact_uuid + return _capture_posthog_person_event("newsletter_subscribed", props, {"email"}) + + def get_posthog_trackers( project_id: str, api_host: str = POSTHOG_API_HOST, diff --git a/packages/reflex-site-shared/src/reflex_site_shared/backend/signup.py b/packages/reflex-site-shared/src/reflex_site_shared/backend/signup.py index 113690b58bf..485d394d105 100644 --- a/packages/reflex-site-shared/src/reflex_site_shared/backend/signup.py +++ b/packages/reflex-site-shared/src/reflex_site_shared/backend/signup.py @@ -6,6 +6,9 @@ import httpx from email_validator import EmailNotValidError, ValidatedEmail, validate_email +from reflex_components_internal.blocks.telemetry import ( + track_newsletter_posthog_subscription, +) import reflex as rx from reflex_site_shared.constants import ( @@ -102,6 +105,7 @@ async def signup( }, ) return + yield track_newsletter_posthog_subscription(email) yield IndexState.send_contact_to_webhook(email) yield IndexState.add_contact_to_loops(email) async with self: