Receiving

Get replies and other mail for your domain as a webhook, and fetch each message from the API.

Setup

  1. Add the domain and verify its DKIM record (Domain Verification). The verified record is what proves the domain is yours.
  2. Point its MX record at smtp.eml.dev (priority 10), with no other MX records.
  3. Turn on Receiving on the domain's page, or PATCH /api/v1/domains/:name with {"domain": {"inbound_enabled": true}}. The MX check runs at once.
  4. Subscribe a webhook to email.received.

Every address at the domain is received; plus addressing (hello+token@) works as is. The MX moves all of that name's mail here, so use a subdomain such as replies.example.com (added and verified as its own domain) to keep your mailbox where it is.

What happens to a message

We check SPF, DKIM, DMARC and ARC while the sender is still connected, add an Authentication-Results header, and store the message encrypted for a year. The sender is told it was accepted only once it is stored. Then email.received fires with the metadata, never the content:

email.received
{
  "id": "msg_01J...",
  "type": "email.received",
  "timestamp": "2026-10-16T12:00:00.000Z",
  "data": {
    "id": 42,
    "domain": "replies.example.com",
    "from": "Erin <erin@example.org>",
    "envelope_sender": "erin@example.org",
    "to": ["hello+abc123@replies.example.com"],
    "subject": "Re: Your issue",
    "message_id": "CAF1...@mail.gmail.com",
    "size": 4211,
    "auth": { "spf": "pass", "dkim": "pass", "dmarc": "pass" },
    "received_at": "2026-10-16T12:00:00.000Z"
  }
}

Fetch the content with your API key: GET /api/v1/inbound_emails/:id returns the headers, the text and HTML bodies and the attachment list; GET /api/v1/inbound_emails/:id/raw returns the message as received (message/rfc822), which is what Action Mailbox and other MIME parsers take. GET /api/v1/inbound_emails lists them.

Treat the content as untrusted: the From header is whatever the sender wrote. Before you act on who a message is from, check that auth.dmarc is pass, or use an address only you hand out (a per-recipient token in a plus address). Never render the HTML body unsanitized.

What is refused

  • Mail that fails DMARC where the sender's domain publishes p=reject (refused permanently).
  • Messages over 1 MB (refused permanently).
  • More than 60 messages a minute, or more than 1 GB a calendar month, per account. The sender is told to try again later, so nothing is lost while you are under the limit again within the sender's retry window.

Each refusal is listed under Inbound in the dashboard and at GET /api/v1/inbound_rejections for 30 days. Limit hits are counted per domain and hour, and the first one in each hour fires inbound.limited. Receiving is free.