The Address as a User ID

Stage: the schema, long before the form. In control: whoever chose the key.

1. One address is being asked to hold two jobs

An address makes a decent login and a poor primary key, and the cost of confusing those two roles arrives the first time somebody wants to change theirs.

As a login it is excellent. People remember it without effort, they can type it on a phone, and it is unique enough in practice that nobody has to invent a username. Nothing here argues against asking for an address at sign-in.

As a key it fails on the one property a key needs, which is that it never changes. Everything below follows from that single mismatch, and the decision that causes it is usually made in an afternoon, years before anyone feels the consequence.

2. The address changes and the key was not supposed to

People change addresses for reasons that have nothing to do with your product. They leave an employer, move off a provider that is shutting down, abandon a mailbox that was compromised, or simply decide the address they made at fifteen is no longer something they want to say out loud on a phone call.

A key is a promise that a row will be identifiable by the same value for as long as the row exists. An address makes no such promise, and it does not consult you before breaking it. What looks like a stable string is a rented one, and the tenancy is short compared with the lifetime of a database.

The second problem is that an address is personal data. A key propagates everywhere by design, which means the personal data propagates everywhere too, into places where you would never deliberately put it.

3. The value leaks into places that outlive the account

Watch where a key ends up in a running system and the leak becomes obvious.

It appears in URLs, because resource paths are built from keys, and URLs land in browser history, in referrer headers sent to third parties, in bookmarks, and in chat messages when somebody shares a link. It appears in server logs, which are retained on a schedule set by someone who never considered they were retaining addresses. It appears in exported file names, in cache keys, in queue payloads, and in the analytics events those systems emit.

None of that infrastructure is designed to hold identifiers that a person can ask you to erase. An internal identifier in all those places is meaningless to anyone who sees it, which is exactly what you want from a value with that much reach.

The third property that disqualifies it is representation. There is no single canonical spelling of an address: case differs, the domain may be written in two scripts, and the local part is somebody else's namespace. A key that has more than one form is not a key, and the way to tame those forms is described in storing an address without breaking it.

4. The alternative fits in one sentence

Give every account an internal identifier that means nothing outside your system, make that the primary key, and store the address as an ordinary attribute with a unique index on its normalised form.

The identifier can be a sequential integer or a generated value, and the choice between them is about exposure rather than correctness. A sequential key visible in a URL tells anyone who looks how many accounts you have and lets them walk the list; a random identifier does not, which is usually worth the extra bytes.

Nothing about this arrangement removes the address from sign-in. The login lookup becomes one indexed query from normalised address to identifier, and every other table points at the identifier. That is the whole change, and applied at the start it costs nothing.

5. What breaks when the address was the key

The failure is a cascade rather than an error message, and it shows up during an operation people expect to be routine.

Changing an address becomes a rewrite of every table that referenced it, which for a system of any age means dozens of them, plus whatever lives outside the database. Anything that cannot be rewritten simply breaks: links already sent, files named after the key, records held by a payment provider or a support tool that has its own copy.

Analytics loses the person in half. Their history exists under two identifiers with no relationship between them, so every measurement of retention, lifetime value, or repeat behaviour undercounts silently rather than failing loudly.

Deletion requests turn hostile. With an internal key you can erase the address, keep the anonymous record, and leave every referencing row intact. With the address as key, removing it means deleting or orphaning everything that pointed at it, and the request you were legally obliged to honour becomes a data loss incident you have to plan around.

6. One person legitimately has several addresses

Work, personal, and the one from two providers ago that still receives things. This is normal, and a schema that permits exactly one address per account forces those people into duplicate accounts.

The shape that works is a set of addresses belonging to one identifier, with one of them flagged as the current contact address. Sign-in accepts any confirmed address in the set. Outbound mail uses the flagged one. Removing an address is a row deletion rather than a migration.

That structure is only expressible because the account has an identity separate from any of its addresses. With the address as key it cannot exist at all, which is why systems built that way end up with two accounts for the same customer and a support process for merging them.

7. One number tells you which side you are on

Count the places that have to be touched for a person to change their address. Not files, not lines of code: places where the value has to be written or rewritten.

If the answer is one row in one table, the key is right and the change is a form submission. If the answer is anything larger, the address is functioning as a key somewhere, whether or not the schema admits it, and the count is a fair estimate of how expensive the next request will be.

This number is worth measuring on a system you inherited rather than assumed. Address-as-key is often not declared in the schema at all. It hides in a foreign key that stores an address string, in a cache prefix, in a file path, in a downstream tool keyed by address because that is the only field it was given.

The handover

The key is chosen before anyone types anything, which makes it the earliest point where somebody far from the user decides what happens to their address later. The states that all this simplifies are laid out in the signup states nobody tests, and the copies of the address that spread through the rest of a company are traced in where the address travels inside a company. If the key is internal anyway, the address can stop being the thing that identifies people at sign-in, which is the argument in signing in with an account you have. From the other side of the same problem, keeping track of the addresses you make is what this looks like for the person holding them, and the generator itself is where the next one comes from.

Read next

All guides