Storing a phone number so it still works in two years
Store one canonical column in E.164, as a string, and normalise at the edge rather than at the point of sending. Every messaging system that has a "why did this fail" problem has a phone number problem underneath it.
The four ways a number gets destroyed
- Stored as an integer. 0712345678 becomes 712345678. The leading zero is not a formatting detail, it is part of the national number.
- Excel. A CSV opened and saved in a spreadsheet turns long numbers into scientific notation and strips leading zeros. This destroys more contact lists than any bug.
- Local format with no country. 0712345678 is unambiguous only if you know the country, and the day you add a second country it is unambiguous nowhere.
- Normalised at send time, not at write time. Now the same person exists three times, and none of the rows know about each other.
Normalise once, at the edge
# Every entry point does this: the form, the CSV import, the API,
# the webhook. Not the send path - by then it is too late to
# deduplicate against what you already have.
def to_e164(raw: str, default_country: str) -> str:
digits = re.sub(r"[^\d+]", "", raw)
if digits.startswith("+"):
return digits
if digits.startswith("00"):
return "+" + digits[2:]
if digits.startswith("0"):
return default_country + digits[1:]
return default_country + digitsThe default_country argument is not optional and must not have a default. A function that silently assumes a country is a function that will one day put a Kenyan prefix on a Ugandan number, and the message will go to a real person who is not your customer.
Keep what they typed
Two columns: the canonical E.164 you send to, and the raw string the person entered. When a number turns out to be wrong, the raw value is the only evidence of whether it was mistyped or mis-normalised, and those have completely different fixes.
Validate what you can, not what you cannot
- Length and prefix against the country's plan catches most typos.
- Whether it is on WhatsApp is a separate question with its own API, and a number can be valid and not on WhatsApp.
- Whether it belongs to your customer is not checkable at all without sending something to it, which is what a confirmation message is for.
Uniqueness, and the trap in it
A unique index on the canonical number is right for contacts and wrong for people. Numbers are recycled, shared between family members, and reassigned by carriers. Treat the number as a route to a person, not as their identity - which is also why handing back an old customer's history on a recycled number is a real risk.
Questions people actually ask
What is E.164?
The international format: a plus, a country code and the national number, with no spaces, dashes or brackets. +254712345678. It is the only format that is unambiguous everywhere.
Should I store the number as a string or a number?
A string, always. Leading zeros are meaningful, numbers get longer than an integer column expects, and nobody ever does arithmetic on a phone number.
Njiwa
One inbox for WhatsApp, Facebook and Instagram, and an API that sends over either WhatsApp rail. 7 days free, no card.