Stories

The email address you’re keying on is not the person

Why email is a fragile anchor for user identity, how keying on the wrong id forks one person into many records, and how an identity graph converges every key onto one canonical customer.

Crossdeck

Summary

  • Most apps identify a user by their email because it feels stable. It is not — people share an email, change it, hold several, and pile onto shared inboxes like support@.
  • Keying identity on the wrong id forks one real person into multiple records. We hit exactly this: a live flow surfaced a single person as three separate customer records, because arrival keyed on the anonymous id instead of converging.
  • The durable identifier is the channel the user cannot re-create — not their email, and not even the auth UID on its own.
  • The fix is a canonical customer id with an identity graph that converges auth UID, device id, email and third-party rail ids onto one person. Email becomes a join key, never the anchor.

Email is the reflex. When you sit down to model a user, the email address is right there — unique-looking, human-readable, already in the signup form. So it becomes the primary key, the join column, the thing every downstream system trusts. It feels like the stable core of a person’s identity.

It is not. And the failure mode is quiet: nothing throws, nothing 500s. You just slowly accumulate a customer table where one human being wears several faces, and none of your counts mean what you think they mean.

Why is email a bad user identifier?

Email fails as an anchor — the immutable thing everything else hangs off — for four ordinary reasons, all of which happen in normal usage, not edge cases:

  • People change emails. They leave a company, switch providers, retire an old address. The person is the same; the string is gone.
  • One person holds several. Work email, personal email, the throwaway they used to dodge a newsletter wall. Same human, three rows.
  • Several people share one. support@, billing@, the shared family inbox. One string, many humans behind it.
  • Emails get reassigned. A departed employee’s address is handed to their replacement. The string persists; the person underneath swapped out entirely.

Any single one of these breaks the assumption that email maps one-to-one to a person. In practice you get all four at once. The moment you anchor identity on email, you have built your customer model on a value the user can freely rewrite, duplicate, or hand to someone else.

What actually broke for us

We did not reason our way to this in the abstract. A live flow taught it to us.

A person arrived through a path where identity was keyed on the id available at that moment — the anonymous device id — rather than reconciled against who they already were. The result: one real person surfaced as three separate customer records. Same human, three rows in the ledger, each with a slice of the truth. Revenue attached to one, activity to another, the email to a third.

Nothing errored. The system did precisely what it was told. That is the insidious part — a forking identity bug does not announce itself. It shows up later as inflated user counts, revenue that will not reconcile, a support agent who cannot find the account, and analytics quietly averaging one person as if they were a crowd.

The reflexive fix — just key on email instead of the anonymous id — trades one fork for another. Anchor on email and you re-introduce every failure above. The problem was never which fragile id we picked. It was picking a fragile id as the anchor at all.

Is the auth UID not the stable identifier?

It is better than email, but it is still not the durable thing. An auth UID is stable only as long as the auth record is. Re-signups, provider migrations, account merges, a user who signs in with Google one week and a magic link the next — each can mint a new UID for the same person. The UID is an identifier, not the person.

Identify by the channel the user cannot re-create. The email they typed is near the bottom of that trust order, not the top.

What is an identity graph?

An identity graph is a structure that maps many identifiers — auth UID, device id, email, third-party rail ids — onto one canonical customer id, so that every key a person shows up under converges on the same single record instead of forking into new ones.

Concretely, that means:

  • One canonical customer id is the anchor. Every other id is an alias that points at it, never a competing primary key.
  • A resolver runs a priority order, from the most-owned key to the least. The keys you control and the user cannot re-create win; a rail id asserted by a system you do not own is trusted last.
  • New keys attach, they do not fork. When a known person appears under a new id, the graph links that id to the existing canonical id, and creates a fresh record only when it is genuinely confident this is someone new.
  • Email is demoted to a join key. It is excellent for matching a person across an integration and unfit as the thing identity hangs off. It joins; it does not anchor.

