---
title: "Get inbound email by id"
method: GET
path: "/emails/{id}"
tags: ["Emails"]
---

# Get inbound email by id

`GET /emails/{id}`

Returns the full record for an inbound email received at one
of your verified domains, including the parsed text and HTML
bodies, threading metadata, SMTP envelope detail, webhook
delivery state, and a `replies` array for any outbound sends
recorded as replies to this inbound.

For listing inbound emails (with cursor pagination, status
and date filters, and free-text search), use
`/emails`. Outbound (sent) email records are NOT returned
here; use `/sent-emails/{id}` for those.

The response carries four sender-shaped fields whose
meanings overlap. `from_email` is the canonical "who sent
this" field for most use cases (parsed bare address from
the `From:` header, with a `sender` fallback). `from_header`
is the raw header including any display name. `sender` and
`smtp_mail_from` both carry the SMTP envelope MAIL FROM
(return-path) and are equal by construction; `sender` is
the older field name retained for compatibility. See
`primitive describe emails:get-email | jq '.responseSchema.properties'`
for per-field detail.

## Response `200`

Email details

- object
  - `success` true, required
  - `data` object, required
    - `id` string, uuid, required
    - `message_id` string, nullable
    - `domain_id` string, uuid, nullable
    - `org_id` string, uuid, nullable
    - `sender` string, required — SMTP envelope sender (return-path) the inbound mail server accepted. Same value as `smtp_mail_from`; both fields exist so protocol-aware tooling can use whichever name it expects. For most legitimate mail this equals `from_email`; for mailing lists, bounce handlers, and forwarders it is typically the bounce-handling address rather than the human-visible sender. **For the canonical "who sent this email" value, use `from_email`.**
    - `recipient` string, required
    - `subject` string, nullable
    - `body_text` string, nullable — Plain-text body parsed from the inbound MIME, matching the `email.parsed.body_text` field on the webhook payload. Null when the message had no text part or parsing failed.
    - `body_html` string, nullable — HTML body parsed from the inbound MIME, matching the `email.parsed.body_html` field on the webhook payload. Null when the message had no HTML part or parsing failed.
    - `status` 'pending' | 'accepted' | 'completed' | 'rejected', required — Lifecycle status of an INBOUND email (a row in the `emails` table). Distinct from `SentEmailStatus`, which describes the OUTBOUND lifecycle (the `sent_emails` table) and uses a different vocabulary because the lifecycles differ. Possible values: - `pending`: the row was inserted at ingestion (mx_main) and has not yet completed the spam / filter / auth pipeline. Body and parsed fields are present; webhook delivery is not yet scheduled. Most rows transition out of `pending` within seconds. - `accepted`: the inbound passed the policy gates and is queued for webhook delivery. The `webhook_status` field tracks the separate webhook-delivery lifecycle from this point. - `completed`: terminal success. Webhook delivery attempted and acknowledged by every active endpoint, OR no endpoints are configured, so the row is durably archived. - `rejected`: terminal failure at ingestion (spam, blocked sender, filter rule, malformed). The body and metadata are stored for auditing but no webhook fires and the row is not repliable. See also `webhook_status` (separate enum tracking the webhook-delivery state machine) and `SentEmailStatus` (the outbound vocabulary).
    - `domain` string, required
    - `spam_score` number, nullable
    - `raw_size_bytes` integer, nullable
    - `raw_sha256` string, nullable
    - `created_at` string, date-time, required
    - `received_at` string, date-time, required
    - `rejection_reason` string, nullable
    - `webhook_status` 'pending' | 'in_flight' | 'fired' | 'failed' | 'exhausted' | 'null', nullable — Webhook-delivery state for an inbound email. Tracks a SEPARATE lifecycle from the email's `status` field; the same row carries both. Possible values: - `pending`: ingestion is past `pending` (the email itself is `accepted`) but the webhook fan-out has not yet started for this row. - `in_flight`: at least one delivery attempt is in flight. - `fired`: terminal success. Every active endpoint acknowledged the delivery (or accepted it after retries). - `failed`: terminal partial-failure. At least one endpoint exhausted its retry budget; some endpoints may still have succeeded. - `exhausted`: terminal failure. Every endpoint exhausted its retry budget without success. - `null`: no endpoints configured, so no webhook lifecycle applies. Note that the value `pending` here does NOT mean the email is `pending`; it means the email is past ingestion but webhook delivery has not yet begun. Two overlapping uses of the word `pending` for distinct lifecycle phases.
    - `webhook_attempt_count` integer, required
    - `webhook_last_attempt_at` string, date-time, nullable
    - `webhook_last_status_code` integer, nullable
    - `webhook_last_error` string, nullable
    - `webhook_fired_at` string, date-time, nullable
    - `smtp_helo` string, nullable
    - `smtp_mail_from` string, nullable — SMTP envelope MAIL FROM (return-path), as accepted by the inbound mail server. Same value as `sender`; both fields exist so protocol-aware tooling can use whichever name it expects. For the canonical "who sent this email" value (display name stripped, From-header preferred), use `from_email`.
    - `smtp_rcpt_to` string[], nullable
    - `from_header` string, nullable — Raw `From:` header from the message body, including any display name (e.g. `"Alice Example" <alice@example.com>`). Use this when you need the display name for rendering. For the bare email address (display name stripped), use `from_email`.
    - `content_discarded_at` string, date-time, nullable
    - `content_discarded_by_delivery_id` string, nullable
    - `from_email` string, required — Bare email address parsed from the `From:` header, with display name stripped (e.g. `alice@example.com`). Falls back to `sender` (the SMTP envelope MAIL FROM) when the `From:` header cannot be parsed. **This is the canonical "who sent this email" field for most use cases**, including comparing against allowlists, routing replies, or displaying the sender to a user. Use `from_header` when you specifically need the display name, or `sender`/`smtp_mail_from` when you need the SMTP envelope value (e.g. to follow a bounce).
    - `to_email` string, required — Parsed to address (same as recipient)
    - `from_known_address` boolean — True when the inbound's sender address has a matching grant in the org's known-send-addresses list. Advisory: a true value does not by itself guarantee that a reply will be accepted by send-mail's gates; the per-send check at send time remains authoritative.
    - `replies` object[], required — Sent emails recorded as replies to this inbound, in send order (ascending). Populated when a customer's send-mail request carries an `in_reply_to` Message-ID that matches this inbound's `message_id` in the same org. Includes attempts that were gate-denied, so the array reflects every recorded reply attempt regardless of outcome.
      - `id` string, uuid, required — Sent-email row id.
      - `status` 'queued' | 'submitted_to_agent' | 'agent_failed' | 'gate_denied' | 'unknown' | 'delivered' | 'bounced' | 'deferred' | 'wait_timeout', required — Lifecycle status of a sent_emails row. Possible values: - `queued`: pre-call INSERT; the outbound agent has not yet replied. - `submitted_to_agent`: agent accepted; `queue_id` is set. - `agent_failed`: agent rejected; `error_code` and `error_message` carry the reason. - `gate_denied`: a recipient-scope gate denied the send; the agent was never called. The `gates` array carries the denial detail. /send-mail returns 403 in this case so callers see the denial synchronously; /sent-emails additionally records the row for historical lookup, which is when this status appears in a listing. - `unknown`: terminal indeterminate; the on-box log poller couldn't classify the receiver's response. - `delivered` / `bounced` / `deferred` / `wait_timeout`: terminal delivery outcomes (see DeliveryStatus).
      - `to_address` string, required — Recipient address as recorded on the sent_emails row.
      - `subject` string, nullable
      - `created_at` string, date-time, required
      - `queue_id` string, nullable — Outbound relay queue identifier when available.
    - `reply_to_sent_email_id` string, uuid, nullable — The `sent_emails.id` of the outbound this inbound was a reply to, when resolvable. Set at inbound ingest by matching the parsed In-Reply-To (or References, as a fallback) against `sent_emails.message_id` in the same org. The mirror of `sent_emails.in_reply_to_email_id` for the inbound side of a thread. NULL when the inbound is not a threaded reply to one of your sends, when neither header survived the path through intermediate MTAs, or on inbound received before this auto-link landed.
    - `thread_id` string, uuid, nullable — Conversation thread this message belongs to. Inbound and outbound messages in the same conversation share a `thread_id`; fetch `/threads/{thread_id}` for the full ordered thread. Assigned at ingest. NULL on messages received before threading was enabled (until backfilled).
    - `parsed` object, required — Parsed MIME content for an inbound email. Mirrors the `email.parsed` object on the webhook payload so a single parser handles both surfaces. `status` is `complete` when parsing succeeded; on `failed` the body/address/attachment fields are absent and `error` describes why.
      - `status` 'complete' | 'failed', required
      - `body_text` string, nullable — Plain-text body. Present when `status` is `complete`.
      - `body_html` string, nullable — HTML body. Present when `status` is `complete`.
      - `reply_to` object[], nullable — Parsed `Reply-To` header addresses.
        - `name` string, nullable — Display name, when present (e.g. `Alice Example`).
        - `address` string, required — Bare email address (e.g. `alice@example.com`).
      - `cc` object[], nullable — Parsed `Cc` header addresses.
        - `name` string, nullable — Display name, when present (e.g. `Alice Example`).
        - `address` string, required — Bare email address (e.g. `alice@example.com`).
      - `bcc` object[], nullable — Parsed `Bcc` header addresses (rarely present on inbound).
        - `name` string, nullable — Display name, when present (e.g. `Alice Example`).
        - `address` string, required — Bare email address (e.g. `alice@example.com`).
      - `to_addresses` object[], nullable — Parsed `To` header addresses.
        - `name` string, nullable — Display name, when present (e.g. `Alice Example`).
        - `address` string, required — Bare email address (e.g. `alice@example.com`).
      - `in_reply_to` string[], nullable — Message-IDs from the `In-Reply-To` header.
      - `references` string[], nullable — Message-IDs from the `References` header.
      - `attachments` object[] — Attachment metadata. Empty array when none.
        - `filename` string, nullable
        - `content_type` string, nullable
        - `size_bytes` integer, required
        - `sha256` string, nullable
        - `part_index` integer — Zero-based index of this part within the message.
      - `error` object, nullable — Present (non-null) only when `status` is `failed`. When present, all three fields are populated, so a consumer can branch on `code` without defensive null checks.
        - `code` string, required — Stable failure code (e.g. `PARSE_FAILED`).
        - `message` string, required
        - `retryable` boolean, required
    - `auth` object, required — SPF / DKIM / DMARC verdicts computed at ingest. Mirrors the `email.auth` object on the webhook payload. Field names are camelCase to match that payload exactly. For messages received before auth was recorded, the verdicts default to `none`.
      - `spf` string, required — SPF result (e.g. `pass`, `fail`, `softfail`, `none`).
      - `dmarc` string, required — DMARC result (e.g. `pass`, `fail`, `none`).
      - `dmarcPolicy` string, nullable — Published DMARC policy (`none`, `quarantine`, `reject`).
      - `dmarcFromDomain` string, nullable — The From-header domain DMARC was evaluated against.
      - `dmarcSpfAligned` boolean, required
      - `dmarcDkimAligned` boolean, required
      - `dmarcSpfStrict` boolean, nullable
      - `dmarcDkimStrict` boolean, nullable
      - `dkimSignatures` object[], required
        - `domain` string, required
        - `selector` string, required
        - `result` string, required — Verification result (e.g. `pass`, `fail`, `none`).
        - `aligned` boolean, required — Whether the signing domain aligns with the From domain (for DMARC).
        - `keyBits` integer, nullable
        - `algo` string, nullable

## Other responses

- `400` — Invalid request parameters
- `401` — Invalid or missing API key
- `404` — Resource not found

---

[API](https://skmtc.dev/primitive/apis/primitive-api.md) · [All operations](https://skmtc.dev/primitive/apis/primitive-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/primitive/primitive-api/revisions/c2299d28022c/schema)
