# Get IMAP email threads with Nylas

Source: https://developer.nylas.com/docs/cookbook/email/threads/list-threads-imap/

Not every email provider is Google, Microsoft, or Yahoo. Thousands of organizations run their own mail servers, and services like Zoho Mail, Fastmail, AOL, and GMX all support IMAP. Nylas connects to any standard IMAP server and constructs conversation threads from message headers, giving you a conversation view through the same [Threads API](/docs/reference/api/threads/) you'd use for Gmail or Outlook.

This guide covers listing threads from generic IMAP accounts and explains how Nylas builds threads without native provider support.

## How do I list IMAP email threads with the Nylas API?

Send a `GET` request to `/v3/grants/{grant_id}/threads` with your API key. The endpoint returns the most recent threads by default and up to 200 per page from any IMAP mailbox. IMAP has no native threads, so Nylas constructs them from `In-Reply-To` and `References` headers and subject matching. See [List threads](#list-threads) for the request and response.

## Why use Nylas for threads instead of IMAP directly?

The IMAP protocol has no built-in concept of conversation threading. RFC 5256 defines a `THREAD` extension, but very few servers implement it, and even those that do offer limited grouping algorithms. To build a conversation view yourself, you'd need to parse `In-Reply-To` and `References` headers from every message, group them by conversation chain, handle subject-line variations, maintain persistent socket connections, and deal with MIME parsing.

Nylas does all of that behind a REST API. The Threads API returns pre-grouped conversations with participant lists, read state, and the latest message content. Your code works across IMAP, Gmail, Outlook, Yahoo, and iCloud without modification.

## Before you begin

You'll need:

- A [Nylas application](/docs/v3/getting-started/) with a valid API key
- A [grant](/docs/v3/auth/) for an IMAP email account
- The IMAP server hostname and port for the provider (e.g., `imap.example.com:993`)


> **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.


### IMAP authentication setup

The generic IMAP connector works with any standard IMAP server. Nylas supports two authentication flows:

| Method                              | Best for                                                                      |
| ----------------------------------- | ----------------------------------------------------------------------------- |
| Hosted OAuth                        | Production apps where Nylas collects IMAP credentials through a guided flow   |
| Bring Your Own (BYO) Authentication | Custom auth pages where you collect IMAP host, port, and credentials directly |

> **Info:** 
> **Most IMAP providers require app passwords** instead of the user's regular login password. This is especially true for providers with two-factor authentication enabled. See the [app passwords guide](/docs/provider-guides/app-passwords/) for provider-specific instructions.

The full setup walkthrough is in the [IMAP authentication guide](/docs/v3/auth/imap/).

## List threads

Make a [List Threads request](/docs/reference/api/threads/get-threads/) with the grant ID. By default, Nylas returns the most recent threads. These examples limit results to 5:

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

```

```json
{
  "request_id": "1",
  "data": [
    {
      "starred": false,
      "unread": true,
      "folders": ["CATEGORY_PERSONAL", "INBOX", "UNREAD"],
      "grant_id": "<NYLAS_GRANT_ID>",
      "id": "<THREAD_ID>",
      "object": "thread",
      "latest_draft_or_message": {
        "starred": false,
        "unread": true,
        "folders": ["UNREAD", "CATEGORY_PERSONAL", "INBOX"],
        "grant_id": "<NYLAS_GRANT_ID>",
        "date": 1707836711,
        "from": [
          {
            "name": "Nyla",
            "email": "nyla@example.com"
          }
        ],
        "id": "<MESSAGE_ID>",
        "object": "message",
        "snippet": "Send Email with Nylas APIs",
        "subject": "Learn how to Send Email with Nylas APIs",
        "thread_id": "<THREAD_ID>",
        "to": [
          {
            "email": "nyla@example.com"
          }
        ],
        "created_at": 1707836711,
        "body": "Learn how to send emails using the Nylas APIs!"
      },
      "has_attachments": false,
      "has_drafts": false,
      "earliest_message_date": 1707836711,
      "latest_message_received_date": 1707836711,
      "participants": [
        {
          "email": "nylas@nylas.com"
        }
      ],
      "snippet": "Send Email with Nylas APIs",
      "subject": "Learn how to Send Email with Nylas APIs",
      "message_ids": ["<MESSAGE_ID>"]
    }
  ],
  "next_cursor": "123"
}


```

```js [listThreads-Node.js SDK]

import Nylas from "nylas";

const nylas = new Nylas({
  apiKey: "<NYLAS_API_KEY>",
  apiUri: "<NYLAS_API_URI>",
});

async function fetchRecentThreads() {
  try {
    const identifier = "<NYLAS_GRANT_ID>";
    const threads = await nylas.threads.list({
      identifier: identifier,
      queryParams: {
        limit: 5,
      },
    });

    console.log("Recent Threads:", threads);
  } catch (error) {
    console.error("Error fetching threads:", error);
  }
}

fetchRecentThreads();


```

```python [listThreads-Python SDK]

from nylas import Client

nylas = Client(
    "<NYLAS_API_KEY>",
    "<NYLAS_API_URI>"
)

grant_id = "<NYLAS_GRANT_ID>"

threads = nylas.threads.list(
  grant_id,
  query_params={
    "limit": 5
  }
)

print(threads)

```


```ruby [listThreads-Ruby SDK]
require 'nylas'

nylas = Nylas::Client.new(api_key: '<NYLAS_API_KEY>')
query_params = { limit: 5 }
threads, _ = nylas.threads.list(identifier: '<NYLAS_GRANT_ID>', query_params: query_params)

threads.each {|thread|
  puts "#{thread[:subject]} | Participants: #{thread[:participants].map { |p| p[:email] }.join(', ')}"
}
```

```java [listThreads-Java SDK]
import com.nylas.NylasClient;
import com.nylas.models.*;
import com.nylas.models.Thread;
import java.util.List;

public class ListThreads {
  public static void main(String[] args) throws NylasSdkTimeoutError, NylasApiError {
    NylasClient nylas = new NylasClient.Builder("<NYLAS_API_KEY>").build();
    ListThreadsQueryParams queryParams = new ListThreadsQueryParams.Builder().limit(5).build();
    ListResponse<Thread> threads = nylas.threads().list("<NYLAS_GRANT_ID>", queryParams);

    for(Thread thread : threads.getData()) {
      System.out.println(thread.getSubject());
    }
  }
}
```

```kt [listThreads-Kotlin SDK]
import com.nylas.NylasClient
import com.nylas.models.*

fun main(args: Array<String>) {
    val nylas = NylasClient(apiKey = "<NYLAS_API_KEY>")
    val queryParams = ListThreadsQueryParams(limit = 5)
    val threads = nylas.threads().list("<NYLAS_GRANT_ID>", queryParams).data

    for (thread in threads) {
        println(thread.subject)
    }
}
```


The response includes a `latest_draft_or_message` object with the most recent message's content. The same code works for Google, Microsoft, and Yahoo accounts.

## List IMAP threads from the terminal

Plain IMAP servers expose no conversation concept, so Nylas threads messages by their `References` and `In-Reply-To` headers. The [CLI](https://cli.nylas.com/docs/commands) shows that reconstructed view: `nylas email threads list` collapses a conversation into one row no matter which IMAP server hosts it.


The Nylas CLI groups messages into conversations the same way the Threads API does. After `nylas init` and `nylas auth login`, `email threads list` returns the 10 most recent threads by default, each collapsing a full back-and-forth into a single row:

```bash
# List the 10 most recent threads
nylas email threads list

# Only unread threads, filtered by subject
nylas email threads list --unread --subject "invoice"

# Open one thread to see every message in the conversation
nylas email threads show <thread-id>
```

Thread search works differently from message search: it filters by field rather than free text, so you match on `--subject`, `--from`, or `--unread` instead of passing a bare query string:

```bash
# Find threads by subject and sender
nylas email threads search --subject "contract renewal" --from "legal@vendor.com"
```

Both commands accept `--json` for scripting. See the [`email threads list`](https://cli.nylas.com/docs/commands/email-threads-list) and [`email threads search`](https://cli.nylas.com/docs/commands/email-threads-search) command reference for every flag.


Because the grouping is header-driven, threading is only as good as the headers the server and senders preserve. Messages from the last 90 days stay searchable server-side, and [`nylas email threads show`](https://cli.nylas.com/docs/commands/email-threads-show) opens a single conversation.

## Filter threads

You can narrow results with query parameters. Here's what works with IMAP accounts:


| Parameter         | What it does                  | Example                       |
| ----------------- | ----------------------------- | ----------------------------- |
| `subject`         | Match on subject line         | `?subject=Weekly standup`     |
| `from`            | Filter by sender              | `?from=alex@example.com`      |
| `to`              | Filter by recipient           | `?to=team@company.com`        |
| `unread`          | Unread only                   | `?unread=true`                |
| `in`              | Filter by folder or label ID  | `?in=INBOX`                   |
| `received_after`  | After a Unix timestamp        | `?received_after=1706000000`  |
| `received_before` | Before a Unix timestamp       | `?received_before=1706100000` |
| `has_attachment`  | Only results with attachments | `?has_attachment=true`        |


Here's how to combine filters. This pulls threads with unread messages from a specific sender:


```bash
curl --request GET \
  --url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/threads?from=alex@example.com&unread=true&limit=10" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>'
```

```js [filterThreads-Node.js SDK]
const threads = await nylas.threads.list({
  identifier: grantId,
  queryParams: {
    from: "alex@example.com",
    unread: true,
    limit: 10,
  },
});
```

```python [filterThreads-Python SDK]
threads = nylas.threads.list(
    grant_id,
    query_params={
        "from": "alex@example.com",
        "unread": True,
        "limit": 10,
    }
)
```


### Search with `search_query_native`

IMAP providers support the `search_query_native` parameter, which maps to the IMAP `SEARCH` command defined in [RFC 3501](https://datatracker.ietf.org/doc/html/rfc3501#section-6.4.4). Like Yahoo and iCloud, generic IMAP lets you combine `search_query_native` with any other query parameter.

```bash
curl --request GET \
  --url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/threads?search_query_native=subject:invoice&limit=10" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>'
```

```js [nativeSearchImapThreads-Node.js SDK]
const threads = await nylas.threads.list({
  identifier: grantId,
  queryParams: {
    searchQueryNative: "subject:invoice",
    limit: 10,
  },
});
```

```python [nativeSearchImapThreads-Python SDK]
threads = nylas.threads.list(
    grant_id,
    query_params={
        "search_query_native": "subject:invoice",
        "limit": 10,
    }
)
```

> **Warn:** 
> **Not all IMAP servers support the `SEARCH` operator.** If `search_query_native` returns a `400` error, the provider doesn't support it. Fall back to standard query parameters (`subject`, `from`, `to`, etc.) instead.

See the [search best practices guide](/docs/dev-guide/best-practices/search/) for more on `search_query_native` across providers.

## Things to know about IMAP threads

The generic IMAP connector is the most flexible provider type. It works with nearly any mail server, but that flexibility comes with some threading-specific trade-offs.

### Threading is constructed from headers

IMAP servers don't provide native thread grouping (despite RFC 5256 defining a `THREAD` extension, very few servers implement it). Nylas builds threads by analyzing `In-Reply-To` and `References` headers on each message, combined with subject-line matching. This approach works regardless of whether the server supports the THREAD extension.

Thread quality depends on the email clients involved in the conversation. Modern clients (Gmail, Outlook, Apple Mail, Thunderbird) set proper `In-Reply-To` and `References` headers. Older or misconfigured clients may not, which can result in messages that should be grouped together appearing as separate threads.

### The 90-day message cache affects threads


Nylas maintains a rolling cache of messages from the last 90 days for IMAP-based providers. Threads are built from cached messages, so conversations that span beyond 90 days may appear incomplete. The `message_ids` array only includes messages within the cache window.

To access older messages directly (not as threads), use `query_imap=true` on the Messages API. The Threads API does not support `query_imap`.


### UIDVALIDITY can affect thread consistency

IMAP servers use a `UIDVALIDITY` value to track whether message UIDs in a folder are still valid. If the server changes this value (due to a folder rebuild or migration), Nylas re-indexes the folder. During re-indexing, threads may temporarily appear incomplete or split.

If you encounter `UIDVALIDITY` errors, check the [IMAP troubleshooting guide](/docs/provider-guides/imap/troubleshooting/) for workarounds.

### Thread metadata aggregation


Thread-level fields are computed from all cached messages in the conversation:

- `unread` is `true` if any message in the thread is unread
- `starred` is `true` if any message is starred
- `has_attachments` is `true` if any message has attachments
- `participants` is the union of all senders and recipients
- `earliest_message_date` reflects the oldest cached message, not necessarily the start of the conversation


### Folder names vary by provider

IMAP folder names are set by each provider. Nylas maps common folders using RFC 9051 standard attributes, but always use the [List Folders endpoint](/docs/reference/api/folders/get-folder/) to discover the actual folder names for each account. This matters when filtering threads with the `in` parameter.

### Sync relies on IMAP idle

Nylas monitors the Inbox and Sent folders with IMAP idle connections for near-real-time change detection. Other folders are checked periodically. Thread updates from Inbox activity appear quickly, while threads in custom folders may take a few minutes to update.

## Paginate through results


The Threads API returns paginated responses. When there are more results, the response includes a `next_cursor` value. Pass it back as `page_token` to get the next page:

```bash
curl --request GET \
  --url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/threads?limit=10&page_token=<NEXT_CURSOR>" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>'
```

```js [paginateThreads-Node.js SDK]
let pageCursor = undefined;

do {
  const result = await nylas.threads.list({
    identifier: grantId,
    queryParams: {
      limit: 10,
      pageToken: pageCursor,
    },
  });

  // Process result.data here

  pageCursor = result.nextCursor;
} while (pageCursor);
```

```python [paginateThreads-Python SDK]
page_cursor = None

while True:
    query = {"limit": 10}
    if page_cursor:
        query["page_token"] = page_cursor

    result = nylas.threads.list(grant_id, query_params=query)

    # Process result.data here

    page_cursor = result.next_cursor
    if not page_cursor:
        break
```

Keep paginating until the response comes back without a `next_cursor`.


## What's next

- [Threads API reference](/docs/reference/api/threads/) for full endpoint documentation and all available parameters
- [Using the Threads API](/docs/v3/email/threads/) for thread concepts and additional operations
- [Messages API reference](/docs/reference/api/messages/) to fetch individual message content from threads
- [List IMAP messages](/docs/cookbook/email/messages/list-messages-imap/) for message-level operations on IMAP accounts
- [Search best practices](/docs/dev-guide/best-practices/search/) for advanced search with `search_query_native` across providers
- [Webhooks](/docs/v3/notifications/) for real-time notifications instead of polling
- [IMAP authentication guide](/docs/v3/auth/imap/) for full IMAP setup including Hosted and BYO authentication
- [IMAP provider guide](/docs/provider-guides/imap/) for general IMAP configuration and troubleshooting
- [App passwords guide](/docs/provider-guides/app-passwords/) for provider-specific app password instructions