---
title: "Reply in thread"
description: "Reply, reply-all and forward from an inbox, keep conversations threaded with In-Reply-To and References, prepare replies as drafts, and label whole threads."
canonical: "https://machinemail.dev/docs/guides/reply-in-thread"
---

# Reply in thread

> Reply, reply-all and forward from an inbox, keep conversations threaded with In-Reply-To and References, prepare replies as drafts, and label whole threads.

Source: https://machinemail.dev/docs/guides/reply-in-thread

Email threading depends on two headers: `In-Reply-To` names the message being answered, and `References` lists the conversation so far. MachineMail sets both for you when you use the reply endpoints, and uses them to group inbound mail into threads.

## Reply

```bash title="Terminal"
curl https://api.machinemail.dev/v0/inboxes/support-agent@machinemail.dev/messages/msg_01m4fy8han0000y3ge3m73vw3w/reply \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "Got it, thanks.", "html": "<p>Got it, thanks.</p>"}'
```

```json title="Response (201)"
{
  "message_id": "msg_01m4fy8hbz0000k3m19n91j9va",
  "thread_id": "thd_01m4fy8h340001pp6c74x4688h",
  "timestamp": "2026-10-09T09:00:16.127Z"
}
```

What the endpoint does:

- **Recipients.** For an inbound message it replies to the message's `reply_to` addresses if present, otherwise to `from`. For one of your own sent messages it goes to that message's `to`. Pass `to` to override.
- **Subject.** `Re: ` plus the original subject, unless it already starts with `Re:`. A `subject` in the body is ignored.
- **Headers.** `In-Reply-To` is the original's `Message-ID`, and `References` is the original's references plus that ID.
- **Thread.** The reply joins the original's thread, so `thread_id` matches.

The body accepts the same fields as a send except `subject`: `text`, `html`, `to`, `cc`, `bcc`, `reply_to`, `labels`, `attachments` and `headers`. See [Sending](https://machinemail.dev/docs/concepts/sending).

The key needs `message_reply` or `message_send`.

**TypeScript**

```ts title="reply.ts"
const inbox = 'support-agent@machinemail.dev';
const res = await fetch(`https://api.machinemail.dev/v0/inboxes/${inbox}/messages/${messageId}/reply`, {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.MACHINEMAIL_API_KEY}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ text: 'Got it, thanks.' }),
});
const { message_id, thread_id } = await res.json();
```

**Python**

```python title="reply.py"
import os, requests

inbox = "support-agent@machinemail.dev"
res = requests.post(
    f"https://api.machinemail.dev/v0/inboxes/{inbox}/messages/{message_id}/reply",
    headers={"Authorization": f"Bearer {os.environ['MACHINEMAIL_API_KEY']}"},
    json={"text": "Got it, thanks."},
)
sent = res.json()
```

## Reply all

`POST .../messages/{message_id}/reply-all` sends to the original `from` and `to`, with the original `cc` copied, all minus your own inbox address.

```bash title="Terminal"
curl https://api.machinemail.dev/v0/inboxes/support-agent@machinemail.dev/messages/msg_01m4fy8han0000y3ge3m73vw3w/reply-all \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "Looping everyone in."}'
```

> **Note:** Reply-all computes `to` and `cc` from the original message and ignores `to` and `cc` in the request. You can still add `bcc`. To choose recipients yourself, use `reply` with `to` and `cc`.

## Forward

`POST .../messages/{message_id}/forward` requires `to`. The subject defaults to `Fwd: ` plus the original subject; pass `subject` to change it. Your `text` comes first, followed by a forwarded-message header (From, Date, Subject, To) and the original plain-text body.

```bash title="Terminal"
curl https://api.machinemail.dev/v0/inboxes/support-agent@machinemail.dev/messages/msg_01m4fy8han0000y3ge3m73vw3w/forward \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"to": ["billing@acme.dev"], "text": "Can you handle this one?"}'
```

A forward starts a new thread. The original's attachments are not copied; pass `attachments` if the recipient needs them. See [Attachments](https://machinemail.dev/docs/guides/attachments).

> **Warning:** Forwarding passes untrusted content to someone else. If the original is `suspicious`, forward it to a human reviewer only, never to an address the message itself asked for.

## How threading works

Every message you send gets a `Message-ID` of the form `<msg_id@inbox-domain>`, for example `<msg_01m4fy8h340000kzb2kp66c161@machinemail.dev>`.

When mail arrives, MachineMail looks at its `In-Reply-To` and `References` headers and searches the same inbox for a message with a matching `Message-ID`, or for one of its own `msg_` IDs. If it finds one, the new message joins that thread. Otherwise it starts a new thread.

So an inbound reply to your message lands in your thread, and the message records the link:

```json title="Inbound reply (trimmed)"
{
  "message_id": "msg_01m4fy8han0000y3ge3m73vw3w",
  "thread_id": "thd_01m4fy8h340001pp6c74x4688h",
  "from": "ada@example.com",
  "subject": "Re: Hello",
  "in_reply_to": "msg_01m4fy8h340000kzb2kp66c161@machinemail.dev",
  "direction": "inbound"
}
```

Threads are per inbox. If two of your inboxes receive the same conversation, each has its own thread.

### Threading a plain send

If you need to answer a message without the reply endpoint, for example a message you only know by its RFC `Message-ID`, set the headers on a normal send. MachineMail resolves the thread from them the same way:

```bash title="Terminal"
curl https://api.machinemail.dev/v0/inboxes/support-agent@machinemail.dev/messages/send \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": ["ada@example.com"],
    "subject": "Re: Hello",
    "text": "Following up.",
    "headers": {
      "In-Reply-To": "<abc123@example.com>",
      "References": "<abc123@example.com>"
    }
  }'
