Receive email, not just send it.
Point MX at Unitpost. Any address on your domain becomes a webhook.
How it works
Sending, in reverse.
Mail lands, we parse it, then POST a signed webhook.
Point one MX record
One MX on a domain you already send from. Same auth.
We parse the MIME
Headers, text, HTML, attachments. Catch-all on the domain.
You get one signed event
email.received. Same signing, retries, and log.
One event, one handler
It's just another webhook.
email.received — same signed envelope as every other webhook.
whsec_••••••••- email.delivered1s200
- email.opened2h200
- email.bounced5h429
- email.clicked8h200
Safe by default
Scanned, verified, never in your payload.
Scanned. Verdicts on the event. Bytes stay out of the payload.
Spam & virus
Confirmed viruses rejected at ingest.
Attachments
Metadata on the event. Bytes via signed URLs.
SPF · DKIM · DMARC
Authentication results attached.
Spam
Flagged, never silently dropped
spam_verdictVirus
Confirmed viruses rejected at ingest
virus_verdictSPF
Sender-policy alignment
spf_verdictDKIM
Signature validation
dkim_verdictDMARC
Policy evaluation
dmarc_verdict
Developer-first
Fetch the full message on demand.
Same REST API and keys you send with.
Parsed
Headers, text, HTML, attachment metadata.
Signed downloads
URLs expire. No leaked links.
import { Unitpost } from "unitpost";
const unitpost = new Unitpost(process.env.UNITPOST_API_KEY);
// The webhook carries metadata — fetch the parsed message on demand.
const email = await unitpost.email.received.get("received_email_9f2c…");
console.log(email.subject, email.text, email.spam_verdict);
// Attachments come back as signed, expiring download URLs.
const file = await unitpost.email.received.attachment(
email.id,
email.attachments[0].id,
);
console.log(file.url); // expires in 5 minutesWhat you can build
Turn inbound mail into product
Every message is a structured event. The inbox is an integration point.
Support inboxes
support@ becomes events. Tickets open in your helpdesk.
Reply handling
Users reply by email. Threads land back in your app.
Parse & extract
Forwarded receipts, orders, forms. Pull the data out.
Email-to-action
Create a task, log an expense, kick off a build.
Attachment intake
Collect files by email. Fetch them over signed URLs.
Conversation history
Full thread next to what you sent. Per contact.
Included free
Send and receive on your domain
$0/ mo
- Included on every plan, including free
- One MX record — catch-all for your domain
- Signed email.received webhooks
- Reuses your verified domain auth
- Parsed headers, bodies & attachments
- Same REST API, dashboard & SDKs
Questions
Common questions
How do I receive email on my own domain with Unitpost?
Turn on receiving for a domain you already send from, add the one MX record we show you, and subscribe a webhook endpoint to the email.received event. Once the MX verifies, every message addressed to that domain is parsed and POSTed to your endpoint. There's no new domain and no separate account — receiving reuses the domain authentication you already verified for sending.
Does the inbound MX record break my existing email?
It can, which is why we check first. Our MX must be the lowest-priority value present or mail won't route to us — so if your root domain already has MX records for Google Workspace or Microsoft 365, pointing it at us would stop mail reaching those inboxes. We detect the conflict, warn you before you proceed, and recommend using a subdomain like inbound.yourdomain instead.
Which addresses receive mail — do I configure each one?
No per-address setup. Receiving is catch-all: any address at the enabled domain lands, and you route on the to field in your own handler. So support@, billing@, and reply+abc123@ all arrive without provisioning anything, which is what makes per-user or per-thread reply addresses practical.
How do I get the message body and attachments?
The email.received webhook carries metadata only — sender, recipients, subject, message_id, and attachment descriptors — so payloads stay small. Fetch the parsed message with GET /api/v1/email/received/{id}, which inlines text and HTML when they're small, then call GET /api/v1/email/received/{id}/attachments/{attachmentId} for a signed download URL that expires in about five minutes. Both need the emails:read scope.
Are inbound messages checked for spam and viruses?
Yes. Every message carries spam, virus, SPF, DKIM, and DMARC verdicts on the event and the stored record, so your handler decides how much to trust a sender. Confirmed viruses are rejected at ingest rather than handed to you. Messages over 25 MB in total, including attachments, are also rejected at ingest and the sender gets a failure notice.
Can I reply in-thread to an email I received?
Yes. Pass the received email's id as in_reply_to on POST /api/v1/email and we populate the In-Reply-To and References headers from the stored Message-ID chain, and prefix the subject with Re: if it isn't already — so mail clients thread the conversation. Any In-Reply-To or References header you set explicitly still wins.
Is inbound email included on the free plan?
Yes. Receiving is included on every plan, including free — it is not a paid add-on. You still need a verified domain and a webhook subscribed to email.received. Received messages don't draw the paid sending allowance; on the free plan they share the monthly email quota with outbound sends.