---
title: "Get one sample email without authentication"
method: GET
path: "/demo/emails/{id}"
tags: ["Demo"]
---

# Get one sample email without authentication

`GET /demo/emails/{id}`

Public, no-auth demo. Returns a single synthetic email shaped exactly like the authenticated `GET /emails/{id}` response (`EmailDetail` schema). No real tenant data is exposed. Rate-limited per IP.

## Path parameters

- `id` string, required

## Response `200`

Synthetic email detail, same shape as the authenticated /emails/{id} response.

- object
  - `success` true, required
  - `demo` true, required
  - `note` string — Reminder that the data is synthetic.
  - `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

- `429` — Rate limit exceeded

---

[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)