The doctrine that fell out of this is one line: converge, never fork. When two signals might be the same person, the default is to converge them under one canonical id and let a human split them if that is wrong — because a missed merge corrupts every number downstream, while an over-merge is visible and correctable.

What we learned

  • Stability of the string is not stability of the identity. Email looks permanent and behaves like a variable. Pick the anchor by what the user cannot re-create, not by what is convenient in the signup form.
  • Forking is the expensive failure, not erroring. A silent split of one person into many quietly poisons user counts, revenue reconciliation and support.
  • Trust order matters more than any single key. Rank the most-owned key first and third-party rail ids last, rather than trusting whichever id happened to be in hand.
  • Email still earns its keep — as a join, not an anchor. It is how you stitch a person across integrations. It is not the spine of the identity model.

None of this required exotic machinery. It required admitting the reflex was wrong and moving the anchor from the most-typed id to the most-durable one. It is also, more or less, why Crossdeck resolves identity the way it does.

One person keeps showing up as several?

Crossdeck resolves anonymous and known activity onto one customer timeline.

Questions people ask

Should I use email as a primary key for users?
No. Email is mutable, shareable and reassignable — people change it, hold several, and pile onto shared inboxes. Use a canonical customer id as the primary key and treat email as one alias among many, useful for joining across systems but not as the anchor identity hangs off.
What is the difference between an identity anchor and a join key?
An anchor is the immutable id everything else attaches to; it should never change for the life of the person. A join key is any attribute, like email, used to match records across systems. The failure mode is using a join key as an anchor: because it can change or be shared, it forks one person into many records.
How does an identity graph prevent duplicate customer records?
It maps every id a person appears under — auth UID, device id, email, rail ids — to one canonical customer id, so a returning person under a new key is linked to their existing record rather than spawning a new one. New identifiers attach to the existing person by default; a new record is created only when the resolver is confident it is genuinely someone new.
Why not just identify users by their auth UID?
The auth UID is more stable than email but still tied to the auth record, which re-signups, provider migrations and account merges can churn. Anchor on the channel the user cannot re-create and resolve every other id, UID included, against that canonical identity.
What does “converge, never fork” mean?
It is the rule that when two signals might be the same person, you default to merging them under one canonical id rather than creating a second record. A missed merge silently corrupts every downstream number; an accidental merge is visible and easy to split. So you bias toward convergence.

Every product named here is built and operated by Cross Constellation.


The rule

Converge, never fork.

Read next

Why we built Crossdeck — and why every product we build helps test it
We thought the App Store Connect wait was the problem. The real problem was fragmentation — the same person scattered across a dozen disconnected tools. So we put one SDK and one customer timeline underneath everything we operate.

Every product is a test environment for the one beneath it
A habit tracker on the App Store, a link-in-bio product carrying anonymous global traffic, and a book catalogue on two platforms. None of them was built to test anything. That is exactly why they work as tests.

The Firebase bill that taught us to see
A runaway Firestore read bill sent us chasing ClickHouse. The real fix was not a new database — it was being able to see where every read came from. What we learned, and why we open-sourced Buckets.

We got our connector rejected. It was the best product spec we received all year.
A marketplace review rejected our connector with roughly a dozen findings. Instead of a failure, it read like a spec — the exact definition of a production-grade third-party integration we would never have written ourselves.

The black box blinks: seeing App Review happen in your own data
App Review is a black box. Identity-joined session data reveals when a reviewer starts testing your build, where they connect from, and the exact path they walk — plus the line we will not cross.

Why Biotree needed Crossdeck Trust — and how we stopped the SEO spam farms
Public, indexed link-in-bio pages are a magnet for spam farms and bot signups. The attack Biotree faced, why the obvious defences backfire, and the signup firewall that stopped it without punishing real users.

Why we are building Crossdeck Sales
Cold outbound is miserable because the seller is blind. We were not — we already saw who landed, what they read, and why. So we are building the tool that turns that journey into outreach that lands.