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.
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);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 at theinboundlabel.anything@inbound.acme.examplethen lands in the same place.
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 inbound storage was full. Delete some mail, or raise the cap. |
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 | Retention removed the message, or it was never this team's. The two are deliberately indistinguishable. |
Storage, and getting it back
Received mail counts against your plan's inbound storage, and there is a daily message cap as well. Over either, new messages are recorded with a dropped_reason rather than stored — so the loop keeps running and you find out from the payload.
DELETE /emails/receiving/{email_id} is the only way to free inbound storage before retention does. It removes the stored objects as well as the record and returns the bytes to your quota; there is no undo and no soft delete. Retention removes what you leave behind, whole — a message past it answers 404 rather than coming back with an empty body.
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.