# How to list IMAP email messages

Source: https://developer.nylas.com/docs/cookbook/email/messages/list-messages-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 exposes it through the same [Messages API](/docs/reference/api/messages/) you'd use for Gmail or Outlook.

This guide covers listing messages from generic IMAP accounts, the catch-all provider type for anything that isn't Google, Microsoft, Yahoo, or iCloud.

## How do I list IMAP 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 messages by default and up to 200 per page from any IMAP mailbox, in the same schema used for Gmail and Outlook. Messages within the 90-day sync window are searchable server-side. See [List messages](#list-messages) for the request and response.

## Why use Nylas instead of IMAP directly?

Building a production-grade IMAP integration is a bigger project than most developers expect. The protocol requires persistent socket connections with heartbeat monitoring and reconnection logic. Messages come back in MIME format, which means parsing multipart content, handling character encodings, and extracting inline attachments. You need to track message UIDs per folder, handle `UIDVALIDITY` changes that can invalidate your entire cache, and deal with server-specific quirks like different hierarchy separators and inconsistent folder naming.

Nylas does all of that behind a REST API. You get clean JSON responses, automatic sync with a local cache, and a single integration that works across IMAP, Gmail, Outlook, Yahoo, and iCloud. Sending email requires a separate SMTP connection in raw IMAP, but Nylas handles both protocols behind one API.

If you're building a quick integration with a single IMAP server and want full control, you can connect directly. For production apps that need reliability across multiple providers, Nylas saves you months of infrastructure work.

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

With Hosted OAuth, users enter their email credentials and Nylas automatically connects. If the IMAP server requires specific connection settings, users can expand "Additional settings" to enter the IMAP host, port, SMTP host, and SMTP port manually.

With BYO Authentication, your app collects the credentials and sends them to Nylas directly:

| Setting         | Description               | Example                 |
| --------------- | ------------------------- | ----------------------- |
| `imap_username` | Email address or username | `user@example.com`      |
| `imap_password` | Password or app password  | (app-specific password) |
| `imap_host`     | IMAP server hostname      | `imap.example.com`      |
| `imap_port`     | IMAP server port          | `993`                   |
| `smtp_host`     | SMTP server hostname      | `smtp.example.com`      |
| `smtp_port`     | SMTP server port          | `465`                   |

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

If your app needs to send email (not just read), add `options=smtp_required` to the Hosted OAuth URL. This ensures users enter their SMTP server details during authentication.

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

## 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}")
    }
}
```


## Filter messages

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


### 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>/messages?search_query_native=subject:invoice&limit=10" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>'
```

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

```python [nativeSearchImap-Python SDK]
messages = nylas.messages.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

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

### The 90-day message cache

Nylas maintains a rolling cache of messages from the last 90 days for all IMAP-based providers. Anything received or created within that window is synced and available through the API. For messages older than 90 days, set `query_imap=true` to query the IMAP server directly. This is slower because of provider latency, but it reaches the full mailbox.


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

```js [queryImap-Node.js SDK]
const messages = await nylas.messages.list({
  identifier: grantId,
  queryParams: {
    queryImap: true,
    in: "INBOX",
    limit: 10,
  },
});
```

```python [queryImap-Python SDK]
messages = nylas.messages.list(
    grant_id,
    query_params={
        "query_imap": True,
        "in": "INBOX",
        "limit": 10,
    }
)
```

When using `query_imap`, you must include the `in` parameter to specify which folder to search.


### Webhooks don't cover old messages

Nylas [webhooks](/docs/v3/notifications/) only fire for changes to messages within the 90-day cache window. If a user modifies or deletes a message older than 90 days, you won't receive a notification. Plan your sync strategy accordingly if your app needs to track changes across the full mailbox.


### Folder names vary by provider

Unlike Google (labels) or Microsoft (standardized internal names), IMAP folder names are set by each provider. What shows up as "Trash" in one provider's web UI might be "Deleted Messages" or "Deleted Items" on the IMAP server.

Nylas maps common folders using [RFC 9051](https://www.rfc-editor.org/rfc/rfc9051) standard attributes (`\Inbox`, `\Sent`, `\Drafts`, `\Trash`, `\Junk`, `\Archive`), but always use the [List Folders endpoint](/docs/reference/api/folders/get-folder/) to discover the actual folder names for each account.

IMAP servers also use different hierarchy separators. Some use `.` (like `INBOX.Accounting.Taxes`) and others use `/` or `\`. Nylas returns the full folder path as-is, so a nested folder might appear as `Accounting.Taxes` or `INBOX\Accounting\Taxes` depending on the server.

> **Info:** 
> **Folders may take up to 10 minutes to appear after authentication.** If a newly connected account shows no folders, wait a few minutes for the initial sync to complete.

### Rate limits depend on the provider

Each IMAP provider sets its own rate limits, and most don't publish them. If your app hits a rate limit, Nylas handles the retry automatically. But if you're polling aggressively for many IMAP users, you may see throttling.

Use [webhooks](/docs/v3/notifications/) instead of polling to avoid rate limit issues. Let Nylas notify you of changes instead of checking repeatedly.

### UIDVALIDITY and message indexing

IMAP servers use a value called `UIDVALIDITY` to track whether message UIDs in a folder are still valid. If the server changes this value (due to a folder rebuild, migration, or misconfiguration), Nylas re-indexes the entire folder to stay in sync.

This usually happens transparently, but it can cause:

- **Temporary inconsistency** where the API may return stale or incomplete results for that folder during re-indexing
- **Sync failures with misconfigured servers** where the provider returns a different `UIDVALIDITY` on every connection, preventing Nylas from maintaining a stable cache. You'll see an error: `Stopped due to too many UIDVALIDITY resyncs`

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

### Encoding requirements

IMAP messages must be:

- **UTF-8 or ASCII encoded.** Messages with other character encodings may not parse correctly.
- **RFC 5322 compliant.** They must conform to the Internet Message Format standard.
- **Include a Message-ID header.** Nylas uses this to identify individual messages.

Most modern mail servers enforce these requirements, but custom or legacy servers may not. If messages aren't syncing, encoding is a common cause.

### Sync relies on IMAP idle

Nylas maintains two low-bandwidth IMAP idle connections per account to monitor the Inbox and Sent folders for real-time changes. Other folders are checked periodically. This means:

- **Inbox and Sent changes** are detected quickly, typically within seconds
- **Other folders** may take a few minutes to reflect changes
- **Webhook latency varies by folder.** Expect faster notifications for Inbox activity than for custom folders.

If a provider doesn't support IMAP idle, Nylas falls back to periodic polling, which increases detection time.

### No calendar support

The generic IMAP connector provides email access only. If you need calendar functionality alongside email for a provider that supports CalDAV (like iCloud or Fastmail), check whether Nylas has a dedicated connector for that provider.

### Contacts are disabled by default

The Contacts API is disabled by default for IMAP grants. When enabled (via [Nylas Support](https://support.nylas.com/)), contacts are parsed from email headers (From, To, CC, BCC, Reply-To fields) rather than synced from an address book.

## 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 IMAP email threads](/docs/cookbook/email/threads/list-threads-imap/) to group these messages into conversations
- [List Gmail messages](/docs/cookbook/email/messages/list-messages-google/) for the same task on Google accounts
- [List Microsoft messages](/docs/cookbook/email/messages/list-messages-microsoft/) for the same task on Microsoft 365 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 conversations
- [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, limitations, and troubleshooting
- [App passwords guide](/docs/provider-guides/app-passwords/) for provider-specific app password instructions