Skip to content
Skip to main content

How to reply to an email thread

Threading replies correctly looks trivial — until you do it wrong and your message lands as a brand-new email in the recipient’s inbox instead of nesting under the original conversation. This recipe shows how to send a reply that threads in every major mail client, what reply_to_message_id actually does, and how to pick the right message to reply to when a thread has more than one.

The same flow works whether the reply is composed by a human in your UI, generated by an LLM, or pushed in from another part of your platform (a signed contract, a deal update, a templated follow-up). It also works for Agent Accounts — the endpoint is the same, only the grant changes.

How threading actually works (and why subject matching breaks)

Section titled “How threading actually works (and why subject matching breaks)”

Email clients group messages into conversations using two headers: In-Reply-To (the Message-ID of the message being replied to) and References (the full chain of Message-IDs in the conversation). When those headers are missing, mail clients fall back to subject-line matching, but that’s unreliable — recipients edit subjects, forwarded messages reuse them, and two unrelated conversations can share the same subject.

You don’t manage those headers yourself. When you call /messages/send with reply_to_message_id, Nylas reads the original message, populates In-Reply-To and References on the outbound, and the reply nests correctly in the recipient’s mail client. The deeper mechanics are in Email threading for agents — same protocol, written from the agent perspective but applies to any send path.

You’ll need:

  • A Nylas application with a valid API key.
  • A grant for the user whose mailbox the reply is being sent from. For most products this is the end user’s connected Google, Microsoft, or IMAP account.
  • Scopes that include both reading messages and sending. For Google that’s typically gmail.send plus gmail.readonly. For Microsoft, Mail.Send plus Mail.Read.

Step 1: List threads for the user to pick from

Section titled “Step 1: List threads for the user to pick from”

Fetch the user’s recent threads so your UI can render a picker. The response includes latest_draft_or_message, so you can show a sender, subject, snippet, and timestamp without a separate Messages call.

Each thread carries id, subject, participants, snippet, latest_message_received_date, and latest_draft_or_message — enough to render a useful picker without any further calls.

For a more focused picker, filter to threads the user actually corresponds with. Two common refinements:

  • Filter by participant. Pass [email protected] to scope to threads with a specific person. Handy when your product already knows which contact the user is working with.
  • Restrict to unread or recent. Pass unread=true to show only threads with new messages, or use received_after / received_before (Unix epoch seconds) to constrain by date.

For Google accounts you can also use Gmail search operators through search_query_native — see the Gmail threads recipe for the full operator list.

A thread is a non-linear collection of messages. When the user picks a thread in your UI, your code needs to pick a specific message to reply to — that’s what creates the threading link, not the thread itself.

Most of the time you want the most recent message. The thread response gives you that directly:

// From the thread you got in step 1
const threadId = thread.id;
const replyToMessageId = thread.latest_draft_or_message.id;
const subject = thread.latest_draft_or_message.subject;
const to = thread.latest_draft_or_message.from; // reply goes back to the sender

If you need a specific earlier message — for example, replying to the message that contained the original request — pull the thread’s message_ids array and let the user pick:

Then fetch any specific message by its ID to show the user what they’d be replying to.

Send the message with reply_to_message_id set to the ID you picked in step 2. Nylas adds In-Reply-To and References headers automatically, and the reply threads correctly in the recipient’s mail client.

A few details worth knowing:

  • Subject convention. Nylas does not auto-prefix Re:. Set the subject yourself. Most products either reuse the original subject verbatim or prefix it with Re: if it doesn’t already start with one.
  • Recipients. Nylas does not auto-fill to either. Use original_message.from for a normal reply, or build the recipient list yourself for a reply-all that includes the original to and cc.
  • The sent message lands in the same thread on the sender’s side too. It shows up in the user’s Sent folder and is grouped into the thread when they open it. No extra work needed for that.

Step 4: Attach files to the reply (optional)

Section titled “Step 4: Attach files to the reply (optional)”

To attach a file to the reply — a signed contract, a generated PDF, a CSV export — add an attachments array to the send request. The Nylas SDKs ship a FileUtils / file_utils helper that builds the attachment object from a file path; for raw bytes (a PDF generated in memory, for example) you can build the attachment object directly with filename, content_type, and base64 content.

For files larger than 3MB and up to 25MB, switch to multipart/form-data. The send attachments recipe walks through the multipart pattern in detail.

When the mailbox is owned by your platform — an automated assistant, an AI agent, a system-of-record inbox — swap the user’s grant ID for an Agent Account grant. The send endpoint and reply_to_message_id work the same way.

The differences sit on either side of the send call:

  • The agent needs the message ID from somewhere. For a thread the agent started, it already has the thread_id and message_id from when it sent the original outbound. For inbound conversations the agent is responding to, the message.created webhook payload includes both.
  • Sending fires message.created for the outbound too. If you have a webhook handler watching the agent mailbox, filter on msg.from so the agent doesn’t trigger itself. See Handle email replies in an agent loop.
  • Thread state mapping lives in your app. Agent Accounts hold conversation context across long-running threads using a mapping from thread_id to internal state — a task, a workflow step, a session. See Email threading for agents for the durable pattern.
  • Don’t fall back to subject-line matching. Some integrations match conversations by subject because it “usually works.” It doesn’t, reliably — recipients edit subjects, multiple threads share subjects, and forwarded messages keep the subject but change the conversation. reply_to_message_id is the right answer.
  • Use the message’s from for the reply destination, not the thread’s participants. The thread’s participants array is the union of everyone in the conversation. If you reply to the participants list directly, you’ll end up sending the reply to yourself among others. Pull the recipient list from the specific message you’re replying to.
  • Check that the thread is still active. A user might pick a six-month-old thread from the picker. Surface the latest_message_received_date in the UI so it’s clear when the conversation last moved.
  • One outbound, one reply. Don’t send the same payload twice if the user clicks “Send” twice — debounce on the client and dedupe by reply_to_message_id + content hash on the server. Email is fire-and-forget on the wire, and the recipient will see both copies.
  • The reply shows up in your mailbox too. The message you sent and any future replies to it land in the same thread. If your product surfaces the thread again later, the reply is already there.