# Get Gmail email threads with Nylas

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

Gmail invented the conversation view that most email clients now copy. Every message in Gmail belongs to a thread, and Google assigns a stable `thread_id` that persists across the entire conversation. Nylas maps this directly to the Threads API, giving you the same conversation grouping you see in the Gmail UI through a simple REST call.

This guide walks through listing threads from Google accounts and covers the Google-specific details: native `thread_id` behavior, how labels interact with threads, search operators, and OAuth scopes.

## How do I list Gmail 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, each with a `latest_draft_or_message` object holding the newest message. Gmail supplies native `thread_id` grouping. The same call works for Outlook, Yahoo, and IMAP. See [List threads](#list-threads) for the request and response.

## Why use Nylas for threads instead of the Gmail API directly?

The Gmail API has a native [Threads resource](https://developers.google.com/gmail/api/reference/rest/v1/users.threads) that returns conversation-grouped messages. But using it requires configuring a GCP project, navigating Google's three-tier OAuth scope system, and for anything beyond basic metadata, going through OAuth verification or a full security assessment. On top of that, the Gmail Threads resource returns message IDs that you then need to fetch individually to get content, requiring multiple API calls per thread.

Nylas gives you a `/threads` endpoint that includes `latest_draft_or_message` with the most recent message's content inline. No extra calls needed. Token refresh, scope management, and the GCP project overhead are handled automatically. And your code works across Gmail, Outlook, Yahoo, and IMAP without any provider-specific branches.

If you only need Gmail and want full control, the Gmail API works well for threads. For multi-provider support or faster development, Nylas simplifies the integration.

## Before you begin

You'll need:

- A [Nylas application](/docs/v3/getting-started/) with a valid API key
- A [grant](/docs/v3/auth/) for a Gmail or Google Workspace account
- The appropriate [Google OAuth scopes](/docs/provider-guides/google/) configured in your GCP project


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


### Google OAuth scopes and verification

Google classifies OAuth scopes into three tiers, and each one comes with different verification requirements:

| Scope tier    | Example                           | What's required                                   |
| ------------- | --------------------------------- | ------------------------------------------------- |
| Non-sensitive | `gmail.labels`                    | No verification needed                            |
| Sensitive     | `gmail.readonly`, `gmail.compose` | OAuth consent screen verification                 |
| Restricted    | `gmail.modify`                    | Full security assessment by a third-party auditor |

If your app needs to read thread content (not just metadata), you'll need at least the `gmail.readonly` scope, which is classified as sensitive. For read-write access, `gmail.modify` is restricted and requires a [security assessment](/docs/provider-guides/google/google-verification-security-assessment-guide/).

Nylas handles the token management, but your GCP project still needs the right scopes configured. See the [Google provider guide](/docs/provider-guides/google/) for the full setup.

## 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, so you can render a thread preview without a separate Messages API call. The same code works for Microsoft, Yahoo, and IMAP accounts.

## List Gmail threads from the terminal

Gmail builds conversations natively, so every message carries a `thread_id` that Gmail assigns itself. The [Nylas CLI](https://cli.nylas.com/docs/commands) surfaces those conversations directly: `nylas email threads list` collapses each Gmail thread into one row, the same grouping you see in the Gmail web UI.


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 Gmail's threading is native, a conversation stays intact even as you apply or remove labels on its messages. The Threads API returns up to 200 threads per page, so `nylas email threads list --limit 200` pulls a full page in one call. Open any conversation with [`nylas email threads show`](https://cli.nylas.com/docs/commands/email-threads-show) to read its messages in order.

## Filter threads

You can narrow results with query parameters. Here's what works with Google 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`        |


> **Warn:** 
> **When using the `in` parameter with Google accounts, you must use the folder (label) ID, not the display name.** Nylas does not support filtering by label name on Google accounts. Use the [List Folders endpoint](/docs/reference/api/folders/get-folder/) to get the correct IDs.

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,
    }
)
```


### Use Gmail search operators

For more advanced filtering, you can use Gmail's native search syntax through the `search_query_native` parameter. This supports the same [search operators](https://support.google.com/mail/answer/7190?hl=en) you'd use in the Gmail search bar:

```bash
curl --request GET \
  --url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/threads?search_query_native=subject%3Ainvoice%20OR%20subject%3Areceipt" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>'
