Inbound Email

Receive mail at any address on a domain you already send from. Messages are parsed, stored for 30 days, and available through the API, the dashboard and the email.received webhook. The API follows Resend's receiving endpoints and object shapes; an existing integration needs the base URL, an SMTPfast key with the inbound scopes, and SMTPfast webhook signature verification.

Setup

  1. Inbound is available on paid plans. The domain must be verified for sending, or be a subdomain of a domain that is verified in the same team.
  2. Turn on receiving for the domain, in the dashboard or with PATCH /v1/domains/:id (below). Most teams use a dedicated subdomain such as inbound.yourdomain.com so the root domain keeps its existing mailbox provider.
  3. Add the MX record returned by the API at your DNS provider, priority 10, pointing at the SMTPfast receiving endpoint. On Cloudflare-hosted zones the Inbound page offers "Add on Cloudflare", which publishes the record through Domain Connect after one approval on Cloudflare's side. Our record must be the only one at the lowest priority; a competing MX at the same or a lower priority gets a share of the mail and the domain shows as failed.
  4. The status flips to active within a minute of the record resolving. Every address at the domain then receives.
bash
curl -X PATCH https://smtpfa.st/api/v1/domains/dom_xyz789 \
  -H "Authorization: Bearer $SMTPFAST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"receiving_enabled": true}'
PATCH/v1/domains/:id

Turn inbound receiving on or off for a domain. Requires the domain:write scope. Returns the MX record to publish and the current receiving status.

Status is one of disabled, pending (record not seen yet), active, or failed (another MX outranks ours). Turning receiving off stops acceptance at SMTP time; nothing is queued or dropped silently.

Parameters

ParameterTypeRequiredDescription
receiving_enabledbooleanYestrue to start receiving, false to stop

Request Body

json
{ "receiving_enabled": true }

Response

json
{
  "id": "dom_xyz789",
  "receiving": {
    "enabled": true,
    "status": "pending",
    "mx_record": { "type": "MX", "priority": 10, "value": "inbound-smtp.eu-west-2.amazonaws.com" },
    "published_mx": [],
    "competing_mx": []
  }
}
GET/v1/emails/receiving

List received emails for your team, newest first. Requires the inbound:read scope. Keyset pagination with after / before cursors, the same way Resend paginates.

List items carry metadata only. Fetch a single email for the body, headers and download links.

Parameters

ParameterTypeRequiredDescription
limitintegerNo1 to 100, default 20
afterstringNoReturn emails older than this id
beforestringNoReturn emails newer than this id

Response

json
{
  "object": "list",
  "has_more": true,
  "data": [
    {
      "object": "email",
      "id": "rcv_01j9x2m8k4",
      "to": ["[email protected]"],
      "from": "Ada Lovelace <[email protected]>",
      "created_at": "2026-09-02T10:00:00.000Z",
      "subject": "Order #4821",
      "received_for": ["[email protected]"],
      "message_id": "<[email protected]>",
      "attachments": [{ "id": "att_7f3a9c1b2d", "filename": "invoice.pdf", "content_type": "application/pdf", "content_disposition": "attachment", "content_id": null, "size": 48213 }],
      "status": "delivered",
      "domain_id": "dom_xyz789",
      "verdicts": { "spf": "PASS", "dkim": "PASS", "dmarc": "PASS", "spam": "PASS", "virus": "PASS" }
    }
  ]
}
GET/v1/emails/receiving/:id

One received email with its parsed text and HTML bodies, headers, attachment metadata and a short-lived link to the raw .eml file.

Download links live for 15 minutes and are minted per request, after authorization. A message whose virus scan did not pass is returned with status quarantined and no body, raw link or attachments.

Response

json
{
  "object": "email",
  "id": "rcv_01j9x2m8k4",
  "to": ["[email protected]"],
  "from": "Ada Lovelace <[email protected]>",
  "created_at": "2026-09-02T10:00:00.000Z",
  "subject": "Order #4821",
  "bcc": [],
  "cc": [],
  "reply_to": [],
  "received_for": ["[email protected]"],
  "message_id": "<[email protected]>",
  "attachments": [
    {
      "id": "att_7f3a9c1b2d",
      "filename": "invoice.pdf",
      "content_type": "application/pdf",
      "content_disposition": "attachment",
      "content_id": null,
      "size": 48213
    }
  ],
  "html": "<p>Hi, the invoice is attached.</p>",
  "text": "Hi, the invoice is attached.",
  "headers": { "from": "Ada Lovelace <[email protected]>", "subject": "Order #4821" },
  "raw": {
    "download_url": "https://smtpfast-inbound-raw.s3.eu-west-2.amazonaws.com/ingress/raw/...",
    "expires_at": "2026-09-02T10:15:00.000Z"
  },
  "status": "delivered",
  "envelope_from": "[email protected]",
  "envelope_to": ["[email protected]"],
  "domain_id": "dom_xyz789",
  "size": 61440,
  "verdicts": { "spf": "PASS", "dkim": "PASS", "dmarc": "PASS", "spam": "PASS", "virus": "PASS" },
  "in_reply_to": null,
  "references": []
}
GET/v1/emails/receiving/:id/attachments

Attachment metadata with a fresh download link for each file.

Response

