---
title: "Search inbound emails"
method: GET
path: "/emails/search"
tags: ["Emails"]
---

# Search inbound emails

`GET /emails/search`

Searches inbound emails with structured filters and optional
full-text matching across parsed email fields. This endpoint is
optimized for filtered inbox views and CLI polling workflows:
callers that only need new accepted mail can pass
`sort=received_at_asc`, `snippet=false`, `include_facets=false`,
and a `date_from` timestamp.

`q`, `subject`, and `body` use the same English full-text index
as the web inbox search. Structured filters such as `from`, `to`,
`domain_id`, status, attachment presence, and spam score bounds
are combined with the text query.

## Query parameters

- `q` string
- `from` string
- `to` string
- `subject` string
- `body` string
- `domain_id` string, uuid
- `reply_to_sent_email_id` string, uuid
- `status` 'pending' | 'accepted' | 'completed' | 'rejected' — 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).
- `date_from` string, date-time
- `date_to` string, date-time
- `has_attachment` 'true' | 'false'
- `spam_score_lt` number
- `spam_score_gte` number
- `sort` 'relevance' | 'received_at_desc' | 'received_at_asc'
- `cursor` string
- `limit` integer
- `snippet` 'true' | 'false'
- `include_facets` 'true' | 'false'

## Response `200`

Search results

- 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
    - `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).
    - `sender` string, required — SMTP envelope sender (return-path) the inbound mail server accepted. For most legitimate mail this equals the bare address in the From header; for mailing lists, bounce handlers, and forwarders it is typically the bounce address rather than the human-visible sender. For the parsed From-header value (with display name handling and a sender-fallback when the header is unparseable), GET the email by id and use `from_email`.
    - `recipient` string, required
    - `subject` string, nullable
    - `domain` string, required
    - `spam_score` number, nullable
    - `created_at` string, date-time, required
    - `received_at` string, date-time, required
    - `raw_size_bytes` integer, 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
    - `thread_id` string, uuid, nullable — Conversation thread this message belongs to. Fetch `/threads/{thread_id}` for the full ordered thread. NULL on messages received before threading was enabled.
    - `attachment_count` integer, required — Number of parsed attachments on the email.
    - `from_known_address` boolean, required — Whether the parsed From address is known to this org from prior authenticated inbound mail.
    - `score` number — Relevance score. Present only when sorting by relevance.
    - `highlights` object
      - `subject` string[], required — Subject snippets with matching terms highlighted.
      - `body` string[], required — Body snippets with matching terms highlighted.
  - `meta` object, required
    - `total` integer, required — Total number of matching records, capped when `total_capped` is true.
    - `total_capped` boolean, required — Whether `total` was capped instead of counted exactly.
    - `limit` integer, required — Page size used for this request.
    - `cursor` string, nullable, required — Cursor for the next search page, or null if no more results.
    - `sort` 'relevance' | 'received_at_desc' | 'received_at_asc', required — Sort mode used for the result page.
  - `facets` object
    - `by_sender` object[], required
      - `value` string, nullable, required
      - `count` integer, required
    - `by_domain` object[], required
      - `value` string, nullable, required
      - `count` integer, required
    - `by_status` object[], required
      - `value` string, nullable, required
      - `count` integer, required
    - `has_attachment` object, required
      - `true` integer, required
      - `false` integer, required

## Other responses

- `400` — Invalid request parameters
- `401` — Invalid or missing API key
- `504` — Primitive timed out while waiting for a downstream operation.

---

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