# Receiving email

You do not poll for mail. A webhook fires, you read the message it names, and you delete it when you are done. This page is that loop — and the Inbox, where the same mail waits to be answered.

## The loop

Subscribe a webhook to `email.received`, and everything else follows from the payload it delivers.

Handling one received message:

```text
const api = (path, init = {}) =>
  fetch(`https://api.rasket.com${path}`, {
    ...init,
    headers: {
      Authorization: `Bearer ${process.env.RASKET_API_KEY}`,
      "User-Agent": "acme-billing/1.0",
    },
  });

// 1. check the signature, as with every Rasket event (see the Webhooks reference)
const event = verifySignature(rawBody, headers, process.env.RASKET_WEBHOOK_SECRET);
if (event.type !== "email.received") return;

// 2. route on what the payload already carries — no call needed
const queue = event.data.received_for.includes("billing@inbound.acme.example")
  ? "billing"
  : "support";

// 3. read the parts the payload deliberately leaves out
const id = event.data.email_id;
const message = await (await api(`/emails/receiving/${id}`)).json();
const files = await (await api(`/emails/receiving/${id}/attachments`)).json();

// 4. each part carries a fresh signed link, good for fifteen minutes
for (const file of files.data) {
  if (file.download_url === undefined) continue; // a part we would not store
  await archive(file.filename, await fetch(file.download_url));
}