```

`headers` is a map of extra header names to values and is passed through to the outgoing message.

## Reply as a draft

To have a person review a reply before it goes out, create a draft with `in_reply_to` set to the MachineMail `message_id` you are answering. When the draft is sent it gets the right `In-Reply-To` and `References` headers and joins the thread.

```bash title="Terminal"
curl https://api.machinemail.dev/v0/inboxes/support-agent@machinemail.dev/drafts \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": ["ada@example.com"],
    "subject": "Re: Hello",
    "text": "Here is the refund you asked for.",
    "in_reply_to": "msg_01m4fy8han0000y3ge3m73vw3w"
  }'
```

Drafts do not set the subject or recipients for you, so fill in `to` and `subject` yourself. Send it with `POST /v0/inboxes/{inbox_id}/drafts/{draft_id}/send`, or set `send_at` to schedule it. Every unsent draft is listed in the dashboard at `/app/drafts`, where a person can send or delete it. See [Drafts](https://machinemail.dev/docs/concepts/drafts).

## Read and label a whole thread

`GET /v0/threads/{thread_id}` returns the thread summary and every message in it, oldest first. The same thread is also at `/v0/inboxes/{inbox_id}/threads/{thread_id}`.

```bash title="Terminal"
curl https://api.machinemail.dev/v0/threads/thd_01m4fy8h340001pp6c74x4688h \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
```

```json title="Response (trimmed)"
{
  "thread_id": "thd_01m4fy8h340001pp6c74x4688h",
  "inbox_id": "support-agent@machinemail.dev",
  "labels": ["SENT", "INBOX", "UNREAD"],
  "senders": ["support-agent@machinemail.dev", "ada@example.com"],
  "recipients": ["ada@example.com", "support-agent@machinemail.dev"],
  "subject": "Hello",
  "last_message_id": "msg_01m4fy8hbz0000k3m19n91j9va",
  "message_count": 3,
  "messages": [{ "message_id": "msg_01m4fy8han0000y3ge3m73vw3w" }, { "message_id": "msg_01m4fy8h340000kzb2kp66c161" }]
}
```

Thread labels are the union of the labels on its messages. `PATCH /v0/threads/{thread_id}` applies `add_labels` and `remove_labels` to every message in the thread, which is how you mark a whole conversation read:

```bash title="Terminal"
curl -X PATCH https://api.machinemail.dev/v0/threads/thd_01m4fy8h340001pp6c74x4688h \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"remove_labels": ["UNREAD"], "add_labels": ["resolved"]}'
```

Changing thread labels needs `message_update`. `DELETE /v0/threads/{thread_id}` deletes every message in the thread and needs `thread_delete`.

## Related

- [Threads and messages](https://machinemail.dev/docs/concepts/threads-and-messages)
- [Sending](https://machinemail.dev/docs/concepts/sending)
- [Drafts](https://machinemail.dev/docs/concepts/drafts)
- [Give an agent an inbox](https://machinemail.dev/docs/guides/give-an-agent-an-inbox)
