Allin — internal
Enter the password to continue.
Allin — Data, CRM & Comms

Allin — data, CRM & comms

Everything touching Segment → Amplitude / Customer.io / Meta, plus RevenueCat and Anthropic as data processors — re-checked tonight against the actual live code, not the intended design or the previous version of this page. Three previously-undocumented gaps turned up that way, two of them already fixed. Sorted by how much each actually matters, not by when it was found.

How this list was built

So you can judge whether it's actually complete, not just trust it
Every claim below was re-verified, not carried over: git branch -a plus a diff against main on both repos (iOS branch fix/apple-signin-email-scope; the backend has 7 open branches touching consent, erasure, export, and pseudonymization), and a direct read of every consent-adjacent surface — Allin/Home/SettingsView.swift, ConsentSheet.swift, AnalyticsClient.swift, MetaAdsClient.swift, and every backend function that talks to Segment (recommend-plan, revenuecat-webhook, delete-user-data). One claim — "Meta advertising is properly gated" — got checked against the actual linked FBSDKCoreKit SDK source (a real local checkout under Xcode's DerivedData, not the docs or this app's own comment asserting it), because an unverified claim about consent gating is exactly the kind of thing that's easy to assert once and never check again. That check is what surfaced the finding below. Three real, previously-undocumented things came out of this pass — two already fixed, one already resolved on a branch: (1) two backend functions were forwarding real analytics events — for one of them, the user's actual check-in areas and 1–10 ratings — to Segment with zero consent check, because they run entirely server-side, outside the app's own consent gate; (2) Meta was receiving real purchase and registration events regardless of advertising consent, because the setting this app's own code (and this page) claimed gated that only gates Meta's automatic events, not the explicit ones actually being sent; (3) the recommend-plan repo/live drift flagged last time as an active, unresolved risk has, since, actually been fixed and committed — it just hasn't been pushed. Terms like "edge function," "migration," "committed vs. deployed" are the same vocabulary as the GDPR page's glossary — not repeated here.
How to read this: Each item below is either something already true and verified (green, top section) or something that needs a decision or an action (High/Medium/Low). "High" means a real legal exposure or a silently-broken mechanism right now. "Low" means genuinely fine to leave for later, no live risk in the meantime.

Already confirmed done

Amplitude no longer receives your email

Done — live
The Segment mapping that sent it as a User Property was removed and re-verified directly against the app's analytics code, not just the config screen. Live since 2026-08-16 — the only item on this whole page confirmed live in production, not just committed.

Customer.io is live and working

Done
Site ID + track API key confirmed by a real API call (workspace 216147, EU region).

Segment plan tier confirmed — plus a new caveat worth knowing