// 5. give the storage back
await api(`/emails/receiving/${id}`, { method: "DELETE" });
```

## Your Inbox

The same mail is waiting for you in your [Inbox](https://www.rasket.com/docs/inbox), the mail app behind the envelope in the dashboard’s top bar. Messages that answer each other are one conversation: we thread on the `In-Reply-To` and `References` headers a mail client sets, and never on the subject line, so two strangers who both write "Invoice" do not land in the same place. A message we accepted but would not store is threaded too, marked with the reason.

A conversation holds both halves — what arrived and what you sent back — and lives as long as the mail it is made of. Delete the last message and the conversation goes with it.

> Only the screen was renamed. The API path `/emails/receiving` is unchanged, and so is every field on it.

## Replying

**You reply from the same name on your sending domain.** A message to `support@inbound.acme.example` is answered as `support@acme.example` — the address the person already knows, and the one their mail client can line up with your DNS.

So replying needs a domain you have verified for sending. Your managed receiving address is ours rather than yours, and its reputation is shared, so it receives and never sends. A team that has verified nothing sees the composer switched off and **Add a domain to reply** in its place; the [Domains reference](https://www.rasket.com/docs/api-reference/domains) is the path out of that.

| Mail arrived at | Your reply goes out as | Their answer comes back to |
| --- | --- | --- |
| `support@acme.example`, receiving at the domain itself | `Acme Support <support@acme.example>` | The same address. There is nothing separate to show you. |
| `support@inbound.acme.example`, receiving at a label | `Acme Support <support@acme.example>` | `support@inbound.acme.example` |
| Your managed receiving address | `support@` one of your verified sending domains | That domain's own receiving address, if it has one. |

The From picker offers any name you have saved at any of your verified sending domains, and nothing else — never an unverified domain, and never the managed address. When the address you pick answers somewhere else, the composer says so underneath it — **Replies will go to support@acme.example** — so you can answer as `sam@acme.example` and still have the next message land in `support`. A reply-to must be an address on a host you receive on; anything else is refused, because a reply-to nobody can answer is worse than none.

- **One `Re:`, never a stack of them**, and we set the headers that keep your answer inside the conversation on their side as well.
- **Reply, reply to everyone, forward, or start something new.** All four are in the inbox app — see [Inbox](https://www.rasket.com/docs/inbox).
- **A reply can carry files.** Up to ten of them, alongside your text, your mailbox's signature if it has one, and the message you are answering, quoted.

> If the domain you reply from does not receive mail and we have no arrival address to fall back on, the reply goes out with no separate replies-go-to at all — so the answer is sent to a domain we do not receive on, and you never see it. Turn receiving on for that domain.

## Why the payload is small

The event carries the envelope and the attachment list, and nothing else. That is a decision rather than an omission, and each half of it has a reason worth knowing:

- **No bodies.** A webhook is retried until your endpoint accepts it, ten attempts over about sixteen hours. A body that was retried for hours is hours of duplicated content arriving at your endpoint, for a message you may not even want.
- **No download links.** A link lives fifteen minutes. One minted at delivery would be dead by the time a retry succeeded, which is a worse failure than no link at all — it looks like it should work.
- **Enough to route.** `received_for`, `from`, `subject` and the attachment list are all there, so the common case needs no call.

> Route on `received_for` — the addresses of yours the message was accepted for — and not on `to`, which is whatever the sender wrote in the header.

## Before the loop can run

Be reachable. There are two ways, and they differ only in which address people write to; everything downstream is identical.

- **Your own domain.** Turn receiving on for a verified domain in an inbound-capable region and publish the one `MX` record we hand back. It sits at the domain itself unless you choose otherwise, so `anything@acme.example` lands with us — but that `MX` takes the domain's mail from whichever mailbox provider has it today, so set `receiving_host` to a label such as `inbound` if another provider already receives for it. `anything@inbound.acme.example` then lands in the same place. We check first: turning receiving on at a name whose `MX` already points at another mailbox provider is refused with `409 receiving_mx_in_use`, naming that provider, until you choose a subdomain or send `confirm_replace_mx: true`.
- **The address you already have.** Every team is assigned a managed receiving address — `anything@<word>.<our host>` — when it is created. Anything before the `@` reaches you, so you can tag threads without registering anything. It is shown while no domain of yours is verified for receiving; mail sent there still arrives after one is.

Receiving is available in two regions, United States (N. Virginia) and Ireland (Dublin). A verified domain outside them cannot receive at all, and its domain page says so in place of an address rather than offering a toggle that would do nothing.

An address on your domain that no mailbox claims reaches you only if you have marked one of your mailboxes as the catch-all. Mark one and everything unclaimed lands there, so a customer writing to a name you never set up is not silently lost. With none marked, or with unclaimed addresses switched off, that mail is recorded as not delivered and nothing is stored. [Inbox](https://www.rasket.com/docs/inbox) has the whole of it, along with who on your team can read which address.

> A message larger than **40 MB, attachments included**, is refused before it reaches us, so there is no event and no record. We do not hold it, and we do not truncate it.

## When the loop gets something odd

A message we accepted but would not store is still recorded, with its bodies null and no attachments, so that "nothing arrived" and "something arrived and we would not keep it" are never the same silence.

| You see | It means |
| --- | --- |
| `dropped_reason: "virus"` | Scanned and refused. The envelope is kept, the content is not. |
| `dropped_reason: "storage_quota"` | Your mailbox was full. Delete some mail, or add storage. |
| `dropped_reason: "daily_cap"` | You had already received your plan's messages for the day. |
| `dropped_reason: "parse_failed"` | We could not read the MIME structure. The raw source is still downloadable. |
| `dropped_reason: "no_mailbox"` | No mailbox claims that address, and no catch-all is set to take it. |
| `dropped_reason: "team_blocked"` | Your account is blocked, so mail is recorded but not stored until that is resolved. |
| `404` | You deleted the message, a dropped record aged out, or it was never this team's. They are deliberately indistinguishable. |

## Storage, and getting it back

Your mail is kept until the mailbox is full. There is no retention window on a message you keep: it stays until you delete it. What bounds it is an allowance in bytes, and a daily message cap alongside it. With the mailbox full, the **next** message is recorded with a `dropped_reason` rather than stored — never an older one thrown away — so the loop keeps running and you find out from the payload.

| Plan | Inbox storage |
| --- | --- |
| Free | 3 GB |
| Pro | 8 GB |
| Scale | 15 GB |
| Enterprise | 40 GB |

Another `100 GB` is $15 a month on any plan, the free one included. Add it with `POST /billing/addons`; it is billed on your subscription and the allowance applies at once.

Every open mailbox adds `5 GB` on top, shared or personal, the ones your plan includes included — and `15 GB` each once you have more mailboxes than your plan includes. Open another and the allowance goes up with it; close one and it goes back down. Extra addresses on a mailbox add nothing, because they are the same mailbox. Nothing to buy separately — it is the same single allowance the table above shows.

When your inbox reaches 80% of its storage, and again when it is full, the team's admins get an email. One per crossing, and never a second inside 30 days.

`DELETE /emails/receiving/{email_id}` is how the room comes back. It removes the stored objects as well as the record and returns the bytes to your allowance; there is no undo and no soft delete. A dropped record is the one thing that does age out, on your plan's data retention window — and like a deleted message it goes whole, so it answers `404` rather than coming back with an empty body.

## Route API replies into your Inbox

An email you send over the API can be answered in the Inbox like any other conversation. Set `reply_to` to a name at an address you receive on, and the answer arrives somewhere we can see it.

| You receive at | Set on the send |
| --- | --- |
| the domain itself | Nothing. Your From address already receives. |
| a label, such as `inbound.acme.example` | `reply_to: "orders@inbound.acme.example"` |
| only your managed receiving address | Nothing you can set: it is not an address you send from. |

Arriving is all `reply_to` does. What puts the answer on the right conversation is the `In-Reply-To` it carries, pointing back at the message you sent — so a `reply_to` at an address we do not receive on threads nothing, because the answer never reaches us at all.

## What a reply costs

A reply is an email. It goes out through the pipeline [`POST /emails`](https://www.rasket.com/docs/api-reference/emails) uses, on the transactional meter, and counts as one message on your plan: a thousand replies are a thousand emails.

| Limit | A reply |
| --- | --- |
| Your plan's monthly transactional allowance | Counts it. One message, like any other. |
| Your daily send cap | Counts it. |
| Your suppression list | Applies. A suppressed address is not written to. |
| The verified-domain rule | Applies. There is no second send path that skips it. |
| Ten requests a second, per team | Applies, as it does to a send you make yourself. |
| Sixty replies an hour, per member | Only here — a guard against a stuck composer, not a ration on your plan. |
| A daily ceiling per personal mailbox | Only for mail you send from your own address: 100 a day, or 500 once your account has a payment method. Nothing else counts it. |

There is one edge worth knowing before you meet it: a reply whose every recipient is on your suppression list is refused *after* the message has been counted. The composer checks suppression before it lets you type, so this only happens to an address suppressed in the seconds between.

What you have sent and what your plan allows are on the dashboard's Usage screen, beside every other send.

Mail you send from **your own address** rather than a shared one is not on that meter at all. You are paying for the seat, not for the message, so a personal mailbox draws on neither your monthly allowance nor your daily cap — it has a daily ceiling of its own: a hundred messages, or five hundred once your account has a payment method. Reaching it is a sign something is sending on your behalf, not a sign you should buy more.

| Limit | A message from your own mailbox |
| --- | --- |
| Your plan's monthly allowance | Does not count it. |
| Your daily send cap | Does not count it. |
| 100 a day per mailbox, 500 with a payment method on your account | The whole of what bounds it. |
| Your suppression list | Applies. A hard bounce or a complaint still stops a message. |
| Someone who unsubscribed | Still hears from you. Leaving a mailing list is not asking a person to stop writing back. |

Write to someone from your own address on a shared conversation and their answer still comes back to the shared mailbox, not to you. That is deliberate: the conversation is the team's, so whoever picks it up next finds the whole of it — including on the days you are away.

## Where to go next

- [The Receiving reference](https://www.rasket.com/docs/api-reference/receiving) — all seven operations, with the shape of every response.
- [Events](https://www.rasket.com/docs/events) — the `email.received` payload in full, beside every other event.
- [Domains](https://www.rasket.com/docs/api-reference/domains) — turning receiving on, and verifying the sending domain a reply needs.
