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
- 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.
- Turn on receiving for the domain, in the dashboard or with
PATCH /v1/domains/:id(below). Most teams use a dedicated subdomain such asinbound.yourdomain.comso the root domain keeps its existing mailbox provider. - 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.
- The status flips to
activewithin a minute of the record resolving. Every address at the domain then receives.
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}'/v1/domains/:idTurn inbound receiving on or off for a domain. Requires the domain:write scope. Returns the MX record to publish and the current receiving status.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| receiving_enabled | boolean | Yes | true to start receiving, false to stop |
Request Body
{ "receiving_enabled": true }Response
{
"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": []
}
}/v1/emails/receivingList received emails for your team, newest first. Requires the inbound:read scope. Keyset pagination with after / before cursors, the same way Resend paginates.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| limit | integer | No | 1 to 100, default 20 |
| after | string | No | Return emails older than this id |
| before | string | No | Return emails newer than this id |
Response
{
"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" }
}
]
}/v1/emails/receiving/:idOne received email with its parsed text and HTML bodies, headers, attachment metadata and a short-lived link to the raw .eml file.
Response
{
"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": []
}/v1/emails/receiving/:id/attachmentsAttachment metadata with a fresh download link for each file.
Response
{
"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"
}
]
}/v1/emails/receiving/:id/attachments/:attachment_idOne attachment's metadata and download link.
Response
{
"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"
}/v1/emails/receiving/:idDelete your team's copy of a received email and its attachments. Requires the inbound:delete scope. Other recipients' copies are not affected.
Response
{ "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.
{
"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.
/v1/emails/receiving/:id/replyReply to a received email. Requires the inbound:read and email:send scopes.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| from | string | No | One of the team's reply addresses for this message; defaults to the rule above |
| to | string | string[] | No | Defaults to the sender's Reply-To, then From |
| cc | string | string[] | No | Optional |
| subject | string | No | Defaults to Re: plus the original subject |
| text | string | No | Plain-text body (text or html required) |
| html | string | No | HTML body |
Request Body
{ "text": "Thanks, the replacement ships today." }Response
{ "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.
/v1/emails/receivingList received email with optional filters.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| to | string | No | Only mail for this exact address |
| domain_id | string | No | Only mail on this domain |
| unread | boolean | No | true for unread mail only |
| q | string | No | Search subject, sender and recipient (at least 2 characters) |
Response
{
"object": "list",
"has_more": false,
"data": [ { "object": "email", "id": "cmu...", "subject": "Invoice 42", "read_at": null } ],
"unread_count": 3
}/v1/emails/receiving/{id}Mark one received email read or unread.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| read | boolean | Yes | true for read, false for unread |
Request Body
{ "read": true }Response
{ "object": "email", "id": "cmu...", "read_at": "2026-10-02T10:00:00.000Z" }/v1/emails/receivingMark up to 100 received emails read or unread.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| ids | string[] | Yes | Received email ids |
| read | boolean | Yes | true for read, false for unread |
Request Body
{ "ids": ["cmu1...", "cmu2..."], "read": true }Response
{ "object": "list", "read": true, "updated": 2 }