When a visitor sends a message and the agent isn't online, we email them: subject [muro] new message from Léa on lumiere.shop, with the message body, attachments, and a hidden header that pins the conversation ID. The agent replies from Gmail. The visitor sees a normal chat reply seconds later. There is no agent app open, no portal login.
Outbound — the easy direction
Outbound is mostly about deliverability. We sign all outbound with DKIM, set up SPF for our subdomain, configure DMARC at p=quarantine. We send from notifications@<workspace-slug>.muro.chat so each customer's reputation is its own. We ship via Resend (multi-region, EU residency).
textSubject: [muro] Sara → "Do you ship to Spain?"
From: [email protected]
Reply-To: [email protected]
List-Unsubscribe: <mailto:[email protected]>
References: <[email protected]>
Hi Léa — Sara just messaged on lumiere.shop:
> Do you ship to Spain?
[Open in muro] · [Reply by email below]Inbound — the hard direction
Inbound is where the engineering lives. The MX server (we run a tiny Postfix → relay HTTP webhook) accepts the reply, dumps it as MIME, and we parse it.
1 — Strip the quoted reply
Gmail wraps the original message in <blockquote class="gmail_quote">. Outlook uses <div type="cite">. Apple Mail uses > plain-text quoting. We have a parser per client. Mistakes here are visible: a missed strip means the visitor sees the agent's reply followed by their own original message echoed back.
2 — Strip signatures
Two algorithms run in parallel: a -- \n separator detector (RFC 3676) and a heuristic that looks for "Sent from my iPhone", phone numbers, repeated lines, etc. We err toward keeping content rather than deleting too aggressively.
3 — Reconcile to a thread
We use three signals in priority order: the Reply-To token (most reliable), the In-Reply-To/References headers (RFC 2822), and as last resort a fingerprint match on the workspace + agent email + recent conversation. If two signals disagree we route to a "needs review" queue rather than guess wrong.
The bouncer
The single most useful piece of inbound code we wrote is the autoresponder detector. Out-of-office replies, bounce messages, mailing-list digests — none of these should become chat messages. We check Auto-Submitted, X-Autoreply, Precedence, and a body classifier. False positives here are silent (the visitor never sees a noise message). False negatives are loud and embarrassing.
Things we considered and rejected
- →Polling IMAP: works, but ~30s latency and breaks on Gmail OAuth scope changes
- →Per-customer SMTP: too much surface, customers don't want to host their own MX
- →SES inbound: fine, but the Resend integration is simpler for our scale
- →Markdown in email replies: tempting, but most agents type plain text in Gmail. We render visitor side as plain text + automatic URL detection.
Forwarding is the feature that quietly does most of the work in this product. Every plan includes it. There is no "email forwarding add-on" — that would be a tax on something already nearly free to deliver.