json
{
  "object": "list",
  "data": [
    {
      "id": "att_7f3a9c1b2d",
      "filename": "invoice.pdf",
      "content_type": "application/pdf",
      "content_disposition": "attachment",
      "content_id": null,
      "size": 48213,
      "download_url": "https://...",
      "expires_at": "2026-09-02T10:15:00.000Z"
    }
  ]
}
GET/v1/emails/receiving/:id/attachments/:attachment_id

One attachment's metadata and download link.

Response

json
{
  "id": "att_7f3a9c1b2d",
  "filename": "invoice.pdf",
  "content_type": "application/pdf",
  "content_disposition": "attachment",
  "content_id": null,
  "size": 48213,
  "download_url": "https://...",
  "expires_at": "2026-09-02T10:15:00.000Z"
}
DELETE/v1/emails/receiving/:id

Delete your team's copy of a received email and its attachments. Requires the inbound:delete scope. Other recipients' copies are not affected.

Response

json
{ "object": "email", "id": "rcv_01j9x2m8k4", "deleted": true }

The email.received webhook

Subscribe a webhook to email.received and you get one event per delivery (one per recipient address the message was received for), as soon as the message is parsed or quarantined. The payload is the list item shape above (metadata, no body): fetch the email by id for the content. Delivery, signing and retries work like every other event, see Webhooks.

json
{
  "type": "email.received",
  "timestamp": "2026-09-02T10:00:03.000Z",
  "data": {
    "object": "email",
    "id": "rcv_01j9x2m8k4",
    "from": "Ada Lovelace <[email protected]>",
    "to": ["[email protected]"],
    "received_for": ["[email protected]"],
    "subject": "Order #4821",
    "created_at": "2026-09-02T10:00:00.000Z",
    "attachments": [{ "id": "att_7f3a9c1b2d", "filename": "invoice.pdf", "content_type": "application/pdf", "size": 48213 }],
    "status": "delivered",
    "domain_id": "dom_xyz789"
  }
}

Replying from the mailbox

The Inbound page lists every receiving address with its unread count, and each message has a Reply button. A reply goes out through your normal sending pipeline (it counts as one email, obeys the suppression list and your plan), carries In-Reply-To and References so the recipient's mail client keeps the thread together, and is shown under the received message afterwards. The From address is the receiving address itself when its domain is verified for sending, or when it is a receiving subdomain of a domain you verified: such a subdomain sends on its parent's verification and is signed with the parent's DKIM key, which DMARC accepts. You can also pick a verified parent domain, with Reply-To pointing back at the mailbox so the next message lands there.

POST/v1/emails/receiving/:id/reply

Reply to a received email. Requires the inbound:read and email:send scopes.

Quarantined messages cannot be replied to. A 400 with reply_from in the body lists the addresses you may send from when the from you passed is not one of them.

Parameters

ParameterTypeRequiredDescription
fromstringNoOne of the team's reply addresses for this message; defaults to the rule above
tostring | string[]NoDefaults to the sender's Reply-To, then From
ccstring | string[]NoOptional
subjectstringNoDefaults to Re: plus the original subject
textstringNoPlain-text body (text or html required)
htmlstringNoHTML body

Request Body

json
{ "text": "Thanks, the replacement ships today." }

Response

json
{ "id": "em_01j9x5r2k8" }

Limits and retention

  • Messages up to 25 MiB. Up to 25 attachments of 20 MiB each; larger parts are dropped and noted on the message.
  • Bodies are kept up to one million characters per part. Headers are capped at 200 entries.
  • Everything is retained for up to 30 days, then removed: the parsed message, the raw file and the attachments. Delete earlier through the API when you are done with a message; the shared raw file goes once no team references it.
  • Every message is scanned. A virus verdict other than PASS quarantines it: the metadata is kept so you know it arrived, and the content is never parsed or exposed.
  • Routing follows the SMTP envelope, not the To header, so Bcc and forwarded mail land in the right place and a spoofed To header cannot redirect anything.

Resend compatibility

Paths, pagination, the received email object and the attachment object match Resend's receiving API. SMTPfast adds a few fields (status, verdicts, envelope_from, envelope_to, domain_id, size) and never removes one.

Triage: filters and read state

GET /v1/emails/receiving keeps Resend's shape and adds filters and an unread_count. The read state is the same one the dashboard inbox shows.

GET/v1/emails/receiving

List received email with optional filters.

Parameters

ParameterTypeRequiredDescription
tostringNoOnly mail for this exact address
domain_idstringNoOnly mail on this domain
unreadbooleanNotrue for unread mail only
qstringNoSearch subject, sender and recipient (at least 2 characters)

Response

json
{
  "object": "list",
  "has_more": false,
  "data": [ { "object": "email", "id": "cmu...", "subject": "Invoice 42", "read_at": null } ],
  "unread_count": 3
}
PATCH/v1/emails/receiving/{id}

Mark one received email read or unread.

Parameters

ParameterTypeRequiredDescription
readbooleanYestrue for read, false for unread

Request Body

json
{ "read": true }

Response

json
{ "object": "email", "id": "cmu...", "read_at": "2026-10-02T10:00:00.000Z" }
PATCH/v1/emails/receiving

Mark up to 100 received emails read or unread.

Parameters

ParameterTypeRequiredDescription
idsstring[]YesReceived email ids
readbooleanYestrue for read, false for unread

Request Body

json
{ "ids": ["cmu1...", "cmu2..."], "read": true }

Response

json
{ "object": "list", "read": true, "updated": 2 }