Files
saas_backend/app/core/email_address.py
T
2026-08-31 20:04:12 -04:00

44 lines
1.8 KiB
Python

"""One way to write an email address down.
`users.email` had a plain unique index, so `Alice@example.com` and
`alice@example.com` were two accounts. Nobody types their address the same way
twice: a signup form gets `Alice@Example.com`, the password reset gets
`alice@example.com`, and the second finds nothing. Worse, an invitation or an
identity provider matching a user by address can create a duplicate of a person
who already exists.
Addresses are stored normalised — trimmed and lower-cased — and every lookup
normalises its input, so a row written before this still matches.
**Only the case is changed.** The local part of an address is technically
case-sensitive, and a handful of mail servers honour that; in practice none that
anyone signs up with does, and treating `Alice@` and `alice@` as different people
causes far more trouble than the theoretical correctness buys. This is the same
call the base application made.
Nothing else is touched — no dot-stripping, no plus-tag removal. `a.b+x@gmail.com`
routes to the same mailbox as `ab@gmail.com` at one provider and to a different
one elsewhere, so deciding they are the same person is a guess about somebody
else's mail server.
"""
from __future__ import annotations
from typing import Optional
def normalise(email: Optional[str]) -> Optional[str]:
"""The canonical form: trimmed and lower-cased. `None` stays `None`."""
if email is None:
return None
return email.strip().lower()
def matches(left: Optional[str], right: Optional[str]) -> bool:
"""Whether two addresses are the same person, for comparison in code.
Database comparisons should filter on the normalised column instead; this is
for the places holding two strings already.
"""
return normalise(left) == normalise(right)