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.
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.
Four things happen, and three of them are about not losing anything.
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.
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.
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.
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.
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.
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.
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.
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.
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.
another workspace's conversation → 404, never 403 a 403 confirms the row exists
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.
<script src="https://app.example.com/widget.js" data-key="wk_977MVNt26fTea9CWyX8X3W5M"></script>
CNAME help.yourcompany.com app.example.com TXT _fmverify.help.yourcompany.com -FmtFvJv1AT…
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.
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.
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.
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.
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.
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.
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.
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.