The problem
If you've ever tried integrating WhatsApp Business API for customer support, you know the pain: Meta's verification process, webhook configuration, message template management, media
handling, real-time updates... and then you need a UI for your team to actually use it.
The existing options are either expensive SaaS platforms or closed-source tools you can't customize. I wanted something self-hosted, open-source, and easy to deploy.
So I built Tercela.
What is Tercela?
Tercela is an open-source omnichannel platform for customer communication. Right now it supports WhatsApp — you connect your WhatsApp Business account and get a shared inbox where your team can manage all conversations.
Features
- Shared inbox — all WhatsApp conversations in one place
- Real-time messaging — messages appear instantly via WebSocket
- Message templates — create, edit, and sync templates with Meta
- Contact management — centralized contact database
- Agent assignment — route conversations to team members
- Media support — images, audio, video, documents via S3-compatible storage
- Multi-language UI — English, Spanish, Arabic, Hindi, Indonesian, Turkish
The tech stack
I made some deliberate choices here that I'm happy with:
| Layer | Technology | Why |
|---|---|---|
| Backend | Hono + Bun | Hono is lightweight and fast, Bun gives native performance |
| Frontend | Nuxt 4 + Nuxt UI | File-based routing, auto-imports, great component library |
| Database | PostgreSQL + Drizzle ORM | Type-safe queries, automatic migrations on boot |
| Real-time | Bun native WebSocket | No Socket.io overhead, pub/sub built-in |
| Auth | JWT (HS256) | Simple, stateless |
| API docs | OpenAPI + Scalar | Interactive docs auto-generated from route definitions |
Monorepo structure
tercela/
├── apps/
│ ├── api/ # Hono + Bun (port 3333)
│ └── web/ # Nuxt 4 (port 3000)
├── packages/
│ └── shared/ # Shared types & utilities
└── docker-compose.yml
Architecture decisions
Channel adapter pattern
This is probably the most important design choice. Instead of hardcoding WhatsApp everywhere, I built an adapter interface. WhatsApp is just one implementation.
Adding a new channel (email, webchat, Telegram) means implementing that interface — the inbox, contacts, and routing all work the same regardless of the channel.
Drizzle ORM with named schemas
Instead of dumping everything in the public schema, I organized tables into PostgreSQL schemas:
-
auth→ users -
channels→ channels, templates -
contacts→ contacts -
inbox→ conversations, messages -
config→ settings -
storage→ media
This keeps things clean as the database grows.
Bun native WebSocket
No Socket.io, no ws package. Bun has built-in WebSocket support with pub/sub. When a new message comes in via webhook, the API publishes to the relevant topic and every connected client gets
the update instantly.
Auto-migrations on boot
Drizzle migrations run automatically when the API starts. No manual db:migrate step in production — deploy and it just works.
What's next
- AI / Chatbot support
- Webchat widget (embed on any website)
- Email channel
- Analytics dashboard
Looking for feedback
This is early stage and I'd love to hear from the community:
- Does this solve a real problem for you?
- What channels would you want to see next?
- Architecture feedback — what would you do differently?
Contributions are welcome — even if it's just opening an issue with a feature idea.
Tercela
Open-source omnichannel platform for customer communication
Connect channels like WhatsApp, manage conversations in real time, and organize contacts — all in a unified interface.
Features · Quick Start · API Docs · Contributing · Roadmap
Features
Messaging
- Omnichannel inbox — Manage all conversations from a single dashboard
- WhatsApp integration — Send and receive messages via WhatsApp Cloud API
- Real-time updates — WebSocket-powered live messaging
- Message templates — Create, edit and sync WhatsApp message templates with Meta
- Media support — S3-compatible storage for images, audio, video and documents
Management
- Contact management — Centralized contact database with metadata
- Agent assignment — Route conversations to team members
- Channel adapter pattern — Extensible architecture to add new channels
Developer Experience
- REST API with OpenAPI docs — Interactive API reference via Scalar
- Automatic migrations — Versioned database migrations applied on boot
- Docker-ready — One command to run the entire stack
- Monorepo — Clean separation…

Top comments (0)