```

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

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

Some useful Gmail search operators:

| Operator         | What it does           | Example                   |
| ---------------- | ---------------------- | ------------------------- |
| `from:`          | Threads from a sender  | `from:alex@gmail.com`     |
| `to:`            | Threads to a recipient | `to:team@company.com`     |
| `subject:`       | Subject line contains  | `subject:invoice`         |
| `has:attachment` | Has file attachments   | `has:attachment`          |
| `filename:`      | Attachment filename    | `filename:report.pdf`     |
| `after:`         | Threads after a date   | `after:2025/01/01`        |
| `before:`        | Threads before a date  | `before:2025/02/01`       |
| `is:unread`      | Unread threads         | `is:unread`               |
| `label:`         | Threads with a label   | `label:important`         |
| `OR`             | Combine conditions     | `from:alex OR from:priya` |

When using `search_query_native`, you can only combine it with the `in`, `limit`, and `page_token` parameters. Other query parameters will return an error. See the [search best practices guide](/docs/dev-guide/best-practices/search/) for more details.

## Things to know about Google threads

A few provider-specific details that matter when you're working with threads on Gmail and Google Workspace accounts.

### Gmail has the most precise threading

Gmail assigns a native `thread_id` to every message, and Nylas maps this directly to the thread object's `id` field. Gmail's threading algorithm uses `In-Reply-To` and `References` headers combined with subject matching to group messages. This is the most accurate threading of any provider because it's based on Google's own conversation logic rather than heuristic matching.

The `thread_id` is stable and persistent. It doesn't change when messages are archived, labeled, or moved to trash. You can safely store it as a permanent reference to a conversation.

### Labels apply to messages, not threads

This is the biggest conceptual difference from how threads work visually in Gmail. Labels are assigned to individual messages, but the Gmail UI shows a thread in a label's view if any message in the thread has that label. Nylas mirrors this: the thread object's `folders` array is the union of all labels across every message in the conversation.

A thread might show:

```json
{
  "folders": ["INBOX", "SENT", "UNREAD", "CATEGORY_PERSONAL", "IMPORTANT"]
}
```

If you archive a thread in Gmail, the `INBOX` label is removed from all messages. But the thread still exists and is accessible through other labels or by its ID.

### Thread metadata is aggregated

Like Microsoft, thread-level fields on Google are computed from all messages:

- `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
- `message_ids` lists every message in the thread

To change read/starred state on individual messages, use the Messages API with specific message IDs from the thread.

### Google Workspace vs. personal Gmail

Threading works identically on both. The only differences are administrative:

- **Workspace admins** can restrict third-party app access. If a Workspace user can't authenticate, their admin may need to allow your app in the Google Admin console.
- **Delegated mailboxes** (shared mailboxes in Workspace) require special handling. See the [shared accounts guide](/docs/provider-guides/google/shared-accounts/).

### Rate limits are per-user and per-project

Google enforces quotas at two levels, same as with the Messages API:

- **Per-user:** Each authenticated user has a daily quota for API calls
- **Per-project:** Your GCP project has an overall daily limit across all users

Use [webhooks](/docs/v3/notifications/) or [Google Pub/Sub](/docs/provider-guides/google/connect-google-pub-sub/) instead of polling to avoid hitting limits.

## 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 Google messages](/docs/cookbook/email/messages/list-messages-google/) for message-level operations on Google accounts
- [Search best practices](/docs/dev-guide/best-practices/search/) for advanced search with `search_query_native` and Gmail operators
- [Webhooks](/docs/v3/notifications/) for real-time notifications instead of polling
- [Google Pub/Sub](/docs/provider-guides/google/connect-google-pub-sub/) for real-time sync with Gmail accounts
- [Google provider guide](/docs/provider-guides/google/) for full Google setup including OAuth scopes and verification