# Receiving

The mail sent to your team: the message, its attachments, its raw source, and the one call that deletes all three.

## A received message is its own resource

It is never an `emails` row. The two have separate ID spaces and separate reads: `GET /emails/{email_id}` will not find a received message, and `GET /emails/receiving/{email_id}` will not find one you sent. Nor does `POST /emails/{id}/share` accept a received ID — sharing inbound mail is not something this release does, and it answers `404`.

- `created_at` is when the message was *received*, not when we wrote the record.
- Route on `received_for`, the addresses of yours the message was accepted for. `to` is whatever the sender wrote, and a sender can write anything.
- `html` and `text` are stored exactly as decoded and are never sanitised at rest. Sanitise before you render them.

> The `received` figure on [Metrics](https://www.rasket.com/docs/api-reference/metrics) is the count of messages the mail provider accepted from you for *sending*. It has nothing to do with inbound mail.

## When a message has no content

A message we accepted but would not store is still recorded, with a `dropped_reason`, `html` and `text` null, and no attachments. The record exists so you can tell "nothing arrived" from "something arrived and we would not keep it", which are very different problems.

| dropped_reason | Means |
| --- | --- |
| `storage_quota` | Your mailbox was full. Delete some mail, or add storage. |
| `daily_cap` | You had already received your plan's messages for the day. |
| `virus` | The message was scanned and refused. We do not store it. |
| `parse_failed` | We could not read the MIME structure. The raw source is still downloadable. |
| `no_mailbox` | No mailbox claims that address, and no catch-all is set to take it. |
| `team_blocked` | Sending and receiving are paused on the project. We emailed its admins why, and how to ask us to look again. |

A message you keep has no retention window: it is held until you delete it, or until the mailbox is full and the *next* message is the one dropped. A dropped record is the exception — it ages out on your plan's data retention window, and like a deleted message it is removed *whole*, so it answers `404` rather than coming back emptied.

## Attachments and the links to them

The attachments listed inside a message carry no link — minting one per part on every read of every message would be work nobody asked for. Read `…/{email_id}/attachments` when you want the bytes, and each part comes back with a `download_url` valid for fifteen minutes.

- A part we refused to store is listed with its size and **no** `download_url` and no `expires_at`. Check for the field rather than assuming it.
- A download takes no `Authorization` header: the signed link is the credential, so a browser can follow it without an API key ever reaching one.
- Bytes are always served as a download, and a content type outside our allowlist — `text/html` and `image/svg+xml` among them — is handed over as `application/octet-stream`.

## The Inbox does not move any of this

Received mail is also a conversation in the dashboard, and a member can answer it there. None of that reaches this resource: `/emails/receiving` keeps its paths, its shapes and its permissions, the `email.received` payload keeps its fields, and no thread resource was added. A reply is an ordinary send and appears on `/emails` like any other. [Replying](https://www.rasket.com/docs/receiving#replying) in the guide is the whole of it.

## Endpoints

### `GET /emails/receiving`

Every message sent to this team, newest first.

#### Query parameters

| Field | Type | Description |
| --- | --- | --- |
| `limit` | integer | How many items to return, 1–100. Defaults to 20. |
| `after` | string | Return the page that follows this item ID. Mutually exclusive with `before`. |
| `before` | string | Return the page that precedes this item ID. Mutually exclusive with `after`. |
| `search` | string | Case-insensitive substring of the subject, the sender's address or name, or any recipient. `%` and `_` match literally. 1–200 characters. |
| `start_date` | string | ISO 8601, inclusive. Messages received before this are excluded. |
| `end_date` | string | ISO 8601, inclusive. Messages received after this are excluded. |

List received emails:

```sh
curl -X GET "https://api.rasket.com/emails/receiving?limit=20" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"
```

```ts
const response = await fetch("https://api.rasket.com/emails/receiving?limit=20", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.RASKET_API_KEY}`,
    "User-Agent": "acme-billing/1.0",
  },
});

const data = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://api.rasket.com/emails/receiving?limit=20",
    headers={
        "Authorization": f"Bearer {os.environ['RASKET_API_KEY']}",
        "User-Agent": "acme-billing/1.0",
    },
)