DoneSource: Segment dashboard + delete-user-data's own code
Free tier, 101 of 1,000 monthly tracked users (Aug 5–Sep 5 window) — as previously confirmed; not re-checked live tonight. New, found while reading delete-user-data's own code comments rather than assumed: Segment's user-deletion/suppression API ("Regulations," the one that would actually erase a person's record inside Segment itself) requires a Team/Business-tier workspace, not Free. So even once the missing SEGMENT_ACCESS_TOKEN/SEGMENT_SOURCE_ID are resolved, Segment-side erasure may still not work on the current plan — worth knowing before spending more effort chasing the token alone. Not urgent: Amplitude and Customer.io erasure already work independently of this (see the medium-priority item below).

Apple's DPA is already covered

Done
Governed by the Apple Developer Program License Agreement, already accepted. Nothing to do here.

Subscriptions are tracked — and also feed Segment server-side

DoneSource: supabase/functions/revenuecat-webhook/index.ts
user_entitlements table live, linked to RevenueCat via revenuecat_app_user_id, updated by the RevenueCat webhook — as before. Newly confirmed by reading the actual webhook code (not assumed from the table existing): it also forwards every subscription lifecycle event — started, renewed, cancelled, billing issue, expired — to Segment → Amplitude/Customer.io itself, server-side, on RevenueCat's own schedule. That's a separate, direct integration from anything the iOS app's AnalyticsClient sends from the device — see the consent-gate finding below for why that distinction mattered.

Confirmed, not assumed: no email/name/account id is sent to Anthropic

Done
Checked the exact code that builds every request to Anthropic (classify-affect, classify-crisis, recommend-plan) — only check-in/intake free text goes across, nothing identifying.

High priority

real exposure or a silently broken mechanism

Two backend functions sent real analytics with zero consent check — now gated

recommend-plan + revenuecat-webhook, entirely server-side, entirely missed by the app's own consent gate
Claude found + built it, Marie deploys
What was actually happening
recommend-plan forwards a plan_block_generated Segment event and a Customer.io identify() call carrying the user's real selected areas and 1–10 ratings — literally which belief areas they're least satisfied with — every time a plan is generated. revenuecat-webhook forwards every subscription lifecycle event the same way. Both fire completely unconditionally: no check against the user's analytics-consent choice, ever, at either site.
Why it slipped through
AnalyticsClient's consent gate (hasAnalyticsConsent, checked before .configure()/.identify()/.track()) only covers events the iOS app decides to send from the device. Both of these run server-side — one triggered by an HTTP call, one by RevenueCat's own webhook — so there was never a client-side check to bypass; nothing stood in for it on the server either.
What was built
A shared _shared/consent.ts helper that looks up the most recent analytics row in user_consents (the same audit-trail table from the consent-RPC fix below) for a given user id, and gates both call sites on it. Fails closed on purpose: no row on record, the migration not deployed yet, or a lookup error all resolve to "don't send" — matching what GDPR actually requires (affirmative consent, not silence read as yes).
Status
Built and committed — backend branch fix/server-side-analytics-consent-gate (merges combined-fixes-2026-08-15 + feat/gdpr-consent-record). Depends on the user_consents table actually being live — until then this just means the events stop firing, not that anything errors.
Open question — decided
Every existing user has zero rows in user_consents (it didn't exist until this table shipped), so the moment this gate deploys, subscription/plan analytics for every current user goes quiet in Amplitude/Customer.io until they revisit the consent screen or Settings → Privacy. Decided: no silent backfill. Instead, a specific push/email campaign will ask existing users to actively opt in — see the new to-do card just below. That's the GDPR-cleaner choice (real, provable consent going forward, not an assumed "yes" for people who were never shown the new granular screen), at the cost of a real gap in Amplitude/Customer.io continuity for anyone who doesn't respond to the campaign.
See it
allin-backend · branch fix/server-side-analytics-consent-gate · supabase/functions/_shared/consent.ts
supabase/functions/recommend-plan/index.ts · supabase/functions/revenuecat-webhook/index.ts

New to-do: a specific campaign to get existing users to opt in

Resolves the "silent analytics gap" above with real consent instead of a backfill
Isabel + Marie — copy needs both before it sends
Why this exists
Once the server-side consent gate above deploys, every pre-existing user (everyone who signed up before the granular consent screen shipped, and hasn't since opened Settings → Privacy) has no consent row on record and goes quiet in Amplitude/Customer.io by default — fail-closed, on purpose. The fix isn't to assume a default; it's to actually ask.
What this needs
One Customer.io send (email, and/or a push if the person already has notifications on) to existing users, pointing them at Settings → Privacy to make a real choice. Depends on the server-side consent gate + user_consents table actually being live first — sending it before that deploys would ask people to opt into something that isn't wired up yet.
What I haven't done
Not drafting the send copy here — this is a real Customer.io campaign to real users, and copy needs Isabel + Marie sign-off before anything goes out, per the standing rule. Flagging the to-do now, as asked; happy to draft copy options next if that's wanted.
Status
Not started. Sequencing: deploy user_consents + the server-side gate first, then this campaign, ideally before or alongside the same release that ships the gate — otherwise the "gone quiet" window is longer than it needs to be.

Push device tokens almost certainly never reach Customer.io — a timing race, not a guess

Very likely the exact issue you remembered — found in the actual code and Segment/Customer.io's own docs, not assumed
Marie — pick a fix, both need your call
What's actually happening
Customer.io's Segment integration only picks up a push device token from context.device.token when it's already set on one of three specific events — Application Opened, Application Installed, Application Uninstalled — per Customer.io's own integration docs. Segment's iOS SDK only sets that field once registeredForRemoteNotifications(deviceToken:) is called, which is what AnalyticsClient.registerDeviceToken() does.
Why it's a real race, not theoretical
In AllinApp.swift, AnalyticsClient.shared.configure() starts the Segment SDK — which fires Application Opened automatically, essentially at launch, via trackApplicationLifecycleEvents(true). In AppDelegate.swift, registerForRemoteNotifications() requires a network round-trip to Apple's APNs servers before didRegisterForRemoteNotificationsWithDeviceToken fires and the token becomes known — structurally slower than a same-run-loop-turn event. The two paths are independent Tasks with no ordering between them. On a cold launch, the odds strongly favor Application Opened firing — and being sent to Customer.io — before the token exists to attach to it. This isn't first-install-only: it's every cold launch, since Application Opened fires on each one.
Confirmed there's no fallback path
Checked Package.resolved and the Xcode project directly — only analytics-swift (Segment), purchases-ios-spm (RevenueCat), facebook-ios-sdk, and supabase-swift are linked. No Customer.io native SDK exists in this app at all, so the Segment-context mechanism above is the only path a device token has to reach Customer.io — and it's the one racing.
Two real fixes — genuine trade-offs, not built yet
1) Add Customer.io's own iOS SDK (its Messaging Push module) alongside Segment, and call its dedicated device-token method directly from AppDelegate — this is what Customer.io's own docs recommend for exactly this setup. Fully decoupled from Segment's timing: the token reaches Customer.io the moment APNs returns it, launch-lifecycle-event timing stops mattering. Cost: a new SPM package added in Xcode (a build change — needs you, not me), a new "Push API Key" from the Customer.io dashboard (different credential from the Track API key already in Supabase), and one real test push to confirm delivery.
2) Stay Segment-only, and re-fire a signal once the token is known. Cheaper, no new dependency — but the safe version of this means re-sending the literal Application Opened event name (since that's what Customer.io's mapping is documented to trigger on), which also reaches Amplitude by default and would double-count that event for the session. analytics-swift 1.9.3 doesn't expose a simple per-call "send to this destination only" flag; there's an enrichment-closure mechanism that could suppress it for Amplitude specifically, but I haven't built or tested that, and getting it wrong quietly corrupts an Amplitude funnel metric — the same class of mistake this whole page exists to catch.
What I didn't do
Didn't pick one and ship it — one changes the Xcode build, the other risks a different real regression in Amplitude. Both need your call, not mine.
One more thing worth 2 minutes in the Segment dashboard
The exact trigger condition wired up on Customer.io's (Actions) destination for this source lives in Segment's web UI, which I have no credentials to inspect (same access gap as the missing SEGMENT_ACCESS_TOKEN, already known). Customer.io (Actions) destination → mappings → confirm what's actually configured, rather than relying on Customer.io's public docs for what's typical.
See it
Allin/Core/AnalyticsClient.swift (registerDeviceToken, configure) · Allin/Core/AppDelegate.swift · Allin/Core/AllinApp.swift · Allin.xcodeproj/project.pbxproj + Package.resolved (no CustomerIO SDK linked)

Email to Customer.io — code path unaffected by the Amplitude fix, one live check still worth doing

Marie — 2-minute check
The Amplitude email fix (Done section above) was made entirely as a Segment-dashboard mapping that drops email specifically for the Amplitude destination — not a change to AnalyticsClient.identify() itself, which still sends userId + email in one shared call feeding every destination. Customer.io's copy of that same call is untouched by that fix and should be unaffected — that's as far as this can be confirmed from the code. Whether Customer.io's own destination mapping is actually configured to consume the email trait is a Segment-dashboard setting, same access gap as above. If a specific past incident is what you're remembering, it's worth two minutes in Segment: Customer.io (Actions) destination → Identify mapping → confirm email maps to Customer.io's email field, then check one real test user actually shows up as a person in Customer.io with that email attached.

Meta was receiving real events regardless of advertising consent — now fixed

This page (and the code's own comments) previously overstated how gated this was
Claude found + built it, Marie deploys
What was actually happening
MetaAdsClient's enable/disableTrackingSettings() toggle two SDK settings (isAutoLogAppEventsEnabled, isAdvertiserIDCollectionEnabled) based on the user's advertising-consent choice — that part was real and correct. But trackPurchase() and trackCompleteRegistration() — called unconditionally, no consent check at either call site (SubscriptionManager.swift on every successful subscribe, AuthenticationManager.swift on every Apple sign-in) — call Meta's SDK through a different path (AppEvents.shared.logPurchase / .logEvent) that those two settings don't actually gate at all.
How this was checked, not assumed
Read the actual linked FBSDKCoreKit source directly (a local checkout under DerivedData, not the docs or this app's own comment): isAutoLogAppEventsEnabled only gates logInternalEvent — the SDK's own automatic events. The explicit logEvent/logPurchase path (doLogEvent:) has no consent-style check in it at all, only a remote kill-switch. So a user who explicitly declined advertising consent was still generating a real Purchase event — actual amount and currency — and a CompleteRegistration event inside Meta's system.
What was built
Moved the consent check inside MetaAdsClient itself — one hasConsent gate reused by track, trackPurchase, trackStartTrial, and trackCompleteRegistration — instead of relying on every call site to remember to check first, since that's the exact pattern that had already failed twice.
Status
Built and committed — iOS branch fix/apple-signin-email-scope (not yet pushed). No visual or copy change — this only changes which network calls fire.
See it
Allin/Core/MetaAdsClient.swift

Consent wasn't actually being recorded anywhere — now built, on both entry points

Every "record_consent_granted" call was silently failing — and until tonight, so was every change made later
Claude built it, Marie deploys
What was actually happening — corrected against the live database, not just the repo
Re-checked directly against live Supabase (not assumed from the branch): a function called record_consent_granted does already exist live — but it's an older, zero-argument version (the one your own project notes already flagged as untracked-but-live), and all it does is stamp a single blanket timestamp into user_entitlements.consent_granted_at — no record of which specific consents (analytics/advertising/reminders) were actually granted. The app's real call — on main, live today — doesn't call this function at all; the onboarding consent sheet only sets a local on-device flag, nothing reaches Supabase. The 3-argument version of the call (p_analytics/p_advertising/p_reminders) only exists on this session's branch, wrapped in a silent try? — and because no live function matches those 3 named parameters, that call would also fail silently if shipped as-is today. Live proof, checked directly: 0 of 112 real users have consent_granted_at set at all. Nothing about consent has ever actually landed in Supabase for anyone.
Why it matters
GDPR Article 7(1) requires being able to demonstrate that someone consented — not just that the app currently behaves as if they did. The actual protection (not sending analytics/ads events if someone said no) is enforced separately, client-side, so that part was fine — this gap was specifically about having no audit trail to prove it.
What was built, and what's new tonight
A proper user_consents table (one row per grant/withdrawal: user id, which consent, on/off, timestamp) plus a new, correctly-3-argument record_consent_granted (matching what the app actually calls) and record_consent_change (a second function for single-toggle changes). The onboarding side was wired first; Settings → Privacy's two toggles were wired to record_consent_change this session — before that, changing your mind after the fact wasn't recorded at all, only the very first choice was (once the RPC itself existed).
Status
Migration + both RPCs built and committed — backend branch feat/gdpr-consent-record. iOS wired on both entry points — branch fix/apple-signin-email-scope. Neither is deployed/pushed — re-verified directly against live Supabase just now: no user_consents table, no record_consent_change function exist live yet. Until this migration deploys, consent recording stays exactly where it is today: nowhere.

Anthropic DPA — not signed

Newest-discovered processor, handles special-category text
Marie — active request, not a click-through
What's open
Anthropic processes real check-in and plan-intake text (memory, belief, trigger, reflection) to classify emotion/crisis risk and compose plans. Their standard terms say this isn't used to train models, but a formal signed agreement confirming that isn't in place yet. Unlike some processors, this likely needs an actual support/Console request, not just accepting a checkbox.
Who
Marie — I can't execute this, it's a real external ask.

Supabase DPA — not confirmed

The primary database, holding everything directly
Marie
What's open
Supabase hosts every table Allin has, including all the special-category check-in/plan text, directly and unencrypted. Of every processor on this page, this one holds the most, so an unconfirmed DPA here is the highest-blast-radius gap even though Supabase is infrastructure, not a "third party" in the way Segment/Amplitude feel.
Who
Marie — check Supabase's DPA acceptance/terms directly in their dashboard or with support.

recommend-plan repo/live drift — the fix itself is done, what's left is getting it live

Previously flagged as an active, unresolved redeploy risk — that part is now resolved
Marie — approve the push
What was wrong
The version of recommend-plan tracked on main was FTS-only (no Anthropic call) while the function actually running in production used a Claude/Anthropic composition path — meaning the next unrelated push to main would have silently redeployed the stale version over the correct live one.
What's changed since
This has actually been fixed: the real live source was pulled directly (supabase functions download, not guessed from a binary bundle) and every function now matches what's really running — recommend-plan, plus three previously-untracked functions (recommend-audio, classify-affect, classify-crisis) that existed live but nowhere in the repo. CI also changed so each function only redeploys when its own folder changes, closing the exact mechanism that made this dangerous.
Status
All of the above is committed — backend branch fix/recommend-plan-repo-drift (also present in combined-fixes-2026-08-15). What's left isn't technical — it's approval to push, since main today still doesn't reflect any of it.

delete-user-data function — built, not deployed, now a real release-sequencing risk

iOS already calls it; deploying it late means every account deletion 404s
Marie — explicit go to deploy
What's open
The function that fans erasure out to Amplitude + Customer.io (in addition to the existing Supabase-side delete_user()) is written, committed, and confirmed to have the secrets it needs already live. It's just never been deployed.
Why this is now more urgent than "nice to have"
Confirmed directly in the code: iOS's fix/apple-signin-email-scope branch's deleteAccount() already calls delete-user-data's URL instead of the raw delete_user RPC. The live App Store build still correctly calls the raw RPC (confirmed separately on main), so nothing is broken today — but it means delete-user-data has to be deployed before or in the same release as that iOS branch ships. If the iOS update goes out first, every account deletion hits a 404 against a function that doesn't exist yet.
Who
Needs your explicit go to deploy — this touches live infrastructure. Flagging the ordering now so whoever preps that release doesn't miss it.

Medium priority

real, but not urgent

Amplitude DPA — unconfirmed

Risk just went down, not away
Marie
Was "medium-high" on the original checklist specifically because of the email it used to receive — now that's fixed, the data flowing there is lower-sensitivity, but a signed DPA is still required regardless of how sensitive the data is. Worth confirming, not urgent.

Customer.io DPA — unconfirmed

EU-region workspace may not even need a transfer mechanism
Marie
Workspace 216147 is EU-region, so the usual cross-border-transfer concern may not apply at all — but that needs explicit confirmation rather than assuming, since "probably fine" isn't the same as verified.

RevenueCat API key needs regenerating

Legacy format, incompatible with the current API
Marie
The current secret key is legacy-format and doesn't work against RevenueCat's v2 API — needed for any direct entitlement/subscription auditing. Generate a new v2 secret key in the RevenueCat dashboard when convenient; not urgent since the webhook-based sync into user_entitlements already works independently of this.

Segment access token/source id — still missing

Blocks Segment-side erasure specifically; already deprioritized once
Dead end, per your earlier call
Amplitude/Customer.io erasure work independently of this and don't need it. This only affects deleting a person's raw record inside Segment itself, and there's no accessible UI path to get these credentials in this workspace. Restating its real priority (medium, not high) rather than re-opening it — you already called this one. Also see the plan-tier note in the Done section above: the API this would unlock needs a Team/Business-tier workspace, not just the credentials — worth checking both at once if this is ever revisited.

Low priority

genuinely fine to leave for now

Meta DPA — unconfirmed

Lowest-sensitivity data of any processor here
Marie, whenever
Only device/advertiser id ever reaches Meta, and only when both ATT and the advertising toggle are on (as of tonight's fix, that's now actually true for every Meta call, not just the ID itself). No special-category data involved.

RevenueCat DPA — unconfirmed

Contract-necessity basis, not consent
Marie, whenever
Purchases require processing subscription data as a matter of fulfilling the contract — no special-category data, lowest legal exposure of the group.

The two screens that actually gate this page

real mockups live on the GDPR page — shown here because this page is what they gate
Both screens below decide whether anything on this page fires at all — the onboarding consent sheet sets the initial grant; Settings → Privacy is where someone comes back to change it, which is also the half of the flow that only became provably recorded tonight (see the consent-record card above). Full copy, decision-tracking, and the "no separate decline button" open question live on the GDPR page — not rebuilt here, just shown so this page is self-contained to glance at.

We want to make this practice better,

your choice, for each of these.

🔔
reminders
📈
improve the app
📣
ads
continue
Onboarding consent sheet — sets record_consent_granted. Source: Allin/Home/ConsentSheet.swift

privacy & data

📈
improve the app
so we can see which sessions help
📣
ads
so we can show relevant ads elsewhere
Settings → Privacy — the revisit screen, now wired to record_consent_change. Source: Allin/Home/SettingsView.swiftPrivacyConsentView

Cross-references: the fuller GDPR picture (pseudonymization, encryption, consent copy sign-off, the new "download my data" Settings entry point) lives on the GDPR & onboarding wiki. The DPA checklist itself is ~/Documents/allin-gdpr/DPA_CHECKLIST.md.

Who does what — pulled out on its own

I

Isabel

One CRM-specific item below has a ready prompt — paste it into your own Claude. The rest of your open items touching this workstream (consent-screen sign-off, the age-gate call, marketing claims) already live on the GDPR page's own "who does what" — not duplicated here.

Confirm the consent-screen wording matches what each toggle actually does
A copy-accuracy question, not a design one — the two toggles route to real, specific places
Prompt for your Claude
Open Allin's consent screen and Settings > Privacy screen copy (I can show you the mockups, or open https://allin-crm.pages.dev password isma2026 for the current wording). "Improve the app" sends data to Amplitude and Customer.io via Segment; "ads" sends data to Meta, and only when Apple's own tracking prompt is also allowed. Walk me through whether the current one-line descriptions for each toggle honestly represent that, in plain English a user would actually understand — and suggest better wording if not, so I can bring it to Marie.
M

Marie

DPA checks in progress (Anthropic, Supabase, Amplitude, Customer.io, Meta, RevenueCat) · approve pushing the recommend-plan repo-drift fix · explicit go to deploy delete-user-data — mind the release-sequencing dependency with the iOS branch · approve deploying the user_consents migration together with the new server-side consent gate (one doesn't do much without the other live) · decide the pre-existing-user backfill question for that gate, with Isabel · regenerate the RevenueCat v2 API key · re-check Segment's plan tier before investing more in the missing access token, given the Team/Business-tier requirement found tonight.