---
title: "Reply in a thread"
method: POST
path: "/inbox/threads/{thread_id}/reply/"
tags: ["inbox"]
---

# Reply in a thread

`POST /inbox/threads/{thread_id}/reply/`

## Request

- Base URL: `https://api.getoneprofile.ai` (the document declares no server; this is the origin it was published from)
- URL: `https://api.getoneprofile.ai/inbox/threads/{thread_id}/reply/`
- Auth: one of:
  - API key in header `X-API-Key`
  - HTTP bearer

Sends from the mailbox that owns the conversation, so the prospect always sees a reply from the address that emailed them. Free of credits. Attachments ride on the email (multipart form). `channel` overrides which channel the reply goes out on when both identities are known; absent, the thread's own channel decides. A pick the thread cannot honour returns 422 with the reason. `to` redirects the reply (first address is the To line, the rest ride as CC) and `bcc` rides blind; both are email-only. `omit_signature` skips the mailbox signature append, for a composer whose body already carries it as visible text.

## Path parameters

- `thread_id` string, uuid, required

## Response `201`

Successful Response

- MessageReadSchema
  - `attachments` AttachmentReadSchema[], required
    - `content_id` string, nullable: The file's Content-ID, without its angle brackets, or null when it has none. The message's HTML names an image shown inside it as cid:<content_id>, but mail clients set a Content-ID on ordinary attachments too, so one alone does not mean the file is shown inside the message: only the HTML naming it does.
    - `content_type` string, required
    - `filename` string, required
    - `id` string, uuid, required
    - `size_bytes` integer, required
  - `authentication_result` string, nullable: The receiving mail server's verdict on whether a message really came from the sender's domain, by DMARC or SPF: `pass`, `fail`, `none` when the server gave no verdict, or `unknown` when its verdict was neither. The agent only answers an email on its own when it passed. Null when no verdict was recorded, such as on LinkedIn messages and on messages sent from Oneprofile.
  - `body_html` string, nullable: The sender's raw HTML part; sanitise before rendering.
  - `body_text` string, required
  - `bounce_severity` 'hard' | 'soft', required: Hard vs soft severity of a BOUNCE message. Only HARD marks the contact bounced (the Bounced condition, the enrolment analytics bounce rate, the per-message outcome). SOFT still fills the Bounces view and never looks like a reply, but the address may still be reachable, so the cadence is left alone.
  - `channel` 'email' | 'linkedin', required: Which surface a conversation happens on. Threads are contact-scoped, not channel-scoped, so this says how a thread is REACHED rather than partitioning the inbox: a reply arrives, is classified and stops the cadence identically either way, and only the send path differs.
  - `direction` 'outbound' | 'inbound', required
  - `files_pending` boolean, required: True while the files this email came with are still being read from the mailbox, so `attachments` may not list them yet: it fills in once they are read, and the conversation updates on its own when that happens. False on every other message, including one whose files are already listed and one that came with none.
  - `from_address` string, required
  - `id` string, uuid, required
  - `mailbox_id` string, uuid, nullable, required
  - `message_class` 'reply' | 'bounce' | 'out_of_office' | 'automated' | 'forward', required: What a message turned out to be; NULL on an ordinary message of ours. REPLY stops the cadence; BOUNCE (a DSN) marks the contact bounced and never looks like a reply; OUT_OF_OFFICE never stops anything and defers the cadence to the stated return when one is parsed; AUTOMATED is a machine writing back (a ticketing auto-ack, a mailing list, a no-reply robot), and like OUT_OF_OFFICE it never stops a cadence, because nobody read the mail. The first four are inbound. FORWARD is ours, outbound: a message of the thread passed on to somebody else (``service.forward_message``). Nothing was said to the contact, so it is never an answer to them: it does not move a conversation out of the Inbox, count as replied, or end a done mark.
  - `send_state` 'sent' | 'queued' | 'failed', required: Whether a message has actually left. Only meaningful on LinkedIn. An email row is written after the provider accepted it, so it is ``SENT`` the moment it exists; a LinkedIn reply is written when it is QUEUED, and cannot become ``SENT`` until the customer's own browser has delivered it. Showing the two the same way would have the product assert a delivery it has no evidence for.
  - `sent_at` string, date-time, required
  - `sent_by_kind` 'person' | 'system': Who composed an outbound message: a person, or the product itself. Only meaningful outbound, and NULL on anything inbound. The distinction exists because an agent-written reply has to stay attributable after the fact: "did a human send this" is the first question asked of any send that turns out to have been wrong, and a column is the only place that answer survives.
  - `sequence_id` string, uuid, nullable: The sequence this message was sent from, when it came from one.
  - `step_id` string, uuid, nullable: Which step sent this message, carried per-message (not just on the thread) because a contact re-enrolled in a different sequence keeps their earlier messages' own sequence/step, distinct from the thread's current one.
  - `subject` string, required
  - `thread_id` string, uuid, required
  - `to_address` string, required

## Other responses

- `401`: Unauthorized
- `403`: Forbidden
- `404`: Not found.
- `422`: Validation failed.
- `500`: Internal server error.
- `502`: The mailbox could not send.
- `503`: Service unavailable.

---

[API](https://skmtc.dev/getoneprofile/apis/api-reference-oneprofile.md) · [All operations](https://skmtc.dev/getoneprofile/apis/api-reference-oneprofile/llms.txt) · [OpenAPI document](https://skmtc.dev/getoneprofile/apis/api-reference-oneprofile/revisions/4a6bd71e3f02?raw)
