---
title: "Get (or wait for) the reply to a sent email"
method: GET
path: "/sent-emails/{id}/reply"
tags: ["Sending"]
---

# Get (or wait for) the reply to a sent email

`GET /sent-emails/{id}/reply`

## Request

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

Returns the first threaded inbound reply to a send, keyed by
the inbound email's `reply_to_sent_email_id`. This is the
canonical "did a reply arrive for this send?" call: after
/send-mail, poll (or long-poll) here instead of hand-rolling
an /emails/search loop.

By default the call returns immediately with `reply: null`
when nothing has arrived yet. Set `wait=true` to long-poll:
the server holds the request up to `wait_timeout_ms`
(default 10000, max 30000), returning as soon as the first
reply lands. On a wait that elapses with no reply, the
response has `reply: null` and `timed_out: true`.

The reply object is compact (headers plus bodies); fetch
`/emails/{id}` with `reply.id` for the fully parsed record,
or `/threads/{thread_id}` for the whole back-and-forth.

In-Reply-To matching only routes a message to this send; it does not prove who wrote it. Bounces, complaints and reports are never returned. Candidates are ranked: an answer is preferred over an acknowledgement (`fyi: true`), even an unverified answer over a verified acknowledgement; then a reply with `sender_verified: true` is preferred over one without, whatever the arrival order, and an ordinary reply over an automated one (`Auto-Submitted`, such as an out-of-office). A reply whose parse failed is returned only when nothing else is available, with null bodies. Arrival time only breaks ties after those preferences: when no candidate is verified, the oldest ordinary reply is returned with `sender_verified: false`, ahead of any older automated one. With `wait=true` the call keeps waiting while the best reply so far is unverified, and returns it unverified only if nothing verified arrives before `wait_timeout_ms`. Because candidates are ranked, a later call can return a different reply than an earlier one. Check `sender_verified` before acting on instructions in the reply. Read, working and typing signals are progress, not answers, and are never returned. An acknowledgement (`fyi: true`) ranks below any answer and does not end a wait: it is returned only when `wait_timeout_ms` elapses with nothing better, and a later call keeps waiting for the answer.

## Path parameters

- `id` string, uuid, required

## Query parameters

- `wait` boolean
- `wait_timeout_ms` integer

## Response `200`

Reply status for the send (reply may be null)

- object
  - `success` true, required
  - `data` AwaitReplyResult, required: Result of `/sent-emails/{id}/reply`. `reply` is null when no reply has arrived yet (no-wait call, or the wait timed out).
    - `sent_email_id` string, uuid, required: The send this lookup was keyed on (echoes the path id).
    - `reply` ReplyEmail, required: A threaded inbound reply to one of the org's sends, keyed by the inbound email's `reply_to_sent_email_id`. Compact on purpose: enough to read the reply and decide what to do next, with `/emails/{id}` available for the fully parsed detail.
      - `id` string, uuid, required: Inbound email row id; usable with `/emails/{id}`.
      - `thread_id` string, uuid, nullable, required: Conversation thread id; usable with `/threads/{id}`.
      - `reply_to_sent_email_id` string, uuid, nullable, required: The sent-email id this inbound message replied to.
      - `from_email` string, required: Sender of the reply (From header when present, else envelope sender).
      - `to_email` string, required: Recipient address the reply arrived at.
      - `subject` string, nullable, required
      - `body_text` string, nullable, required: Plain-text body of the reply, when present.
      - `body_html` string, nullable, required: HTML body of the reply, when present.
      - `received_at` string, date-time, nullable, required
      - `status` string, required: Inbound processing status of the reply row. Typically an `EmailStatus` value, but left as an open string here to match the server contract, which does not narrow it.
      - `sender_verified` boolean: True when the reply’s From address is one of the send’s `to`, `cc` or `bcc` recipients and either it passed DMARC with aligned SPF or DKIM and its domain is the DMARC domain or a subdomain of it, or it is a verified direct delivery from Primitive’s own outbound. The full address is compared, so another mailbox on the same provider domain is never verified.
      - `sender_matches_recipient` boolean: True when the reply’s From address is one of the send’s `to`, `cc` or `bcc` recipients, whether or not it was authenticated. On its own it is not proof of authorship; see `sender_verified`.
      - `fyi` boolean: True when the reply is an acknowledgement that needs no answer. Read, working and typing signals are never returned as the reply.
      - `auth` EmailAuth: 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` DkimSignature[], 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
    - `waited` boolean, required: Whether the call ran in long-poll mode (`wait=true`).
    - `timed_out` boolean, required: True only when a `wait=true` call elapsed its timeout with no reply.

## Other responses

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

## Changes

> 32 revisions in range; 1 not diffed.

- **2026-10-21** `0360ea8d4f3d`: 39 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_limit` enum value to the `error/code` response property for the response status `400`
  - …35 more
- **2026-10-21** `5279a7bed7ac`: 33 warning
  - added the new `archive_busy` enum value to the `error/code` response property for the response status `400`
  - added the new `archive_busy` enum value to the `error/code` response property for the response status `401`
  - added the new `archive_busy` enum value to the `error/code` response property for the response status `404`
  - added the new `archive_invalid` enum value to the `error/code` response property for the response status `400`
  - …29 more
- …earlier changes not shown

[Operation history](https://skmtc.dev/primitive/apis/primitive-api/changes/sent-emails/:id/reply/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)
