Yes, Resend can receive email. Inbound went generally available in late 2025, and there are two ways in: a managed address at <alias>@<id>.resend.app that needs no DNS at all, or your own domain with a single MX record on the host inbound. Incoming mail fires an email.received webhook, and the one thing that surprises everybody is that the webhook payload is metadata only: the body and the attachment bytes come from separate API calls.
What Resend inbound actually is.
Inbound turns Resend from a send-only service into a two way one. Resend runs the receiving infrastructure, accepts mail addressed to you, stores it, and tells your backend it arrived. You do not run an SMTP server, you do not manage a spool, and you do not spend your Saturday getting a VPS off a blocklist.
The use cases are the obvious ones and they are all shaped the same way: something arrives, your code reacts. Support inboxes that create tickets. Reply-to addresses on lifecycle email so a customer can answer a message and reach a human. Parsing addresses that ingest forwarded receipts or vendor notifications. Any workflow where the trigger is a person sending you mail rather than clicking something in your product.
Two ways to set it up.
Pick based on whether a human will ever see the address. Everything downstream, the webhook and the API, behaves identically either way.
| Option | DNS needed | Address | Use it when |
|---|---|---|---|
| Managed address | None | <alias>@<id>.resend.app | Prototypes, internal tools, and anything where the address is never shown to a customer. Zero DNS means zero propagation wait. |
| Custom domain | One MX record, host inbound, priority 10 | <alias>@inbound.example.com | Anything customer facing. The address is on your brand, and it reuses the domain you already verified for sending. |
The managed option is genuinely zero setup: Resend gives you an address on a resend.app subdomain tied to your account, and mail to it starts flowing immediately. The trade is that the address is not yours, so it is fine for a parsing pipeline and wrong for a support inbox a customer will read.
The custom domain path is one record on a domain you have already added to Resend.
# The one record custom-domain inbound needs Type MX Host inbound Priority 10 Value (copy from the Resend dashboard) # Confirm it resolves before you test dig +short MX inbound.example.com
Copy the value from the dashboard rather than from anywhere else, and if the domain already has other MX records make sure nothing else is sitting on the host inbound. For the complete picture of the records a Resend domain uses and why verification stalls, see every DNS record Resend asks for.
The email.received event.
Subscribe an endpoint to email.received and Resend posts to it when mail lands. The envelope is the same as every other Resend webhook: a type, a created_at, and a data object. It is signed through Svix like the rest, so the same verification applies, and the Resend webhooks guide covers the headers, the retries, and the idempotency rules that go with it.
{
"type": "email.received",
"created_at": "2026-07-29T14:02:11.126Z",
"data": {
"email_id": "56761188-7520-42d8-8898-ff6fc54ce618",
"from": "customer@example.com",
"to": ["support@inbound.example.com"],
"cc": [],
"bcc": [],
"subject": "Re: your invoice",
"message_id": "<CAF-abc123@mail.example.com>",
"attachments": [
{ "filename": "invoice.pdf", "content_type": "application/pdf" }
]
}
}Read that payload for what is missing rather than what is present. There is no html, no text, and no attachment content. Treat field lists as a floor, not a ceiling, and parse defensively: providers add fields over time, and to is an array even when there is one recipient.
The gotcha: the payload is metadata only.
This is the thing that costs people an afternoon. The email.received webhook is a notification that a message arrived, not the message. You get who it was from, who it was addressed to, the subject, the message_id, and the names and content types of any attachments. You do not get the body, and you do not get the attachment bytes.
Everything else is fetched by data.email_id, in two separate calls: the Received Emails API for the message itself, and the Attachments API for file contents. That design is deliberate. It keeps webhook payloads small and uniform no matter how large the incoming mail was, which is what makes the delivery timeouts and retry behavior predictable.
// email.received is a notification, not the message.
// data.email_id is the handle for everything you actually want.
async function handleInbound(event) {
const { email_id: emailId, from, subject, attachments } = event.data;
// 1. The body: one call to the Received Emails API, by id.
const message = await fetchReceivedEmail(emailId);
// 2. Attachment bytes: a separate call, also by id. The webhook
// gave you filenames and content types, nothing more.
const files = await Promise.all(
attachments.map((a) => fetchAttachment(emailId, a)),
);
await createTicket({ from, subject, body: message.text, files });
}The fetchReceivedEmail and fetchAttachment calls above are thin wrappers you write over those two endpoints. We are deliberately not printing exact paths here, because API routes get versioned and a stale URL in a blog post is worse than no URL: read them from Resend's current API reference. The shape of the integration is what matters, and it is always the same: verify, ack, enqueue, then fetch by id in the background rather than inside the 15 second webhook response window.
Everything is stored, webhook or not.
A detail worth knowing before you architect around webhooks: all inbound mail is stored by Resend and retrievable through the API even if you never configure a webhook at all. That gives you two useful properties. First, a webhook outage is not data loss, since the messages are still there to be fetched once you recover. Second, you can build a polling integration instead of an endpoint if a public HTTPS handler is awkward for you, for example inside a private network or a scheduled job.
The webhook is still the right default, because polling adds latency and wasted calls. But knowing the storage is the source of truth changes how you handle failures: your recovery path is a backfill, not a plea for redelivery.
Sending and receiving verify separately.
A Resend domain has two capabilities, sending and receiving, and each is verified independently against its own records. That is why you can see a status of partially_verified. It is not a stuck state or an error. It means one capability passed and the other did not, which for most teams means the sending records went green months ago and the inbound MX record you just added has not been detected yet.
The practical implication is that adding inbound to an existing domain cannot break your sending. The checks do not share state. If the inbound record is wrong, inbound stays unverified and your outbound mail carries on exactly as before.
Testing inbound locally.
Your laptop has no public URL, so run a tunnel: ngrok pointed at your local port, with the tunnel URL registered as the webhook endpoint. Then send a real email to your inbound address and watch it arrive. This is better than crafting a fake payload, because a real delivery is really signed, so you are exercising your verification path rather than skipping past it.
The managed resend.app address is the fastest way to get a test loop going, since it sidesteps DNS propagation entirely. Set up the managed address, build the handler against it, and switch to the custom domain once the logic works.
What is not documented.
Two things people reasonably ask that we are not going to make up answers for. The maximum size of an inbound message, including attachments, is not stated in Resend's public documentation. Nor is the retention period for stored inbound mail and its attachments. If either constrains your design, for example if you are ingesting large PDFs or need messages available for a compliance window, ask Resend support and get the answer in writing rather than trusting a number you read somewhere.
The reply path, on the other hand, is straightforward: receive the webhook, fetch the body if you need it, and call the send API, referencing the original message_id so the reply threads properly in the recipient's client. If you would rather not host that handler, connecting Resend to GetFluxly lets you trigger an automation off the event and send the reply through your own Resend account.
Resend inbound email, common questions answered.
Can Resend receive emails?
Yes. Inbound email is a generally available Resend feature, launched in late 2025, and it works two ways. You can use a managed address in the form alias@id.resend.app, which needs no DNS at all, or you can receive on your own domain by adding a single MX record on the host inbound at priority 10. Incoming mail is stored by Resend, retrievable through the API, and can fire an email.received webhook to your backend.
Why is the email body missing from the email.received webhook?
Because the webhook payload is metadata only, by design. It carries from, to, cc, bcc, subject, message_id, and a list of attachment names and content types, but not the message body and not the attachment bytes. To get the content you make a separate API call keyed on data.email_id: the Received Emails API returns the message, and the Attachments API returns the file contents. This keeps webhook payloads small and predictable regardless of how large the incoming message was.
Do I need my own mail server to receive email with Resend?
No. Resend runs the receiving infrastructure, so there is no SMTP server for you to operate, patch, or keep off blocklists. Your side of the integration is an HTTPS endpoint that accepts the email.received webhook, or nothing at all if you would rather poll the API, since every inbound message is stored whether or not a webhook is configured. The only piece of mail infrastructure you touch is one MX record, and even that is optional if you use a managed resend.app address.
What is a partially verified domain in Resend?
It means one of the domain's two capabilities passed verification and the other did not. A Resend domain can do sending and receiving, and each is checked independently against its own DNS records, so partially_verified is the status you land on when, for example, the sending records are green but the inbound MX record has not been detected yet. It is not an error state. If you only ever intend to send, you can leave a domain partially verified indefinitely.
Can I reply to inbound email automatically?
Yes, and it is the most common reason to set inbound up in the first place. Your handler receives the email.received webhook, fetches the body by email_id if it needs to read the message, and then calls the Resend send API to reply, threading correctly by referencing the original message_id. If you would rather not write and host that handler, GetFluxly can trigger an automation off the inbound event and send the reply through your existing Resend account instead.