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.
// 1. verify the webhook, as with every Rasket event
const event = verifyWebhook(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 { data: message } = await rasket.emails.receiving.get(event.data.email_id);
const { data: files } = await rasket.emails.receiving.attachments.list(event.data.email_id);
// 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 rasket.emails.receiving.remove(event.data.email_id);Your Inbox
The same mail is waiting for you in the dashboard, under Inbox. 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 is the path out of that.
| Mail arrived at | Your reply goes out as | Their answer comes back to |
|---|---|---|
support@inbound.acme.example | Acme Support <support@acme.example> | support@inbound.acme.example |
support@acme.example, when you receive at the domain itself | Acme Support <support@acme.example> | The same address. There is nothing separate to show you. |
| 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. Underneath it, Replies go to is shown rather than hidden, and you can point it at any address on a host you receive on: answer as sam@acme.example and keep the next message landing in support. An address we do not receive on 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, or reply to everyone. Starting a new message, or forwarding one, is not something this release does.
- A reply carries no attachments. It is 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. A body that was retried for six hours is six 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,subjectand 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.
- The address you already have. Every team is assigned a managed receiving address —
anything@<word>.<our host>— on first visit to the Receiving screen. Anything before the@reaches you, so you can tag threads without registering anything. - Your own domain. Turn receiving on for a verified domain in an inbound-capable region and publish the one
MXrecord we hand back. It sits at theinboundlabel unless you choose otherwise, soanything@inbound.acme.examplelands in the same place. Setreceiving_hostto another label, or to@foranything@acme.exampleitself — but anMXat@takes the domain's mail from whichever mailbox provider has it today, so choose it only for a domain whose every address should reach us.
Receiving is available in two regions. 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.
A message larger than 150 KB, attachments included, never enters the loop: the sending server is refused before the message 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. |
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. Buy it from Inbox in the dashboard; it is billed on your subscription and the allowance applies at once.
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 |
|---|---|
a label, such as inbound.acme.example | reply_to: "orders@inbound.acme.example" |
| the domain itself | Nothing. Your From address already receives. |
| 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 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. |
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.
Where to go next
- The Receiving reference — all seven operations, with the shape of every response.
- Events — the
email.receivedpayload in full, beside every other event. - Domains — turning receiving on, and verifying the sending domain a reply needs.