> ## Documentation Index
> Fetch the complete documentation index at: https://docs.drin.run/llms.txt
> Use this file to discover all available pages before exploring further.

# Reply in-thread

> POST /v1/emails/{id}/reply answers a message in one call. Drin derives From from the parent inbox, To from the parent sender, prepends Re: to the subject, and sets In-Reply-To and References so mail clients thread the reply correctly.

Answering a message is one call. POST /v1/emails/{id}/reply takes the id of the message you're replying to and derives everything an in-thread reply needs — the From and To addresses, the Re: subject, and the In-Reply-To / References headers — so the recipient's mail client threads it correctly.

A normal send makes you assemble the envelope yourself: pick a verified `from`, set the recipient, re-type the subject with a `Re:` prefix, and hand-write the `In-Reply-To` and `References` headers from the parent's message id. Forget any of those and the reply lands as a fresh thread. The reply endpoint does all of it from one input: the id of the message you are answering.

**`POST /v1/emails/{id}/reply`**

The `{id}` in the path is the parent message — typically the inbound message you got from a [thread](/receiving) or the `inbound_received` webhook. The only required content is a body: pass `html`, `text`, or both.

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.drin.run/v1/emails/msg_in_01/reply \
    -H "Authorization: Bearer $DRIN_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "text": "Thanks for reaching out — order #4182 ships tomorrow.",
      "html": "<p>Thanks for reaching out — order #4182 ships <b>tomorrow</b>.</p>"
    }'
  ```

  ```typescript Node.js theme={null}
  import { DrinClient } from "@drin00/sdk";

  const drin = new DrinClient({ apiKey: process.env.DRIN_API_KEY });

  // id = the message you are answering (e.g. the inbound message from a thread)
  const { id, status } = await drin.emails.reply("msg_in_01", {
    text: "Thanks for reaching out — order #4182 ships tomorrow.",
    html: "<p>Thanks for reaching out — order #4182 ships <b>tomorrow</b>.</p>",
  });

  console.log(id, status); // "msg_out_02", "queued"
  ```

  ```python Python theme={null}
  import os, requests

  requests.post(
      "https://api.drin.run/v1/emails/msg_in_01/reply",
      headers={"Authorization": f"Bearer {os.environ['DRIN_API_KEY']}"},
      json={"text": "Thanks for reaching out — order #4182 ships tomorrow."},
  )
  ```
</CodeGroup>

The reply is queued like any send and returns its new message id. It becomes the next outbound message in the same thread.

```json 202 Accepted theme={null}
{
  "id": "msg_out_02",
  "status": "queued"
}
```

## What Drin sets for you

Send nothing but a body and Drin fills the rest in from the parent message:

* **From** defaults to the parent message's inbox address — the address it was sent to. Your reply comes from the same mailbox the conversation is already on.
* **To** defaults to the parent message's sender, so the reply goes back to whoever wrote in.
* **Subject** defaults to the parent subject with a `Re:` prefix — and is *not* doubled when the subject already starts with `Re:`.
* **In-Reply-To** and **References** are set from the parent's RFC 822 message id and its existing reference chain, so Gmail, Outlook, and Apple Mail collapse the reply into the original conversation instead of starting a new one.

<Note>
  **A body is required.** At least one of `html` or `text` must be present. Everything else is optional — omit it and the derived default applies.
</Note>

## Request body

Every field overrides the value Drin would otherwise derive. Supply only what you want to change.

<ParamField body="html" type="string">
  HTML body of the reply. Required unless `text` is given.
</ParamField>

<ParamField body="text" type="string">
  Plain-text body. Required unless `html` is given. Provide both for the best deliverability.
</ParamField>

<ParamField body="subject" type="string">
  Override the auto-generated `Re:` subject. Omit to keep the threaded default.
</ParamField>

<ParamField body="from" type="object">
  `{ email, name? }`. Override the sending address — useful to hand a conversation to a different inbox (e.g. escalate from `support@` to `billing@`). Must be an address on a domain you own.
</ParamField>

<ParamField body="to" type="array">
  `{ email, name? }[]`. Override the recipients. Defaults to the parent message's sender.
</ParamField>

<ParamField body="cc" type="array">
  `{ email, name? }[]`. Additional copy recipients.
</ParamField>

<ParamField body="attachments" type="array">
  `{ filename, content, contentType? }[]` with `content` base64-encoded.
</ParamField>

## Overriding the defaults

The defaults cover the common case; pass any field to change it. Here a reply is escalated to a different inbox and CC'd to a supervisor — the threading headers are still derived from the parent, so it stays in the same conversation.

```typescript Override From + CC theme={null}
await drin.emails.reply("msg_in_01", {
  text: "Routing this to our billing team.",
  // Override any default Drin would have derived from the parent:
  from: { email: "billing@yourdomain.com", name: "Acme Billing" },
  cc: [{ email: "supervisor@yourdomain.com" }],
});
```

## The agent inbox loop

Reply is the closing move of the receive → read → respond loop. Read a thread, let an agent draft an answer from the conversation, and post it — without ever touching a mail header.

```typescript Receive → reply theme={null}
import { DrinClient } from "@drin00/sdk";

const drin = new DrinClient({ apiKey: process.env.DRIN_API_KEY });

// 1. A webhook handed you an inbound messageId. Read the full thread.
const thread = await drin.threads.get(threadId);
const last = thread.messages.at(-1)!;

// 2. Let an agent draft a reply from the conversation...
const draft = await agent.answer(thread.messages);

// 3. ...and post it. Threading is handled for you — no headers to set.
await drin.emails.reply(last.id, { text: draft });
```

<Tip>
  **Test it without DNS.** Use `POST /v1/inbound/simulate` to inject a synthetic inbound message, then call `reply` on the returned `messageId` to exercise the full loop locally. See [Inbound & threads](/receiving#simulate).
</Tip>

<Card title="Webhooks" icon="bell" href="/webhooks">
  Get pushed every `inbound_received` event so an agent can reply the moment a message lands.
</Card>
