Receive and parse inbound email programmatically
Create an inbox, point your domain's MX at ThreadCamp, and read inbound mail via HMAC-signed webhooks or polling. Messages are parsed and threaded for you; Routes can forward or trigger code.
Three steps: inbox, MX, consume
Receiving mail programmatically comes down to three things: a place for the mail to land, a way for the internet to route mail there, and a way for your code to read it. In ThreadCamp that is an inbox, your domain's MX records pointed at us, and either a webhook or a poll. You do not run an SMTP server, parse MIME by hand, or manage a mailbox on disk.
# 1. Create an inbox
curl https://threadcamp.vercel.app/v1/inboxes \
-H "Authorization: Bearer sk_live_..." \
-d '{ "domain": "acme-agents.email", "display_name": "Support" }'
# -> { "id": "inbox_...", "address": "support@acme-agents.email" }
# 2. Point your domain's MX at ThreadCamp (from POST /v1/domains), then verify.
# 3. Inbound mail now arrives as a signed webhook:
# POST https://your-app.com/hooks/threadcamp
# { "type": "message.received",
# "data": { "inbox_id": "inbox_...", "thread_id": "thr_...",
# "from": "customer@example.com", "subject": "Help",
# "text": "...", "attachments": [ ... ] } }Messages are parsed and threaded
Every inbound message is decoded for you: headers, plain text, HTML, and attachments become first-class fields, and the message is attached to a thread so a reply from the same correspondent lands in the same conversation. That matters for agents, which need conversation state to reply coherently rather than treating each email as an isolated event.
Verify the webhook signature
Inbound webhooks are HMAC-signed. Before you trust a payload, recompute the signature over the exact raw bytes you received and compare it in constant time - never parse the JSON first and never use a plain string equality check that can leak timing. Reject anything that does not match, so a forged POST to your endpoint cannot inject a fake inbound email.
import crypto from "node:crypto";
// Verify the HMAC signature on an inbound ThreadCamp webhook
export function isValid(rawBody: string, signature: string, secret: string) {
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody, "utf8")
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected),
);
}Or wait for one specific message
For short-lived jobs - an agent completing a signup, a test asserting that a notification arrived - polling is simpler than standing up a webhook endpoint. wait_for_message blocks until a message matching your filter (sender, subject) arrives or a timeout elapses, which is the clean primitive for retrieving an OTP mid-task.
Keep reading
Questions
How do I receive email programmatically?
Create an inbox with POST /v1/inboxes, point your domain's MX records at ThreadCamp, and consume inbound mail either by subscribing to the message.received webhook or by polling the messages endpoint. Each message is parsed into headers, text, HTML and attachments and attached to a thread.
Webhooks or polling - which should I use?
Webhooks are the default: message.received is delivered as an HMAC-signed POST within moments of the mail landing, which suits event-driven agents. Polling suits short-lived tasks and tests - list the inbox's messages, or use wait_for_message to block until a specific email arrives.
How do I verify an inbound webhook is genuine?
Each webhook carries an HMAC signature over the raw body using your endpoint's signing secret. Recompute the HMAC on the bytes you received and compare in constant time before trusting the payload. Reject anything that does not match.
Start reading inbound mail today.
No credit card. Full API, MCP and webhooks on the free tier.