Live chat · Email · Help centre

A chat window, a mailbox, and one thread underneath.

SuperProfIntercom is a support inbox for small teams. Customers write from a chat widget on your site or by email to your own address — and both land in the same conversation, with the same history and the same reply box.

There is no screenshot of the widget on this page. The demo is a working storefront that takes your own workspace key and puts the real widget on it, so what you try is the thing that ships rather than a picture of it.

01 Both ends, at once

Two ends of the same conversation.

On the left, somebody on a coffee roaster’s website. On the right, the support inbox. Nothing sits between them but a WebSocket: the message reaches the other side before the sender’s own Sending… has finished, and the reply comes back the same way.

Visitor · acmecoffee.com widget
Hi there 👋
Ask us anything — we usually reply in a few minutes.
Is the Guji Highland still available in 1kg?
Seen
Write a message…
Agent · one inbox ● Live
Sofia MarchettiIs the Guji Highland still available in 1kg?now1
Grinder recommendation for the Arch BlendThat is helpful, thank you. One more — does the same…12m2
Wholesale pricing for 6kg a weekWe are a small cafe in Peckham going through roughly…17m1
Tom WhitfieldYou: That should not happen and I am sorry. A replace…1h
02 How a message gets there

Four things happen, and three of them are about not losing anything.

01

The widget asks who to reply to — and a Skip that genuinely skips

Before the socket opens, the visitor is asked for a name and an email. It is asked once, and skipping is real. A typed address is a claim, not a proof, so it never resolvesa person: typing somebody else’s address gets you a new conversation, not their history. What it does buy is somewhere to write back when nobody is online.

02

The message goes over a socket — and is kept if it can't

Write with the connection down and the message stays on screen marked Sending…, held in the browser. When the socket comes back it is replayed automatically, keyed on an id the browser generated, so a replay of something that did land cannot post it twice. Reconnection backs off with full jitter from zero — a fixed delay brings every client back at the same instant and finishes what the outage started.

03

It is ordered by a counter, not by a clock

Every message takes the next number from a per-conversation counter, under a row lock, inside the transaction that writes it. Clocks skew, messages tie under load, and every row written in one Postgres transaction shares the same timestamp. That counter is also the cursor a reconnecting client catches up from, so it has to be right.

04

The reply goes back the way it came

A reply to a chat conversation returns over the visitor’s socket; a reply to an email conversation is composed and queued for delivery. The agent types in one box either way, and the box says where it is going.

03 The email channel

Email is a channel, not an afterthought.

Your workspace is reachable at its own address. A customer’s reply threads back into the same conversation two independent ways — the In-Reply-To chain, and a signed token in the reply address for the mail clients that strip headers. When both answer and disagree, the token wins.

The reply address
acme+c57-a3f9e1b204@yourdomain
      ↑ conversation   ↑ HMAC

The HMAC is not decoration. A bare +c57 is a number anybody can count upwards — and injecting a message into any conversation in any workspace, which an agent then reads as coming from the customer, is not a bug you find later.

What the webhook does with each delivery
a real message        → threaded, stored
the same one twice    → recognised, dropped
an out-of-office      → dropped before storage
an unknown address    → answered, dropped
a forged signature    401

Four of those five answer 200. A 2xx tells the provider to stop sending; a 5xx tells it to try again. A retry is only ever correct when a retry might succeed — so an unparseable email answers fine, thank you rather than buying six more deliveries of a message with nowhere to go.

04 The help centre

Answer it once, on your own address.

Write articles, group them into categories, publish. Customers get a public help centre with real search — Postgres full-text, the title weighted above the body, kept in step with the article by the database rather than by application code that might forget.

Point a CNAME and a TXT record at us and it serves on help.yourcompany.com instead of a slug on ours. The TXT record is the proof, not the CNAME: anyone can point a name at us, and only somebody who can edit the zone can publish a value we generated.

The widget answers before a person does

As somebody types into the chat box, the widget searches the same articles and offers up to three. Most of the time the answer is already written; the point of a help centre is that nobody has to ask for it.

A draft is a 404, not a “not allowed”

Only published articles leave a public route, and an unpublished one answers exactly as an unknown address does — so the response cannot be used to learn that a draft exists.

A slug is fixed when the article is created

Renaming an article changes its wording, never the address already sitting in somebody’s bookmarks or in a reply you sent last year.

05 When things go wrong

The interesting part of a support tool is what it does on a bad day.

Anything works when the network is up and the provider is answering. These are the three states that decide whether somebody trusts the thing.

A dropped connection does not drop the message

It is held in the browser, marked Sending…, and replayed the moment the socket is usable again — idempotent on a client-generated id, so the replay of something that did land cannot post it twice.

A stale summary beats an empty panel

When a conversation moves past its summary, the old text stays on screen marked stale while a new one is written behind it. Failure never touches the stored row — there is no state in which an error appears where a summary should be.

A send that failed comes back as a note in the thread

Five attempts with growing delays, then the failure is posted into the conversation beside the reply that did not go. An agent who believes they answered and did not cannot be told by a log line.

And one that is a rule rather than a feature
another workspace's conversation → 404, never 403
a 403 confirms the row exists
06 Getting it on your site

One line, before the closing body tag.

No package to install, no build step, nothing to configure. The launcher and the panel live in an iframe so nothing on your page can restyle them and nothing they do can reach your page.

Haven’t got a site to try it on? We keep one. The demo is a working coffee roaster’s storefront that takes your workspace key and puts the real widget on it — anything you send from there arrives in your own inbox as a real chat conversation.

The whole installation
<script src="https://app.example.com/widget.js"
        data-key="wk_977MVNt26fTea9CWyX8X3W5M"></script>
And for the help centre, two DNS records
CNAME  help.yourcompany.com            app.example.com
TXT    _fmverify.help.yourcompany.com  -FmtFvJv1AT…
07 What is built, and what is waiting

The honest state of it.

Some of this is finished on the application side and waiting on a key or a host. Saying so is cheaper than being found out, and the distinction is the interesting part anyway.

Live chatWorking

Both directions over a WebSocket, with presence, read receipts, typing indicators, and the offline queue above. A conversation survives a reload, a reconnect and a second tab.

Help centreWorking

Articles, categories, drafts, a rich text editor, public pages and full-text search. Article HTML is sanitised on write rather than on read, so every surface that renders it — the public page, the search snippet, the widget suggestion — receives markup that is already safe.

Team and rolesWorking

Invite by link or code, admin and agent roles, per workspace — the same person can be an admin in one and an agent in another.

EmailNeeds a provider key

Inbound mail is parsed, threaded, deduplicated and stored; replies are composed and queued. Nothing leaves the machine until a mail provider is configured, and a send job with no provider finishes as skipped rather than pretending to have sent.

AI summariesNeeds a model key

A long conversation gets a wants / tried / standingsummary in the thread’s right rail. The trigger, the cache and the staleness check all run; with no model configured the panel says so plainly rather than pretending to load.

Custom domainsNeeds a proxy

The hostname is claimed, checked against a real DNS resolver and routed to the right workspace. Issuing a TLS certificate for a customer’s hostname is a proxy’s job — on-demand TLS in front of the app — and it is deployment work rather than application work, so it is described rather than stubbed.

Same chat, second deviceNot built

A chat visitor is carried across tabs and return visits by a signed id in their browser. Carrying a thread from a laptop to a phone needs the address proved — a magic link or a signature from your own logged-in site — and a typed address is deliberately not allowed to stand in for either.

Send yourself a message.
It takes about a minute.

Create a workspace, paste its key into the demo storefront, and write something from the corner of that page. It arrives in your inbox while you are still looking at it.