Send · Inbound

Let your app read its mail.

Give your product an address. Replies, forwarded invoices and support mail arrive parsed: sender, subject, text, HTML and attachments, with authentication verdicts and a webhook as soon as they land.

Inbound · [email protected]Sample data
GET /emails/receiving/{id}JSON
{
  "object": "email",
  "id": "01a0c7a8-5293-7f29-8d95-3f48f1a09f76",
  "to": ["[email protected]"],
  "from": "[email protected]",
  "created_at": "2026-09-22T05:48:12.307Z",
  "subject": "Re: Order #1043 has shipped",
  "text": "The parcel arrived, but one book is missing. Photo attached.",
  "html": "<p>The parcel arrived, but one book is missing. Photo attached.</p>",
  "html_format": "data_uri",
  "headers": {
    "from": "Ravi <[email protected]>",
    "in-reply-to": "<[email protected]>",
    "mime-version": "1.0"
  },
  "cc": [],
  "bcc": [],
  "reply_to": [],
  "received_for": ["[email protected]"],
  "message_id": "<[email protected]>",
  "authentication": { "spf": "pass", "dkim": "pass", "dmarc": "pass" },
  "raw": {
    "download_url": "https://api.refiremail.com/files/…",
    "expires_at": "2026-09-22T06:48:12.307Z"
  },
  "attachments": [
    {
      "id": "01a0c7a8-53e0-70be-ac38-98d190f9ebda",
      "filename": "parcel.jpg",
      "content_type": "image/jpeg",
      "content_disposition": "attachment",
      "size": 482113
    }
  ]
}
A reply to an order email, parsed, with its verdicts and attachment. Switch to API for the JSON your app fetches.

01, Set up

Coming

Three steps to a working address.

Receiving uses the same domains, API keys and webhooks as sending. Nothing new to learn.

  • GET/emails/receiving/{id}, Live
  1. Point an MX record at us

    Add an MX record for a subdomain such as in.nimbu.example pointing at in.refiremail.com. A ready-made receiving address for each team, with no DNS to set up, is coming.

  2. Subscribe a webhook to email.received

    It fires once per message, with the sender, recipients, subject and attachment list.

  3. Fetch the full message

    GET /emails/receiving/{id} returns the text, HTML, headers, attachments and verdicts.

app/hooks/email/route.tsTypeScript
import { verifySignature } from './verify-webhook'; // the check on /product/webhooks

export async function POST(req: Request) {
  const body = await req.text();
  if (!verifySignature(req.headers, body)) return new Response('Invalid signature', { status: 400 });

  const event = JSON.parse(body);
  if (event.type === 'email.received') {
    // The event carries metadata only. Fetch the body and attachments.
    const res = await fetch(`https://api.refiremail.com/emails/receiving/${event.data.email_id}`, {
      headers: { Authorization: `Bearer ${process.env.REFIREMAIL_API_KEY}` },
    });
    await openTicket(await res.json());
  }
  return new Response(null, { status: 204 });
}
Webhook bodyJSON
{
  "type": "email.received",
  "created_at": "2026-09-22T05:48:12.640Z",
  "data": {
    "email_id": "01a0c7a8-5293-7f29-8d95-3f48f1a09f76",
    "created_at": "2026-09-22T05:48:12.307Z",
    "from": "[email protected]",
    "to": ["[email protected]"],
    "cc": [],
    "bcc": [],
    "received_for": ["[email protected]"],
    "message_id": "<[email protected]>",
    "subject": "Re: Order #1043 has shipped",
    "attachments": [
      {
        "id": "01a0c7a8-53e0-70be-ac38-98d190f9ebda",
        "filename": "parcel.jpg",
        "content_type": "image/jpeg",
        "content_disposition": "attachment",
        "content_id": null
      }
    ]
  }
}

02, The message

What arrives.

A received email is JSON with everything a MIME parser would give you, plus the things a parser can’t know: who it was really for, and whether the sender is who they say.

FieldWhat’s in it
fromThe sender’s bare address. The display name stays in headers.from.
to · cc · bccWho the message was addressed to.
received_forWhich of your addresses actually received it, taken from the Received headers.
subject · text · htmlThe content. Images referenced by cid: are inlined as data URIs, or kept as cid references with ?html_format=cid.
headersEvery header of the message, including in-reply-to for threading.
attachmentsId, filename, content type and size of each file. Each downloads through a signed link.
rawA signed link to the original message, to archive it or forward it unchanged.
authenticationspf, dkim and dmarc: the results of our own checks when the mail arrived.
message_id · created_atFor deduplicating and ordering what you receive.

03, Verdicts

Verdicts you can trust.

SPF, DKIM and DMARC are checked when the mail reaches our servers. They are never copied from headers the sender wrote.

A forged From fails DMARC here, whatever an Authentication-Results header inside the message claims.

Use the verdicts to decide how far to trust a message before you act on it. Open a ticket for anything, but import a bill only when the supplier’s mail passes DMARC.

A real replyJSON
{
  "from": "[email protected]",
  "subject": "Re: Order #1043 has shipped",
  "authentication": { "spf": "pass", "dkim": "pass", "dmarc": "pass" }
}
A forged senderJSON
{
  "from": "[email protected]",
  "subject": "Urgent: new bank details for payment",
  "headers": {
    "authentication-results": "mx.example; spf=pass; dkim=pass; dmarc=pass"
  },
  "authentication": { "spf": "fail", "dkim": "fail", "dmarc": "fail" }
}

04, Routes

Keep it, or turn it away.

Each receiving domain can have routes by address. With none, every message is stored for your app to fetch.

  • Store (the default)

    The message is kept for your app and announced with an email.received webhook. With no routes at all, this is what happens.

  • Drop

    Mail for the address is refused during the SMTP conversation with a 550, so nothing is stored and no bounce message is generated.

05, Use cases

Built for the mail your product gets back.

Three jobs that usually need a mail server, a parser and a cron job. Here each is a webhook handler.

  • Support replies into your helpdesk

    Give each ticket a plus address and file every reply in the right thread.

    // [email protected] → ticket 4821
    const ticketId = email.to[0].match(/\+(\d+)@/)?.[1];
    await helpdesk.addReply(ticketId, { from: email.from, text: email.text });
  • Replies that become comments

    People answer a notification from their inbox, and the reply lands in the thread it answers.

    // The reply's In-Reply-To names the notification you sent
    const thread = await threads.byMessageId(email.headers['in-reply-to']);
    await thread.addComment({ author: email.from, body: email.text });
  • Supplier bills into your books

    A bills@ address that files every PDF invoice it is sent.

    // [email protected] → your books, one PDF at a time
    const pdfs = email.attachments.filter((a) => a.content_type === 'application/pdf');
    for (const pdf of pdfs) await books.importBill(await download(email.id, pdf.id));

What it costs: each received email counts once on the transactional meter, the same as one you send.See pricing

Give your app an address.

Request early access. Once your account is on, add one MX record and point a webhook at your handler.