print(response.json())
```

#### Response `200`

```json
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "id": "4ef9a417-02e9-4d39-ad75-9611e0fcc33c",
      "to": ["support@inbound.acme.example"],
      "from": "Ronald Williams <ronald.williams@example.com>",
      "subject": "Order #1042 arrived damaged",
      "message_id": "<CAF1042.damaged@mail.example.com>",
      "bcc": [],
      "cc": ["billing@example.com"],
      "reply_to": [],
      "dropped_reason": null,
      "created_at": "2026-09-09T10:16:44.902Z",
      "attachments": [
        {
          "id": "660e8400-e29b-41d4-a716-446655440000",
          "filename": "damage-report.pdf",
          "content_type": "application/pdf",
          "content_disposition": "attachment",
          "size": 20481
        }
      ]
    }
  ]
}
```

- Cursors are received-email IDs. A row carries no `html`, `text`, `headers` or `received_for`; retrieve the message for those.
- A message we recorded but would not store carries a `dropped_reason` and an empty `attachments` array.

### `GET /emails/receiving/{email_id}`

One message, with its headers, bodies and attachment list.

#### Path parameters

| Field | Type | Description |
| --- | --- | --- |
| `email_id` (required) | string | The ID of the received email. |

Retrieve a received email:

```sh
curl -X GET "https://api.rasket.com/emails/receiving/4ef9a417-02e9-4d39-ad75-9611e0fcc33c" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"
```

```ts
const response = await fetch("https://api.rasket.com/emails/receiving/4ef9a417-02e9-4d39-ad75-9611e0fcc33c", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.RASKET_API_KEY}`,
    "User-Agent": "acme-billing/1.0",
  },
});

const { id } = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://api.rasket.com/emails/receiving/4ef9a417-02e9-4d39-ad75-9611e0fcc33c",
    headers={
        "Authorization": f"Bearer {os.environ['RASKET_API_KEY']}",
        "User-Agent": "acme-billing/1.0",
    },
)

id = response.json()["id"]
```

#### Response `200`

```json
{
  "object": "email",
  "id": "4ef9a417-02e9-4d39-ad75-9611e0fcc33c",
  "to": ["support@inbound.acme.example"],
  "from": "Ronald Williams <ronald.williams@example.com>",
  "subject": "Order #1042 arrived damaged",
  "message_id": "<CAF1042.damaged@mail.example.com>",
  "bcc": [],
  "cc": ["billing@example.com"],
  "reply_to": [],
  "received_for": ["support@inbound.acme.example"],
  "html": "<p>The box was crushed on one side.</p>",
  "text": "The box was crushed on one side.",
  "headers": {
    "X-Mailer": "Apple Mail (2.3774)"
  },
  "dropped_reason": null,
  "created_at": "2026-09-09T10:16:44.902Z",
  "attachments": [
    {
      "id": "660e8400-e29b-41d4-a716-446655440000",
      "filename": "damage-report.pdf",
      "content_type": "application/pdf",
      "content_disposition": "attachment",
      "size": 20481
    }
  ]
}
```

- `created_at` is when the message was **received**, not when we wrote the record.
- Route on `received_for` — the addresses of yours the message was accepted for — rather than on `to`, which is whatever the sender wrote.
- `subject` and `message_id` are empty strings when the message carried no such header.
- `html` and `text` are never sanitised at rest. Sanitise before you render them.
- The raw source is not included. `…/raw/download` serves it as a file.
- A message you keep has no retention window; it is held until you delete it. A dropped record does age out, and like a deleted message it goes whole and answers `404` — there is no emptied record.

### `GET /emails/receiving/{email_id}/attachments`

Each with a signed link to its bytes.

#### Path parameters

| Field | Type | Description |
| --- | --- | --- |
| `email_id` (required) | string | The ID of the received email. |

#### Query parameters

| Field | Type | Description |
| --- | --- | --- |
| `limit` | integer | How many items to return, 1–100. Defaults to 20. |
| `after` | string | Return the page that follows this item ID. Mutually exclusive with `before`. |
| `before` | string | Return the page that precedes this item ID. Mutually exclusive with `after`. |

List the attachments of a received email:

```sh
curl -X GET "https://api.rasket.com/emails/receiving/4ef9a417-02e9-4d39-ad75-9611e0fcc33c/attachments" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"
```

```ts
const response = await fetch("https://api.rasket.com/emails/receiving/4ef9a417-02e9-4d39-ad75-9611e0fcc33c/attachments", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.RASKET_API_KEY}`,
    "User-Agent": "acme-billing/1.0",
  },
});

const data = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://api.rasket.com/emails/receiving/4ef9a417-02e9-4d39-ad75-9611e0fcc33c/attachments",
    headers={
        "Authorization": f"Bearer {os.environ['RASKET_API_KEY']}",
        "User-Agent": "acme-billing/1.0",
    },
)

