# How to list Google email messages

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

Gmail and Google Workspace accounts are the most common provider type developers integrate with. If you've ever worked with the Gmail API directly, you know the OAuth verification process, scope restrictions, and label system add significant friction. Nylas abstracts all of that behind the same [Messages API](/docs/reference/api/messages/) you'd use for Microsoft or IMAP.

This guide walks through listing messages from Google accounts and covers the Google-specific details you need to know: labels, search operators, OAuth scopes, and rate limits.

## How do I list Gmail messages with the Nylas API?

Send a `GET` request to `/v3/grants/{grant_id}/messages` with your API key. The endpoint returns the 50 most recent Gmail messages by default and up to 200 per page, each carrying a unified `folders` array that maps Gmail labels to a standard model. The same call works for Microsoft, Yahoo, iCloud, and IMAP. See [List messages](#list-messages) for the full request and response.

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

The Gmail API requires more setup overhead than most developers expect. You need to configure a GCP project, navigate Google's three-tier OAuth scope system (non-sensitive, sensitive, restricted), and for any scope beyond basic metadata, go through Google's OAuth verification or even a full third-party security assessment. On top of that, the Gmail data model uses labels instead of folders, message IDs are hex strings, and rate limits are enforced at both the per-user and per-project level.

Nylas normalizes all of this. Labels map to a unified folder model. Token refresh and scope management happen automatically. You don't need a GCP project or a security assessment to get started. And your code works across Gmail, Outlook, Yahoo, and IMAP without any provider-specific branches.

If you only need Gmail and want full control over the integration, the Gmail API works. If you need multi-provider support or want to skip the verification process, Nylas is the faster path.

## 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 message 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 messages

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

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

```

```json
{
  "request_id": "d0c951b9-61db-4daa-ab19-cd44afeeabac",
  "data": [
    {
      "starred": false,
      "unread": true,
      "folders": ["UNREAD", "CATEGORY_PERSONAL", "INBOX"],
      "grant_id": "1",
      "date": 1706811644,
      "attachments": [
        {
          "id": "1",
          "grant_id": "1",
          "filename": "invite.ics",
          "size": 2504,
          "content_type": "text/calendar; charset=\"UTF-8\"; method=REQUEST"
        },
        {
          "id": "2",
          "grant_id": "1",
          "filename": "invite.ics",
          "size": 2504,
          "content_type": "application/ics; name=\"invite.ics\"",
          "is_inline": false,
          "content_disposition": "attachment; filename=\"invite.ics\""
        }
      ],
      "from": [
        {
          "name": "Nylas DevRel",
          "email": "nylasdev@nylas.com"
        }
      ],
      "id": "1",
      "object": "message",
      "snippet": "Send Email with Nylas APIs",
      "subject": "Learn how to Send Email with Nylas APIs",
      "thread_id": "1",
      "to": [
        {
          "name": "Nyla",
          "email": "nyla@nylas.com"
        }
      ],
      "created_at": 1706811644,
      "body": "Learn how to send emails using the Nylas APIs!"
    }
  ],
  "next_cursor": "123"
}


```

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

import Nylas from "nylas";

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

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

    console.log("Messages:", messages);
  } catch (error) {
    console.error("Error fetching emails:", error);
  }
}

fetchRecentEmails();


```

```python [listMessages-Python SDK]

from nylas import Client

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

grant_id = "<NYLAS_GRANT_ID>"

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

print(messages)

```


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

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

messages.each {|message|
  puts "[#{Time.at(message[:date]).strftime("%d/%m/%Y at %H:%M:%S")}] \
      #{message[:subject]}"
}
```

```java [listMessages-Java SDK]
import com.nylas.NylasClient;
import com.nylas.models.*;
import java.text.SimpleDateFormat;

public class ListMessages {
  public static void main(String[] args) throws NylasSdkTimeoutError, NylasApiError {
    NylasClient nylas = new NylasClient.Builder("<NYLAS_API_KEY>").build();
    ListMessagesQueryParams queryParams = new ListMessagesQueryParams.Builder().limit(5).build();
    ListResponse<Message> message = nylas.messages().list("<NYLAS_GRANT_ID>", queryParams);

    for(Message email : message.getData()) {
      String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").
          format(new java.util.Date((email.getDate() * 1000L)));

      System.out.println("[" + date + "] | " + email.getSubject());
    }
  }
}
```

```kt [listMessages-Kotlin SDK]
import com.nylas.NylasClient
import com.nylas.models.*
import java.text.SimpleDateFormat
import java.util.*

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

    for (message in messages) {
        val date = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
            .format(Date(message.date.toLong() * 1000))
        println("[$date] | ${message.subject}")
    }
}
```


## List Gmail messages from the terminal

The [Nylas CLI](https://cli.nylas.com/docs/commands) lists Gmail messages without any code: `nylas email list` calls the same `/v3/grants/{grant_id}/messages` endpoint and prints the 10 most recent inbox messages. It's the fastest way to confirm a Google grant is syncing before you wire the API into your app.


The Nylas CLI mirrors the Messages API, so you can read the same inbox from your terminal without writing any code. After `nylas init` and `nylas auth login`, the `email list` command returns the 10 most recent inbox messages by default and pages through everything automatically once `--limit` goes over 200:

```bash
# List the 10 most recent inbox messages
nylas email list

