The customer data model behind a unified inbox
A unified inbox for WhatsApp, website widget, Instagram, and email starts with a customer identity model that works across channels. Here is how we designed it and what broke first.
The core problem
Every channel gives you a different customer identifier. WhatsApp: phone number. Website widget: anonymous session ID (or email if captured). Instagram: Instagram user ID. Email: email address.
The "unified" in unified inbox means these should resolve to a single customer profile when possible. Without that, a customer who contacts you on WhatsApp and then follows up on your website is treated as two different people.
The contact model
Our contacts table is the unifying layer. A contact has: - A UUID primary key (our internal ID) - Optional: email, phone number, name - Optional: channel-specific identifiers (WhatsApp phone, Instagram ID, etc.) - A confidence score for any identity matches made by linking logic
When a new conversation starts on any channel, we check for an existing contact that matches the channel-specific identifier. If found, the conversation is linked to the existing contact. If not, a new contact is created.
Identity resolution
The hard case: a customer starts a conversation on the website widget without logging in (no email captured), then contacts us via WhatsApp with their phone number. We now have two contacts for the same person.
We run a linking job that checks for contacts with: - The same phone number across any channel - The same email across any channel - A high-confidence fingerprint match (browser signals, if consented)
When a match is found above a confidence threshold, we surface it to the agent: "This may be the same person as [WhatsApp contact]. Merge?" The agent confirms. We never merge automatically — the risk of wrong merges (and the resulting data leak between two actual different people) is too high.
What broke first
Orphaned contacts. When a WhatsApp number is recycled (common in prepaid markets in MENA), we inherit conversation history from the previous owner. We now add a "confirmed match" signal: if the customer starts a new conversation and references a previous order or account detail, we confirm identity. If they do not, we treat it as a new contact.
Channel switching. A customer escalates from AI (handled via API channel) to human (moved to WhatsApp for a call). The handoff loses the conversation thread in naive implementations. We link the API channel conversation and the WhatsApp follow-up to the same contact, and surface both in the agent view.
The lesson: invest in the contact model early. It is harder to retrofit identity linking than to build it from the start.