print(response.json())
```

#### Response `200`

```json
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "id": "660e8400-e29b-41d4-a716-446655440000",
      "filename": "damage-report.pdf",
      "content_type": "application/pdf",
      "content_disposition": "attachment",
      "size": 20481,
      "download_url": "https://api.rasket.com/emails/receiving/4ef9a417-02e9-4d39-ad75-9611e0fcc33c/attachments/660e8400-e29b-41d4-a716-446655440000/download?expires=1789200000&token=1f0c…",
      "expires_at": "2026-09-09T10:31:44.902Z"
    }
  ]
}
```

- Cursors are attachment IDs; the page is in the order the parts appeared in the message.
- A part we refused to store is listed with its size and **no** `download_url` and **no** `expires_at`. Check for the field before following it.
- `download_url` is valid for fifteen minutes. Mint a fresh one by reading the attachment again rather than storing the link.

### `GET /emails/receiving/{email_id}/attachments/{attachment_id}`

One part, with a fresh signed link.

#### Path parameters

| Field | Type | Description |
| --- | --- | --- |
| `email_id` (required) | string | The ID of the received email. |
| `attachment_id` (required) | string | The ID of the attachment. |

Retrieve one attachment of a received email:

```sh
curl -X GET "https://api.rasket.com/emails/receiving/4ef9a417-02e9-4d39-ad75-9611e0fcc33c/attachments/5f2c9a1b-7e3d-4c8a-9b1f-2e6d0a4c8b73" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"
```

```ts
const response = await fetch("https://api.rasket.com/emails/receiving/4ef9a417-02e9-4d39-ad75-9611e0fcc33c/attachments/5f2c9a1b-7e3d-4c8a-9b1f-2e6d0a4c8b73", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.RASKET_API_KEY}`,
    "User-Agent": "acme-billing/1.0",
  },
});

const { id } = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://api.rasket.com/emails/receiving/4ef9a417-02e9-4d39-ad75-9611e0fcc33c/attachments/5f2c9a1b-7e3d-4c8a-9b1f-2e6d0a4c8b73",
    headers={
        "Authorization": f"Bearer {os.environ['RASKET_API_KEY']}",
        "User-Agent": "acme-billing/1.0",
    },
)

id = response.json()["id"]
```

#### Response `200`

```json
{
  "object": "attachment",
  "id": "660e8400-e29b-41d4-a716-446655440000",
  "filename": "damage-report.pdf",
  "content_type": "application/pdf",
  "content_disposition": "attachment",
  "size": 20481,
  "download_url": "https://api.rasket.com/emails/receiving/4ef9a417-02e9-4d39-ad75-9611e0fcc33c/attachments/660e8400-e29b-41d4-a716-446655440000/download?expires=1789200000&token=1f0c…",
  "expires_at": "2026-09-09T10:31:44.902Z"
}
```

- `content_id` is present only for a part that carried a `Content-ID`; resolve `cid:` references in the HTML body against it.

### `GET /emails/receiving/{email_id}/attachments/{attachment_id}/download`

Follow the signed link and get the bytes.

#### Path parameters

| Field | Type | Description |
| --- | --- | --- |
| `email_id` (required) | string | The ID of the received email. |
| `attachment_id` (required) | string | The ID of the attachment. |

#### Query parameters

| Field | Type | Description |
| --- | --- | --- |
| `expires` (required) | integer | Part of the signature. Copy the whole `download_url`; do not build this yourself. |
| `token` (required) | string | The link's signature, valid for fifteen minutes and for this object only. |

Download a received attachment:

```sh
curl -X GET "https://api.rasket.com/emails/receiving/4ef9a417-02e9-4d39-ad75-9611e0fcc33c/attachments/5f2c9a1b-7e3d-4c8a-9b1f-2e6d0a4c8b73/download?expires=1789200000&token=1f0c9d3b8a72e5461c0d" \
  -H "User-Agent: acme-billing/1.0"
```

```ts
const response = await fetch("https://api.rasket.com/emails/receiving/4ef9a417-02e9-4d39-ad75-9611e0fcc33c/attachments/5f2c9a1b-7e3d-4c8a-9b1f-2e6d0a4c8b73/download?expires=1789200000&token=1f0c9d3b8a72e5461c0d", {
  method: "GET",
  headers: {
    "User-Agent": "acme-billing/1.0",
  },
});

console.log(response.status);
```

