Dev.to WebDev πŸ›  Dev πŸ‘ 1 πŸ“– 3 min read

Knocket: One Knock. Every Connection.

Most chat widgets ship with their own dashboard. And another dashboard for email. And a third for the messaging app your users actually use. If you're an indie maker with 30 signups, opening three dashboards to reply to

Knocket: One Knock. Every Connection.

 Most chat widgets ship with their own dashboard. And another dashboard for email. And a third for the messaging app your users actually use. If you're an indie maker with 30 signups, opening three dashboards to reply to five messages is a joke.

We built Knocket as the free contact layer for indie developers who don't want to babysit dashboards. One line to install a live chat widget. One shareable contact page. One inbox that receives Live Chat, Telegram, and Email in the same place β€” and lets you reply directly from Telegram without opening anything else.

This post walks through the design decisions behind the unified inbox β€” specifically how we route three channels through a single webhook without leaking channel-specific logic all over the codebase.

The problem with "one inbox for everything"

You've seen the pitch before. Every helpdesk product from Intercom on down claims a "unified inbox." In practice they mean:

  • Live chat: real-time bidirectional WebSocket
  • Email: SMTP inbound + polling
  • Messaging apps: platform-specific webhooks (Telegram Bot API, Meta Graph, etc.)

Each channel has its own delivery guarantees, ordering, retry semantics, and failure modes. A naive implementation hardcodes three code paths and inevitably drifts β€” a bug fix in the email path doesn't apply to Telegram, and now you have three subtly different reply behaviors.

The design: normalize at the edge, unify in the middle

We settled on a three-layer shape:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Channel Adapters (one per source)         β”‚
β”‚  ─ Live Chat WebSocket                     β”‚
β”‚  ─ Telegram Bot webhook                    β”‚
β”‚  ─ IMAP/SMTP email                         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
              ↓ normalized event
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Unified Message Bus (single format)       β”‚
β”‚  { conversation_id, sender, body, ... }    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
              ↓ fan-out
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Inbox UI  Β·  Notifications  Β·  Reply Routerβ”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The core rule: anything that knows about "Telegram" or "SMTP" lives only in an adapter. The bus and the UI see one message shape. Adding a new channel (WhatsApp is next) is a new adapter, not a new special case in the reply logic.

The reply router β€” the interesting part

The subtle problem: when you reply from Telegram, the reply must go back to the visitor via the original channel. If Sarah pinged you via the website widget and you type a reply into your Telegram app, that reply has to appear in Sarah's browser β€” not as a Telegram message to Sarah.

Every message on the bus carries source_channel. The reply router reads that field and dispatches to the right outbound adapter:

// Simplified
async function routeReply(originalMessage, replyBody) {
  const adapter = adapters[originalMessage.source_channel];
  await adapter.sendReply({
    conversation_id: originalMessage.conversation_id,
    body: replyBody,
  });
}

The Telegram adapter is where the neat trick lives: when Knocket forwards a live-chat message to your Telegram, the message includes structured metadata so a quote-reply in Telegram carries enough context for the router to reconstruct the original conversation. You reply in Telegram like you're replying to a friend, and it lands in the visitor's browser.

No new dashboard. No context switch. No "let me open the console real quick."

What this made easy

  • New channels are additive. Adding a channel is one adapter file plus registration.
  • The inbox UI is clean. It aggregates information from various channels and distinguishes between social media, email, and web sources.
  • Notifications are trivial. One "new message" event, delivered to browser push and Telegram.
  • Failure modes are localized. If Telegram's API is down, only the Telegram adapter fails. Live chat and email keep flowing.

What we open-sourced

We also released a companion project β€” an open-source AI auto-reply agent that plugs into the same message bus. It's a reference implementation for anyone who wants to run their own AI reply layer against a Knocket inbox: github.com/fangxinmoon/knocket-inbox-agent-EN

If you want to plug it into your own knowledge base, the agent's system prompt and retrieval layer are yours to swap out.

Try it

Knocket is 100% free forever β€” no ads, no seat limits, no paid tier. If you're at the "first 100 users" stage and Intercom's pricing feels absurd, this is the tool we wish existed when we started.

Set-up is one script tag. If you're on Framer, Webflow, WordPress, Ghost, Shopify, or anything else that supports custom code, you're 60 seconds from a live chat widget.

Powered by Tencent RTC infrastructure for global low-latency delivery.

Would love feedback in the comments β€” especially from anyone who's built or hated multi-channel inboxes before. What did we get wrong?

πŸ“° Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.