# Show only unread messages from a specific sender
nylas email list --unread --from billing@vendor.com

# Fetch everything across all folders, paginated automatically
nylas email list --all --all-folders --max 500
```

To find specific messages rather than list them, `email search` runs a full-text query with its own filters for sender (`--from`), date range (`--after` and `--before`), and attachments (`--has-attachment`). Search returns 20 results by default and only fetches more than one page once `--limit` goes over 200:

```bash
# Full-text search restricted to one sender, attachments only
nylas email search "invoice" --from billing@vendor.com --has-attachment
```

Both commands accept `--json`, so you can pipe results into `jq` or a script. See the [`email list`](https://cli.nylas.com/docs/commands/email-list) and [`email search`](https://cli.nylas.com/docs/commands/email-search) command reference for every flag.


The CLI describes `--folder` generically as a folder filter. On Google accounts, the `email list --folder` value is a Gmail label ID such as `INBOX` or `CATEGORY_PERSONAL`, the same value the API's `in` parameter expects. Run `nylas email folders list` to discover a Google account's label IDs, including custom ones. The same `nylas email list` command works unchanged across all 6 providers: Gmail, Microsoft 365, Yahoo, iCloud, IMAP, and Exchange. For sending Gmail from the same command line, see [Send email from the terminal](https://cli.nylas.com/guides/send-email-from-terminal).

## Filter messages

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 unread messages from a specific sender:


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

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

```python [filterMessages-Python SDK]
messages = nylas.messages.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>/messages?search_query_native=subject%3Ainvoice%20OR%20subject%3Areceipt" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>'
```

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

```python [nativeSearchGmail-Python SDK]
messages = nylas.messages.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:`          | Messages from a sender  | `from:alex@gmail.com`     |
| `to:`            | Messages 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:`         | Messages after a date   | `after:2025/01/01`        |
| `before:`        | Messages before a date  | `before:2025/02/01`       |
| `is:unread`      | Unread messages         | `is:unread`               |
| `label:`         | Messages 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

A few provider-specific details that matter when you're building against Gmail and Google Workspace accounts.

### Labels, not folders

This is the biggest conceptual difference from Microsoft. Gmail uses labels instead of folders, so a single message can have multiple labels at once. When you list messages with `?in=INBOX`, you're filtering by the `INBOX` label, not a folder.

Nylas normalizes this into a `folders` array on each message object. A Gmail message might look like:

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

Common system labels you'll see:

