Identities
An Identity is one address on one channel — an email address, a phone number, a WhatsApp handle — held as its own row and keyed by value_key(kind, value). It resolves to exactly one Person, and a person may hold as many as they like. Identities are what make an arriving message find the right human instead of creating a new one.
Kind plus value, normalised#
The key is the pair, not the value. +91 90000 00000 as a phone identity and the same digits typed into an email field are different things, and keying on the pair keeps them apart. value_key also normalises, which is why a number written with spaces and the same number written without them resolve to one row rather than two — a lookup that compared raw strings would create a second person every time somebody's client formatted an address differently.
| Kind | Looks like | Arrives from |
|---|---|---|
| Email address | someone@example.net | A message fetched from Gmail, Microsoft Graph or IMAP |
| Phone number | +91 90000 00000 | An inbound call, or a phone follow-up being placed |
| WhatsApp handle | A number in WhatsApp's own form | An inbound WhatsApp message |
Whether an address on a new channel is attached to an existing person or starts a new one is a matching decision with its own rules — one person across phone, email and WhatsApp is the page for that, and it matters more than this one when something has gone wrong.
Why not a column on the person#
- Cardinality. A person has an unknown number of addresses. Columns force a guess, and the guess is always too small for somebody.
- Channels are added. A new channel adds identity rows and changes nothing about the person record, which is the same reason the engine reads canonical tables rather than provider ones.
- Resolution is a lookup.
by_handleneeds one indexed read from an address to a person; a column layout turns that into a scan across several columns. - Provenance. A row can be added and removed on its own, so attaching an address to the wrong human is repairable without touching anything else the person owns.
The cost of the row-per-identity shape is that a person's addresses have to be fetched, and fetching them one person at a time is exactly the pattern that made relationship lists expensive. The rewrite that made the Owner's customer list 5,574 statements into three was the same lesson: read the set, not the row.
Adding and removing one#
Add an identity with
people.add_identity— from the person's screen, or automatically when a message arrives that resolves to them.Result Future messages from that address reach this person, and their history is one history rather than two.
Remove an identity that belongs to somebody else.
Result The address stops resolving here. The next message from it is a first contact and creates its own person, which is the correct result even though it looks like an appearance out of nowhere.
Check the messages that arrived while it was attached.
Result They stay where they were filed. Removing the identity changes what happens next; it does not retrospectively re-file the past.
Questions#
Can one address resolve to two people?
No — an identity resolves to one person. Two people who genuinely share a mailbox are better modelled with that address against the organisation, because whichever human it is attached to will otherwise be credited with everything the other one writes.
What happens to identities when two records are merged?
They are preserved from both sides. merge_people keeps identities, stages, follow-ups, deals, demos, cases and onboarding from both records — that list is the point of what a merge preserves.
Does adding an identity let Connect contact that address?
It lets Connect recognise it. Whether anything is sent is decided by autonomy for that channel and by any block held in memory as block:<channel>, both of which are evaluated independently of how the address became known.