---
title: "Read what an inbound email says"
method: GET
path: "/emails/{id}/readable"
tags: ["Emails"]
---

# Read what an inbound email says

`GET /emails/{id}/readable`

## Request

- Base URL: `https://api.primitive.dev/v1`
- URL: `https://api.primitive.dev/v1/emails/{id}/readable`
- Auth: HTTP bearer

Returns an inbound email as text a person or a model can read: the words of the message in reading order, without markup, hidden content, link targets or the quoted history below a reply. For a marketing email this can be a hundred times smaller than the record from `GET /emails/{id}`, which you still need for the stored bodies, headers, authentication results or delivery state.

Each link is a `[n]` marker beside its label. `link_count` says how many there are; ask for targets with `links`.

A long body comes in pages of at most `max_chars` characters, ending on a line break where one is in reach. When `body_next_offset` is not null, send it as `offset` to read on.

The access rules of `GET /emails/{id}` apply. When content is withheld (for example a sender your filter blocked), the identifying fields are returned with an empty `body_text` and a `body_source` of `none`.

## Query parameters

- `max_chars` integer
- `offset` integer
- `links` string

## Response `200`

The readable email

- object
  - `success` true, required
  - `data` EmailReadable, required: What an inbound email says, as plain text, with the fields that identify it.
    - `id` string, uuid, required: The inbound email id.
    - `thread_id` string, uuid, nullable, required: Its conversation, once it has been placed in one.
    - `received_at` string, date-time, required: When it was received.
    - `from` string, required: The From header with its display name, or the bare sender address when there is none.
    - `to` string, required: The address it was delivered to.
    - `subject` string, nullable, required: The subject line.
    - `parse_status` 'pending' | 'processing' | 'complete' | 'failed' | 'null', nullable, required: How far parsing has got, as on `GET /emails/{id}`. While it is `pending` or `processing`, the body, `thread_id` and attachments may be missing or change, so read again shortly. `complete` is final. `failed` is final too: parsing ended without them, so they may be empty. Null on an email older than this field, and before parsing is recorded.
    - `preheader` string, nullable, required: The preview line a mail client shows beside the subject, hidden at the top of the body. Not repeated in `body_text`.
    - `body_text` string, nullable, required: One page of the readable body: the words of the email in reading order. Each link is a `[n]` marker beside its label, without its target (see `links`). Hidden content and the quoted history below a reply are left out. Empty when there is nothing to read or the body is not shown (a blocked sender, or a parse that failed after the body was accepted). Null when your access to the mailbox shows headers only (metadata depth): this field carries message content.
    - `body_source` 'html' | 'text' | 'none', required: The stored body the text came from, or `none` when there was nothing to read.
    - `body_chars` integer, required: Length in characters of the whole readable body, across every page.
    - `body_offset` integer, required: Where this page starts in the readable body: the `offset` sent, or 0.
    - `body_next_offset` integer, nullable, required: The `offset` of the next page. Null at the end of the body.
    - `body_incomplete` boolean, required: True when the HTML body was nested too deeply to read to the end and there was no text body to use: the readable body stops early. Use the detail read for the rest.
    - `quoted_chars_removed` integer, required: Characters of quoted history (earlier messages a reply repeats) removed.
    - `image_count` integer, required: Images in the body. Many images with little text means the email says most of it in pictures.
    - `link_count` integer, required: Different links the whole readable body refers to, across every page.
    - `links` EmailReadableLink[]: Link targets. Present only when the request carried `links`.
      - `n` integer, required: The number in this link's `[n]` marker in `body_text`.
      - `url` string, required: The target as the email wrote it. It can be long, and can be a tracking redirect.
    - `attachments` EmailReadableAttachment[], required: The attached files. Empty when there are none or the content is withheld.
      - `filename` string, nullable, required: The name the sender gave it, if any.
      - `content_type` string, nullable, required: The declared media type, such as `application/pdf`.
      - `size_bytes` integer, nullable, required: Size of the decoded file.
      - `part_index` integer, nullable, required: Its position among the attachments.

## Other responses

- `400`: Invalid request parameters
- `401`: Invalid or missing API key
- `404`: Resource not found
- `429`: Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.

## Changes

> 32 revisions in range; 1 not diffed.

- **2026-11-03** `bce409c97acb`: 1 breaking, 124 warning
  - the response property `allOf[subschema #2]/data/body_text` became nullable for the status `200`
  - added the new `access_mode_unavailable` enum value to the `error/code` response property for the response status `400`
  - added the new `access_mode_unavailable` enum value to the `error/code` response property for the response status `401`
  - added the new `access_mode_unavailable` enum value to the `error/code` response property for the response status `404`
  - …121 more
  - …this revision’s changelog is incomplete
- **2026-10-21** `0360ea8d4f3d`: 52 warning
  - added the new `agent_changed` enum value to the `error/code` response property for the response status `400`
  - added the new `agent_changed` enum value to the `error/code` response property for the response status `401`
  - added the new `agent_changed` enum value to the `error/code` response property for the response status `404`
  - added the new `agent_changed` enum value to the `error/code` response property for the response status `429`
  - …48 more
- …earlier changes not shown

[Operation history](https://skmtc.dev/primitive/apis/primitive-api/changes/emails/:id/readable/get.md)

---

[API](https://skmtc.dev/primitive/apis/primitive-api.md) - [All operations](https://skmtc.dev/primitive/apis/primitive-api/llms.txt) - [OpenAPI document](https://skmtc.dev/primitive/apis/primitive-api/revisions/bce409c97acb?raw)
