Designing a Shared Team Inbox for WhatsApp: Lessons From Building Zetta CRM
When your entire sales and support operation runs on WhatsApp, the inbox becomes the most critical piece of infrastructure you own. Not your website. Not your dashboard. The inbox.
This is the story of how we designed the shared team inbox inside Zetta CRM — the architectural trade-offs, the problems that only show up at scale, and the patterns that survived contact with real teams.
The Starting Constraint: WhatsApp Is Not Email
Email inboxes have decades of tooling built around them. Folders, rules, threading, assignment. WhatsApp has none of that infrastructure. Messages arrive as a flat stream. There's no native concept of "assign this conversation to Sarah" or "label this contact as VIP."
So we had to build all of that from scratch — but shaped around how WhatsApp actually behaves:
- Messages are real-time (not pull-based)
- Conversations span text, images, documents, voice notes, and locations
- Group chats have multiple participants with different roles
- Phone numbers can change identity (device switches, number porting)
- Read receipts and presence are expected by customers
Zetta CRM treats WhatsApp as a first-class protocol, not a bolt-on channel.
Architecture Overview
Here's the high-level system design:
┌─────────────────────────────────────────────────────┐
│ WhatsApp Gateway │
│ (multi-device connection, message normalization) │
└──────────────────────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ Message Processing Pipeline │
│ │
│ ┌──────────┐ ┌───────────┐ ┌──────────────────┐ │
│ │ Contact │ │ Label │ │ Assignment │ │
│ │ Resolver │ │ Engine │ │ Router │ │
│ └──────────┘ └───────────┘ └──────────────────┘ │
└──────────────────────┬──────────────────────────────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
┌───────────┐ ┌──────────┐ ┌──────────────┐
│ Team │ │ AI Agent │ │ Webhook / │
│ Inbox UI │ │ (Hallo) │ │ Integrations │
└───────────┘ └──────────┘ └──────────────┘
Every incoming message passes through three stages before reaching a human or AI agent: contact resolution, label evaluation, and assignment routing.
Contact Resolution: The Identity Problem
WhatsApp identifies users by phone number, but phone numbers are not stable identities. People change numbers, share devices, or use WhatsApp Business with a different number than their personal account.
Zetta CRM maintains a unified contact database that:
- Deduplicates by phone number + country code normalization
- Merges contact records when a known customer messages from a new number
- Preserves history — conversation threads follow the contact, not the number
- Enriches with metadata from previous interactions (labels, notes, custom fields)
The contact resolver runs in under 10ms per message. At 10,000+ messages per day across our platform, that latency budget matters.
Labels: Lightweight but Powerful
We debated building a full tagging taxonomy versus simple flat labels. We chose labels with one key addition: automation triggers.
A label in Zetta CRM is not just metadata. It can:
- Route a conversation to a specific team member or group
- Trigger a webhook to an external system
- Change AI agent behavior (e.g., label "escalated" disables auto-reply)
- Filter the inbox view for focused work
Label: "hot-lead"
→ auto-assign to Sales Team
→ push to Google Sheets (webhook)
→ AI: switch to sales-qualified prompt
Label: "support-tier-2"
→ route to senior agent
→ AI: disabled (human-only)
→ SLA timer: 30 minutes
Labels are applied manually by team members, automatically by the AI agent based on conversation content, or via API by external systems. This flexibility means teams can start simple and add automation incrementally.
Role-Based Access: Who Sees What
A 3-person team and a 30-person team have very different access needs. We designed a role system that scales without becoming bureaucratic:
Owner — full access, billing, can delete workspace
Admin — manage team members, configure AI, view all conversations
Agent — sees assigned conversations + unassigned queue
Viewer — read-only access to conversations and analytics (for managers)
The key insight: agents should see their own conversations plus the unassigned pool. They should NOT see other agents' active conversations by default. This prevents stepping on each other's toes and gives customers a consistent experience.
Admins get a bird's-eye view across all conversations for quality monitoring and load balancing.
Real-Time Sync: The WebSocket Challenge
WhatsApp users expect instant delivery. If a customer sends a message and the agent sees it 30 seconds later, the experience feels broken.
Our real-time layer uses WebSocket connections to push messages to the inbox UI with sub-second latency:
WhatsApp → Gateway → Redis Pub/Sub → WebSocket Server → Browser
│
├── typing indicators
├── presence (online/offline)
├── read receipts (synced back)
└── assignment notifications
We chose Redis Pub/Sub over a dedicated message broker (Kafka, RabbitMQ) for this layer because:
- Message durability isn't critical here (we persist to DB separately)
- Latency is the priority — Redis delivers in microseconds
- Operational simplicity — one less system to maintain
- Our scale (thousands of concurrent connections, not millions) fits Redis comfortably
If we hit connection limits, the upgrade path to a dedicated broker is clean because the pub/sub interface is abstracted.
Multi-Number Support
Many businesses operate multiple WhatsApp numbers: one for sales, one for support, one for a specific product line. Zetta CRM supports connecting multiple numbers to a single workspace.
This sounds simple but introduces routing complexity:
- Which number should outbound messages come from?
- If a customer messages Number A, can an agent on Number B see it?
- How do we prevent cross-contamination between brands sharing a workspace?
Our solution: number-level permissions. Each number has its own team assignment, and conversations are scoped to the number they arrived on. Cross-number visibility is opt-in at the admin level.
Analytics: Measuring What Matters
A shared inbox without analytics is a black box. Teams need to know:
- Response time: How long until first reply? (We track median, p95, and p99)
- Resolution rate: What percentage of conversations reach a conclusion?
- Agent load: Who's handling more? Who needs help?
- AI deflection: How many conversations did the AI resolve without human help?
- Peak hours: When is the team overloaded?
We built these as first-class metrics inside Zetta CRM, not as an afterthought reporting tab. The analytics feed directly into routing decisions — if Agent A's queue is full and Agent B is idle, new conversations route to B.
What We Got Wrong (And Fixed)
Mistake 1: Over-engineering assignment rules early.
We built a complex rule engine before we had users. Turns out, 80% of teams just want round-robin or manual pick-from-queue. We simplified the default and moved complex rules to an "advanced" tier.
Mistake 2: Treating groups like DMs.
Our first version applied the same inbox logic to group messages. It created chaos — hundreds of messages flooding the inbox from a single active group. We had to build group-specific behavior: aggregate by group, show only actionable messages, and let teams mute groups from the inbox without leaving them on WhatsApp.
Mistake 3: Not exposing the API early enough.
Developer teams wanted to integrate Zetta into their own systems from day one. We were too focused on the UI and delayed the API. Lesson learned: the API is the product for technical teams. We now ship API-first and the UI consumes the same endpoints.
The Developer Layer
For teams that want programmatic access, Zetta CRM exposes:
- REST API — full CRUD on contacts, conversations, labels, messages
- Webhooks — real-time events (new message, label applied, assignment changed)
- MCP (Model Context Protocol) — for AI agents and LLM-powered workflows to interact with the CRM programmatically
The MCP integration is particularly interesting for developer teams building custom AI agents. Instead of hardcoding WhatsApp logic, your agent connects to Zetta's MCP endpoint and gets structured access to conversations, contacts, and actions.
Lessons for Builders
If you're building anything on top of WhatsApp for teams:
- Start with the message model, not the UI. How you normalize, store, and route messages determines everything downstream.
- Groups are a different product. Don't treat them as "DMs with more people."
- Real-time is table stakes. Anything over 2 seconds feels broken to WhatsApp users.
- Labels beat folders. Flat, flexible, automatable.
- Ship the API on day one. Your power users will thank you.
Try Zetta CRM
If your team handles customer conversations on WhatsApp and you're still managing them on personal phones or a clunky tool that treats WhatsApp as a second-class channel, check out Zetta CRM.
Connect a number, invite your team, and see what a WhatsApp-native inbox feels like.
Built by Cipta Dusa — software development for teams that move fast.
Top comments (0)