---
title: "Simulate an inbound email (test key)"
method: POST
path: "/v1/test/inbound"
tags: ["Inbound"]
---

# Simulate an inbound email (test key)

`POST /v1/test/inbound`

## Request

- Base URL: `https://api.mails.ai`
- URL: `https://api.mails.ai/v1/test/inbound`
- Auth: HTTP bearer

**Requires scope:** `send`. **Test key only** (`mk_test_…`). Drives the receive/classify half of the product with a hand-supplied envelope: runs the real classifier, threads the message, persists a received row, and emits the same typed event + webhook a real inbound would. Set `in_reply_to_message_id` to a prior test-send id to simulate a `reply.received`. Send a whole email as `raw_base64` (up to 3 MB) to test attachments: `from`, `subject` and `body_text` then default to the email's own.

## Request body

- TestInboundRequest
  - `agent` string, required: The agent that receives the message: its name or `agt_` id. An agent the workspace does not have is a 404 `agent_not_found`, a key tied to another agent gets a 403, and a name two live agents share is a 409 `agent_name_conflict`.
  - `from` string, email: Sender address. Required unless raw_base64 is sent, in which case it defaults to the raw email's From.
  - `to` string: The address the message was sent to, kept as its `delivered_to`: any address real mail reaches this agent at (see the agent's `receiving_address`), such as the one a test signed up with. Left out, it is the agent's `receiving_address`. Any other address is a 400 `invalid_param_value`.
  - `from_name` string: Sender display name, up to 256 characters. Left out, it is the raw email's From name when `raw_base64` is sent; otherwise the message has none.
  - `subject` string: Subject line, up to 2,000 characters. Left out, it is the raw email's Subject when `raw_base64` is sent; otherwise the message has none.
  - `body_text` string: Message text. Required unless raw_base64 is sent, in which case it defaults to the raw email's text.
  - `raw_base64` string: A complete raw email (RFC 822), base64-encoded, up to 3 MB decoded. Its attachments become downloadable exactly as on real mail; from, subject and body_text default to the raw email's own.
  - `in_reply_to_message_id` string: Id (`msg_…`) of one of this agent's sent messages, to simulate a reply to it. The message is then marked `is_thread_reply` and raises `reply.received`, unless `spf` and `dkim` are both `fail`. Left out, or naming any other message, it arrives as first contact.
  - `spf` 'pass' | 'fail' | 'unknown': Pretend SPF result, taken as given (default pass).
  - `dkim` 'pass' | 'fail' | 'unknown': Pretend DKIM result, taken as given (default pass). With spf, both `fail` simulates `message.received.unauthenticated`. Real mail gets that event when no DKIM signature aligned with the From domain verifies and the From domain's DMARC policy is quarantine or reject.

## Response `201`

The simulated inbound message and its classification.

- TestInboundResult
  - `message_id` string, required: Id of the received message this call stored (`rcv_…`); read it at `GET /v1/messages/received/{id}`.
  - `thread_id` string, required: The thread the message was filed in (`thrd_…`), chosen as for real mail: that of the message named by `in_reply_to_message_id` when it is this agent's, else an open thread of the agent's with the same subject that the sender is already in, else a new one. It never joins a thread holding live mail.
  - `event_id` string, required: Id of the event this call raised (`evt_…`), sent to your webhooks with `test_mode` true; read it at `GET /v1/events/{id}`.
  - `event_type` 'message.received' | 'reply.received' | 'message.received.unauthenticated', required: `reply.received` when `in_reply_to_message_id` named one of this agent's sent messages, `message.received` otherwise, and `message.received.unauthenticated` whenever `spf` and `dkim` are both `fail`.
  - `quarantined` boolean, required: True when `classification.injection_score` is 0.95 or more. The stored message then has `parse_status` `quarantined`.
  - `test_mode` boolean, required: Always `true`: the message and its event are test mail, which is never billed.
  - `has_attachment` boolean: True when the message carries at least one file attachment (embedded images do not count). Only a `raw_base64` email can carry one.
  - `attachments` ReceivedAttachment[]: The raw email's attachments, when `raw_base64` was sent.
    - `id` string, required: Attachment id (`att_…`), unique within the message.
    - `filename` string, required: The file's name, cleaned: no path, no control characters, at most 255 characters.
    - `content_type` string, required: The MIME type the sender declared, e.g. `application/pdf`.
    - `size` integer, required: Size in bytes of the decoded file.
    - `content_id` string, nullable, required: The part's Content-ID without angle brackets, which the HTML body references as `cid:`.
    - `inline` boolean, required: True for an image embedded in the HTML body rather than attached as a file.
  - `classification` Classification, required
    - `intent` string: What the message wants, as the classifier read it. Fixed values when it was not read for intent: `general_reply` for a reply in a thread from a sender that passed authentication, `unknown` when a rule settled it (failed authentication, in a thread or not, or a message under 80 characters from a Gmail, Outlook, Hotmail or Yahoo address), `unclassified` when the agent's `classify_inbound` is off or the classifier could not run.
    - `entities` object: Extracted entities as key→value pairs (object) — present when intent classification ran.
    - `urgency` number: Urgency score 0–1 (higher = more time-sensitive).
    - `injection_score` number, required: How likely the message is a prompt-injection attempt, from 0 to 1; at 0.95 or more it is quarantined. 0.5, with `scan_unavailable` in `injection_categories`, when the scan could not run.
    - `injection_categories` string[]: The kinds of injection the scan found; empty when it found none. `auth_fail` marks a sender that failed authentication (the message's `auth_failed` is true), in a thread or not, and `scan_unavailable` a scan that could not run.
    - `sender_reputation` number: Trust score for the sender, 0 to 1 (higher is more trusted): the classifier's estimate when it read the message for intent, otherwise a preset, such as 0.3 for a sender that failed authentication.
    - `classifier_model` string: What scored the message: a model name; a value starting `rules-layer` when a rule settled the intent; `injection-scan-only`; or `scan-unavailable` when the scan could not run.

## Other responses

- `400`: Invalid request — malformed JSON or a field failed validation. Also `feature_not_enabled` when this server has a feature switched off (custom domains before they are available).
- `401`: Missing or invalid API key.
- `403`: The API key lacks the required scope (`insufficient_scope`), or the token belongs to an app connected by sign-in and this operation is closed to connected apps (`connected_app_not_allowed`).
- `404`: Resource not found in this workspace, or the agent a send names is archived (`agent_archived`).
- `409`: Conflict — duplicate resource, an in-progress idempotent request, a draft or scheduled message that started sending while it was being changed, or a thread deleted for good while one of its messages is being sent (`duplicate_resource`); or an agent name more than one agent has (`agent_name_conflict`: use the agent's id); or a status change on an archived agent, since archiving cannot be undone (`agent_archived`).
- `413`: Payload too large (`body_too_large`): `raw_base64` decodes to more than 3 MB, the most this route takes.
- `429`: Rate or quota limit exceeded. The body's `resets_at` and `retry_after_seconds` say when the request can succeed. A `Retry-After` header is sent only when that is 60 seconds or less; a longer wait (an hourly, daily or monthly cap) sends none, so retry at `resets_at` or raise the limit instead of sleeping.
- `500`: Internal server error.

---

[API](https://skmtc.dev/mails/apis/mails-ai-api.md) · [All operations](https://skmtc.dev/mails/apis/mails-ai-api/llms.txt) · [OpenAPI document](https://skmtc.dev/mails/apis/mails-ai-api/revisions/4d4930a8c33b?raw)