| Gmail UI name  | Label ID              |
| -------------- | --------------------- |
| Inbox          | `INBOX`               |
| Sent           | `SENT`                |
| Drafts         | `DRAFT`               |
| Trash          | `TRASH`               |
| Spam           | `SPAM`                |
| Starred        | `STARRED`             |
| Important      | `IMPORTANT`           |
| Primary tab    | `CATEGORY_PERSONAL`   |
| Social tab     | `CATEGORY_SOCIAL`     |
| Promotions tab | `CATEGORY_PROMOTIONS` |
| Updates tab    | `CATEGORY_UPDATES`    |

Custom labels created by the user will have auto-generated IDs. Use the [List Folders endpoint](/docs/reference/api/folders/get-folder/) to discover them. Google also supports custom label colors via `text_color` and `background_color` parameters when [creating](/docs/reference/api/folders/post-folder/) or [updating](/docs/reference/api/folders/put-folders-id/) folders.

### Message IDs are shorter than Microsoft's

Google message IDs are shorter hex-style strings (like `18d5a4b2c3e4f567`), compared to Microsoft's long base64-encoded IDs. They're stable across syncs and safe to store in your database.

The `thread_id` field is a Google-native concept. Gmail groups related messages into threads automatically. If you're building an inbox UI, you'll probably want to use the [Threads API](/docs/v3/email/threads/) instead of listing individual messages.

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

Google enforces quotas at two levels:

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

Nylas handles retries when you hit rate limits, but if your app polls aggressively for many users, you may exhaust your project quota. Two ways to avoid this:

- Use [webhooks](/docs/v3/notifications/) instead of polling so Nylas notifies your server when new messages arrive
- Set up [Google Pub/Sub](/docs/provider-guides/google/connect-google-pub-sub/) for real-time sync, which gives you faster notification delivery for Gmail accounts with `gmail.readonly` or `gmail.labels` scopes

### One-click unsubscribe headers

If your app sends marketing or subscription email through Gmail accounts, be aware that Google requires one-click unsubscribe headers for senders who send more than 5,000 messages per day to Gmail addresses. You'll need to include `List-Unsubscribe-Post` and `List-Unsubscribe` custom headers in your send requests.

This doesn't affect listing messages, but it's worth knowing if your app both reads and sends email. See the [email documentation](/docs/v3/email/) for implementation details.

### Google Workspace vs. personal Gmail

Both work with Nylas, but there are differences:

- **Workspace admins** can restrict which third-party apps have 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/).
- **Service accounts** are available for Google Workspace Calendar access (not email). See the [service accounts guide](/docs/provider-guides/google/google-workspace-service-accounts/).

## Paginate through results


The Messages 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>/messages?limit=10&page_token=<NEXT_CURSOR>" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>'
```

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

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

  // Process result.data here

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

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

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

    result = nylas.messages.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

- [List Gmail email threads](/docs/cookbook/email/threads/list-threads-google/) to group these messages into conversations
- [List Microsoft messages](/docs/cookbook/email/messages/list-messages-microsoft/) for the same task on Microsoft 365 accounts
- [List Yahoo Mail messages](/docs/cookbook/email/messages/list-messages-yahoo/) for the same task on Yahoo accounts
- [Messages API reference](/docs/reference/api/messages/) for full endpoint documentation and all available parameters
- [Using the Messages API](/docs/v3/email/messages/) for search, modification, and deletion
- [Threads](/docs/v3/email/threads/) to group related messages into Gmail-style conversations
- [Search best practices](/docs/dev-guide/best-practices/search/) for advanced search with `search_query_native` and provider-specific 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
- [Google verification & security assessment](/docs/provider-guides/google/google-verification-security-assessment-guide/), required for restricted scopes in production
- [List Gmail emails from the terminal](https://cli.nylas.com/guides/list-gmail-emails) -- read and search Gmail messages using the Nylas CLI
- [Send email from the terminal](https://cli.nylas.com/guides/send-email-from-terminal) -- send email from the command line without writing code