```python
import os

import requests

response = requests.get(
    "https://api.rasket.com/emails/receiving/4ef9a417-02e9-4d39-ad75-9611e0fcc33c/attachments/5f2c9a1b-7e3d-4c8a-9b1f-2e6d0a4c8b73/download?expires=1789200000&token=1f0c9d3b8a72e5461c0d",
    headers={
        "User-Agent": "acme-billing/1.0",
    },
)

print(response.status_code)
```

- This route takes no `Authorization` header: the link carries its own signed authorization so a browser can follow it.
- A `User-Agent` is still required, as it is on every other route.
- Every refusal is a `404` with the same message — a bad signature, an expired link, an object that has been deleted, an ID from another team. The link is the credential, so which one it was is not something its holder has proven they may know.
- The response is the file itself, not JSON. It is always served as a download, and a type outside our allowlist — `text/html` and `image/svg+xml` first among them — is handed over as `application/octet-stream`.

### `GET /emails/receiving/{email_id}/raw/download`

The message exactly as it reached us, as `raw.eml`.

#### Path parameters

| Field | Type | Description |
| --- | --- | --- |
| `email_id` (required) | string | The ID of the received email. |

#### Query parameters

| Field | Type | Description |
| --- | --- | --- |
| `expires` (required) | integer | Part of the signature. Copy the whole `download_url`; do not build this yourself. |
| `token` (required) | string | The link's signature, valid for fifteen minutes and for this object only. |

Download the raw message:

```sh
curl -X GET "https://api.rasket.com/emails/receiving/4ef9a417-02e9-4d39-ad75-9611e0fcc33c/raw/download?expires=1789200000&token=1f0c9d3b8a72e5461c0d" \
  -H "User-Agent: acme-billing/1.0"
```

```ts
const response = await fetch("https://api.rasket.com/emails/receiving/4ef9a417-02e9-4d39-ad75-9611e0fcc33c/raw/download?expires=1789200000&token=1f0c9d3b8a72e5461c0d", {
  method: "GET",
  headers: {
    "User-Agent": "acme-billing/1.0",
  },
});

console.log(response.status);
```

```python
import os

import requests

response = requests.get(
    "https://api.rasket.com/emails/receiving/4ef9a417-02e9-4d39-ad75-9611e0fcc33c/raw/download?expires=1789200000&token=1f0c9d3b8a72e5461c0d",
    headers={
        "User-Agent": "acme-billing/1.0",
    },
)

print(response.status_code)
```

- The raw source is deliberately not part of the message body: it can be as large as the whole message, and a reader who wants it wants a file.
- This route takes no `Authorization` header: the link carries its own signed authorization so a browser can follow it.
- A `User-Agent` is still required, as it is on every other route.
- Every refusal is a `404` with the same message — a bad signature, an expired link, an object that has been deleted, an ID from another team. The link is the credential, so which one it was is not something its holder has proven they may know.
- The response is the file itself, not JSON. It is always served as a download, and a type outside our allowlist — `text/html` and `image/svg+xml` first among them — is handed over as `application/octet-stream`.

### `DELETE /emails/receiving/{email_id}`

Remove the message and give the storage back.

#### Path parameters

| Field | Type | Description |
| --- | --- | --- |
| `email_id` (required) | string | The ID of the received email. |

Delete a received email:

```sh
curl -X DELETE "https://api.rasket.com/emails/receiving/4ef9a417-02e9-4d39-ad75-9611e0fcc33c" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"
```

```ts
const response = await fetch("https://api.rasket.com/emails/receiving/4ef9a417-02e9-4d39-ad75-9611e0fcc33c", {
  method: "DELETE",
  headers: {
    Authorization: `Bearer ${process.env.RASKET_API_KEY}`,
    "User-Agent": "acme-billing/1.0",
  },
});

const { id } = await response.json();
```

```python
import os

import requests

response = requests.delete(
    "https://api.rasket.com/emails/receiving/4ef9a417-02e9-4d39-ad75-9611e0fcc33c",
    headers={
        "Authorization": f"Bearer {os.environ['RASKET_API_KEY']}",
        "User-Agent": "acme-billing/1.0",
    },
)

id = response.json()["id"]
```

#### Response `200`

```json
{
  "object": "email",
  "id": "4ef9a417-02e9-4d39-ad75-9611e0fcc33c",
  "deleted": true
}
```

- Permanent, and there is no soft delete: the stored objects go with the record.
- The bytes return to this team's inbox storage allowance. It is the only way to make room short of buying more.
- This is the one receiving operation a restricted `sending_access` key can never reach — as, in fact, none of them can.
