# Email threading explained

Source: https://developer.nylas.com/docs/cookbook/email/email-threading-explained/

Every mail provider groups related messages differently, and that difference leaks into your app the moment you try to show a conversation. Gmail hands you a native thread ID on every message. Outlook leans on a `conversationId` plus `References` headers. Plain IMAP gives you `In-Reply-To` and `References` headers and expects you to stitch the chain together yourself. Build directly against three providers and you write three threading models.

This page explains how threading works on each provider, how a single threads endpoint collapses those models into one JSON shape, and how to send a reply that nests under the original conversation instead of starting a new one.

## What is email threading and how does an email API handle threads in Gmail?

Email threading groups messages that belong to the same conversation so a reply appears under the original instead of as a separate item. Gmail tracks this with a native thread ID on every message. The Nylas Email API exposes that grouping through `GET /v3/grants/{grant_id}/threads`, which returns one `thread` object per conversation across all 6 supported providers.

Gmail's model is the cleanest of the providers. Every message carries a stable `threadId`, and the Gmail API endpoint `users.threads.get` returns the full chain in one call. The API maps that native ID onto its own `thread_id` field, which appears on every message object: "Every message is associated with a thread, whether that thread contains one message or many." That single field is what lets you regroup messages without parsing `References` headers by hand, which is exactly what the other providers force on you.

## How does email threading help an AI agent understand conversation context?

An AI agent needs the full back-and-forth, not one isolated message, to answer correctly. A thread object bundles that context: the `subject`, every participant, the `message_ids` for the whole chain, and the `snippet` of the most recent message. Pulling one thread instead of paging the mailbox hands the model a complete conversation in a single request.

When an agent drafts a reply or summarizes a deal, missing earlier messages cause wrong answers: the model contradicts an agreement made three messages back. Fetching `GET /v3/grants/{grant_id}/threads/{thread_id}` returns `message_ids` for the entire conversation plus one expanded `latest_draft_or_message`, so you feed the model the IDs it needs and expand only the bodies that matter. The `earliest_message_date` and `latest_message_received_date` fields, both Unix timestamps, let the agent reason about timing, like whether a 14-day-old quote is still open. This grouping is the difference between an agent that tracks a conversation and one that answers each message blind.

## How threading works across Gmail, Outlook, and IMAP

Threading is provider-specific underneath the unified shape. Gmail assigns a native thread ID per message. Microsoft groups by `conversationId` and `References` headers. IMAP and Exchange rely on `In-Reply-To` and `References` headers, which clients populate inconsistently. The threads endpoint reads each model and returns one normalized object, so you write a single parser instead of three.

The table below maps each native model to the unified field. The middle columns show what you'd handle calling each provider directly; the right column is the single shape the API returns.

| Concept | Gmail API | Microsoft / IMAP | **Nylas threads endpoint** |
| --- | --- | --- | --- |
| Conversation ID | native `threadId` | `conversationId` / `References` | **`thread_id` on every message** |
| Read a conversation | `users.threads.get` | header chain reconstruction | **`GET /v3/grants/{grant_id}/threads/{thread_id}`** |
| Message list in thread | `messages[]` on thread | parse `In-Reply-To` chain | **`message_ids` array** |
| Reply in-thread | set `threadId` on send | set `References` header | **`reply_to_message_id` on send** |
| Providers covered | Gmail only | one provider each | **all 6 providers** |

## How do I list and read threads with one endpoint?

Send `GET /v3/grants/{grant_id}/threads` to list conversations, then `GET /v3/grants/{grant_id}/threads/{thread_id}` to read one. The list call accepts the same filters as messages, including `subject`, `any_email`, `unread`, `starred`, and `latest_message_after`, and it returns the same normalized object across all 6 providers, so one parser handles Gmail, Outlook, IMAP, and Exchange together.

The request below lists the 5 most recent threads for a grant. Use it to power a conversation view, where each row is one thread rather than one message. The `limit` parameter caps the page; pair it with `page_token` from the response to page through the rest.

```bash
curl --request GET \
  --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/threads?limit=5' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>'
```

To read one full conversation, pass the thread ID. The response carries `participants`, `subject`, `snippet`, the `message_ids` for the whole chain, and a `latest_draft_or_message` object expanded inline, so a single call gives you enough to render a conversation header without fetching every body.

```bash
curl --request GET \
  --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/threads/<THREAD_ID>' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>'
```

For the field-by-field walkthrough, see [read a single message or thread](/docs/cookbook/email/get-message-thread/).

## What is the best way to expose email threading data to users inside a SaaS app?

Render the thread, not the mailbox. List with `GET /v3/grants/{grant_id}/threads` so each row is one conversation, show `subject` and `snippet` in the row, then expand the body on click using the `message_ids`. This mirrors how Gmail and Outlook present mail, so the 1 view feels familiar to every user regardless of provider.

A thread object is built for this layout. The `snippet` holds the first 100 characters of the last received message for a preview line, `unread` drives the bold-row styling, and `has_attachments` lets you show a paperclip without a second call. To list every message inside an opened thread, send `GET /v3/grants/{grant_id}/messages?thread_id=<THREAD_ID>` and render each body. One honest tradeoff: if your product only ever connects Gmail and you want pixel-level control over Gmail's label model, calling `users.threads.list` directly avoids an abstraction layer. The moment a second provider appears, that choice means a second threading model to maintain.

## How do I reply inside an existing thread?

Send `POST /v3/grants/{grant_id}/messages/send` with `reply_to_message_id` set to the message you're answering. That single field sets the `In-Reply-To` and `References` headers correctly for you, so the reply nests under the original conversation in Gmail, Outlook, and standards-compliant IMAP clients instead of landing as a brand-new email in the recipient's inbox.

Pick the message to reply to deliberately: the most recent inbound message is usually right, and you'll find its ID in the thread's `message_ids` array or as the `latest_draft_or_message`. Keep the same `subject` so clients that fall back to subject-matching still group it. The send below threads a reply across every provider with one request body.

```bash
curl --request POST \
  --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages/send' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>' \
  --header 'Content-Type: application/json' \
  --data '{
    "subject": "Re: Q3 proposal",
    "reply_to_message_id": "<MESSAGE_ID>",
    "to": [{ "email": "client@example.com" }],
    "body": "Thanks for the notes. Updated draft attached."
  }'
```

For attachments, message selection, and Agent Accounts, see [how to reply to an email thread](/docs/cookbook/email/threads/reply-to-a-thread/).


> **Info:** 
> **New to Nylas?** Start with the [quickstart guide](/docs/v3/getting-started/) to set up your app and connect a test account before continuing here.


## What's next

- [Read a single message or thread](/docs/cookbook/email/get-message-thread/) for the full thread response fields
- [How to reply to an email thread](/docs/cookbook/email/threads/reply-to-a-thread/) to send a reply that threads correctly
- [Build a unified inbox](/docs/cookbook/email/unified-inbox/) to merge threads from every connected provider
- [Getting started with Nylas](/docs/v3/getting-started/) to create a project, connector, and your first grant