Email inbound Live
Your email service provider (or our hosted SMTP) posts inbound customer emails to message.com. We parse them, dedupe on the messageId, drop auto-replies and bounce mail, and create or append to a ticket in the unified inbox.
Endpoint
This endpoint is HMAC-signed. The signing secret is set per workspace at Settings → Integrations → Inbound email. Configure your ESP's inbound webhook (Postmark, SendGrid Inbound Parse, Mailgun, etc.) to post here with the secret you copy from the dashboard.
Most ESPs let you set a signing secret. If your ESP does not, host a small relay that signs the payload before forwarding. The hashing details are in Signature verification.
Inbound addresses
Three flavours of inbound address feed into this endpoint:
- Auto. We assign you
[email protected]. Zero configuration. - Subdomain.
[email protected]. Lets you map multiple local parts (e.g., billing, support) to different departments. - Custom domain.
[email protected]. Requires MX record pointing to our inbound relay or your ESP forwarding inbound mail to us.
Manage these at Inbound addresses API.
Required headers
| Header | Value |
|---|---|
Content-Type | application/json |
X-Signature | sha256=<hex> over the raw request body |
Request body
The shape mirrors the de-facto standard most ESPs produce. Required fields: from, to, subject, and at least one of text or html. Optional fields: messageId, headers, attachments.
{
"from": "Customer Name <[email protected]>",
"to": ["[email protected]"],
"subject": "Help with my order",
"text": "Plain text body",
"html": "<p>HTML body</p>",
"messageId": "<unique-id-from-esp>",
"headers": {
"Auto-Submitted": "...",
"Precedence": "...",
"In-Reply-To": "<[email protected]>",
"References": "<[email protected]> <[email protected]>"
},
"attachments": [
{ "filename": "order.pdf", "url": "https://...", "size": 12345, "contentType": "application/pdf" }
]
}Threading
We thread messages into existing conversations using the In-Reply-To and References headers. Messages whose references match a prior thread are appended; new threads create a new conversation. If both signals are missing, we fall back to subject-line normalisation (strip Re: / Fwd: prefixes) plus visitor email.
Deduplication
The messageId is the dedupe key. The same messageId delivered twice is a no-op. ESPs sometimes deliver duplicates after a retry; this prevents double-tickets. If your ESP does not provide a messageId, we synthesise one from (from + subject + first 200 chars of body), which is good enough for retry storms but does not catch genuine duplicates.
What we drop
The endpoint silently drops (returns 200 OK without creating a ticket) on these signals to avoid filling the inbox with noise:
From: MAILER-DAEMON@*, bounce messages. Handled via the outbound webhook instead.Auto-Submitted: auto-repliedorauto-generated, out-of-office replies.Precedence: bulkorPrecedence: list, mailing list traffic.List-Unsubscribeheader present, newsletter blasts.
Drops are logged in the workspace audit log with a reason field for forensic debugging.
Response
On success: 200 OK with an empty body. On signature failure: 401 invalid_signature. On schema failure: 400 invalid_body with details. On dedupe hit: 200 OK (idempotent).
Example request
curl -X POST 'https://app.message.com/api/v1/email/inbound' \
-H 'Content-Type: application/json' \
-H 'X-Signature: sha256=<hex>' \
--data-binary @inbound.jsonVerifying the signature (your side)
If you proxy or replay inbound mail through your own system, you may want to verify our outgoing signature on the relay leg. The verification logic is symmetric.
import { createHmac, timingSafeEqual } from "crypto";
function verifyInbound(rawBody: Buffer, signatureHeader: string, secret: string) {
// Header format: 'sha256=<hex>'
const [scheme, sig] = signatureHeader.split("=");
if (scheme !== "sha256") throw new Error("bad scheme");
const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
if (!timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
throw new Error("bad signature");
}
}Full multi-language verification reference at Signature verification.
Common pitfalls
- Parsing the body before verifying. Compute the HMAC over the raw body bytes. JSON parsers may reorder keys or normalise whitespace and break the signature.
- Missing
messageId. Without it we synthesise one. Synthesised IDs can collide on near-identical messages. Configure your ESP to forwardMessage-ID. - MX record not switched. For custom domains, mail keeps flowing to your old provider until DNS propagates. Test with a tool that resolves the MX explicitly.
- HTML-only emails. Some senders omit
text. We render the HTML via a sanitiser, but plain-text agents have a worse experience. Ask senders to include both parts when possible.