# How to search email messages

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

You want to find a specific email across a user's mailbox without pulling the whole inbox down first. The Messages API lets you search by envelope fields like `subject`, `from`, `to`, and `any_email`, or hand a provider its own native query string through `search_query_native`. The same request shape works across Gmail, Microsoft 365, Yahoo, iCloud, and IMAP.

This recipe focuses on search specifically: matching by metadata, running provider-native full-text queries, and the limits each provider imposes. For the full filter parameter table, see [List Google messages](/docs/cookbook/email/messages/list-messages-google/).

## How do I search email messages with the Nylas API?

Send a `GET` request to `/v3/grants/{grant_id}/messages` with search parameters such as `subject`, `from`, `to`, or `any_email`. The endpoint returns up to 200 matching messages per page in one unified schema across all 6 providers. For provider-native full-text search, pass a URL-encoded query through `search_query_native` instead.

The `subject` filter is case-sensitive and matches partial strings, so `subject=invoice` returns any message whose subject contains that word. The `any_email` parameter accepts up to 25 comma-separated addresses and matches the To, From, CC, or BCC fields. The request below searches a mailbox for invoice messages and limits the result to 5. It hits the same [List Messages endpoint](/docs/reference/api/messages/get-messages/) you use to page through an inbox.

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

```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 [searchMessages-Node.js SDK]
const messages = await nylas.messages.list({
  identifier: "<NYLAS_GRANT_ID>",
  queryParams: {
    subject: "invoice",
    limit: 5,
  },
});
```

```python [searchMessages-Python SDK]
messages = nylas.messages.list(
  "<NYLAS_GRANT_ID>",
  query_params={
    "subject": "invoice",
    "limit": 5
  }
)
```

## Search from the terminal

The [Nylas CLI](https://cli.nylas.com/docs/commands) runs the same search from your terminal: `nylas email search` matches a free-text query and layers on sender, date, and attachment filters, so you find specific mail without writing a query loop.

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

# Any subject from one sender in a date window
nylas email search "*" --from hr@company.com --after 2025-01-01 --before 2025-12-31
```

Search returns 20 results by default and pages through up to 200 messages once `--limit` goes over that. Search semantics are the provider's, so Gmail's operator-rich search behaves differently from a bare IMAP search. See the [`email search`](https://cli.nylas.com/docs/commands/email-search) command reference for every filter.

## Search by sender, recipient, or participant

Four envelope parameters cover most people-based searches: `from` matches the sender, `to` matches a recipient, `any_email` matches any of the To, From, CC, or BCC fields, and `cc`/`bcc` match those lists directly. The `any_email` parameter takes up to 25 addresses in one comma-separated request, which makes it the fastest way to find every message exchanged with a set of people.

These are server-side filters, so the provider does the matching and you get back only the messages that qualify. The request below finds messages exchanged with two people by passing both addresses to `any_email`. You'd reach for this when building a contact-history view or a per-customer email log.

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

```js [participantSearch-Node.js SDK]
const messages = await nylas.messages.list({
  identifier: "<NYLAS_GRANT_ID>",
  queryParams: {
    anyEmail: "leyah@example.com,nyla@example.com",
    limit: 10,
  },
});
```

```python [participantSearch-Python SDK]
messages = nylas.messages.list(
  "<NYLAS_GRANT_ID>",
  query_params={
    "any_email": "leyah@example.com,nyla@example.com",
    "limit": 10
  }
)
```

You can combine these with `received_after`, `received_before`, `unread`, `starred`, and `has_attachment` to narrow further. For the complete 8-parameter filter reference, see [List Google messages](/docs/cookbook/email/messages/list-messages-google/).

## Run a full-text search with `search_query_native`

The `search_query_native` parameter accepts a URL-encoded, provider-specific query string and passes it straight to the underlying provider. This unlocks full-text search and operators that the standard parameters can't express, across 4 providers: Google, Microsoft Graph, EWS, and IMAP. Each provider speaks its own query dialect, so the syntax inside the string changes per provider.

When you use `search_query_native` on Google or Microsoft, you can only combine it with 3 parameters: `in`, `limit`, and `page_token`. Any other query parameter returns a [`400` error](/docs/api/errors/400-response/). The request below runs a Gmail search for messages whose subject contains "invoice" or "receipt"; the raw query `subject:invoice OR subject:receipt` is URL-encoded before it goes on the wire.

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

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

```python [nativeSearch-Python SDK]
messages = nylas.messages.list(
  "<NYLAS_GRANT_ID>",
  query_params={
    "search_query_native": "subject:invoice OR subject:receipt",
    "limit": 10
  }
)
```

## Which search operators work per provider?

Search capability splits into two tiers. Standard parameters such as `subject`, `from`, `to`, and `any_email` behave consistently across all 6 providers. Native operators differ: Gmail uses search-box syntax, Microsoft Graph uses Keyword Query Language (KQL) or `$filter`, IMAP uses RFC 3501 `SEARCH` keys, and EWS uses Advanced Query Syntax. The table below maps a few common Gmail operators you can drop into `search_query_native`.

| Operator         | What it matches         | 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`          |
| `after:`         | Messages after a date   | `after:2025/01/01`        |
| `OR`             | Combine conditions      | `from:alex OR from:priya` |

Provider dialects vary enough that one native query rarely ports across providers:

- **Google** supports the [Gmail search operators](https://support.google.com/mail/answer/7190?hl=en) from the search box, like `has:attachment` and `label:`.
- **Microsoft Graph** accepts both `$search` ([KQL](https://learn.microsoft.com/en-us/sharepoint/dev/general-development/keyword-query-language-kql-syntax-reference)) and [`$filter` queries](https://learn.microsoft.com/en-us/graph/filter-query-parameter) such as `$filter=from/emailAddress/address eq 'leyah@example.com'`.
- **IMAP, Yahoo, and iCloud** accept any standard parameter alongside `search_query_native`, but some IMAP servers don't implement the `SEARCH` command and return a `400` error.
- **EWS** accepts every parameter except `thread_id`, and only works when the server administrator has enabled search indexing for all mailboxes.

For the full cross-provider breakdown, see the [search best practices guide](/docs/dev-guide/best-practices/search/).

## Things to know about searching

A handful of behaviors trip people up the first time they search at scale. None are obvious from the parameter list alone, so they're worth calling out before you ship.

### Subject matching is case-sensitive and partial

The `subject` parameter does a case-sensitive partial match, which means `subject=Invoice` and `subject=invoice` return different result sets. If you can't predict casing, drop to `search_query_native` and let the provider's full-text index handle case folding. Gmail and Microsoft both treat their native subject operators as case-insensitive, so `subject:invoice` matches "Invoice", "INVOICE", and "invoice" alike.

### Standard filters search metadata, not body text

The standard parameters match envelope and header fields only. None of them search the message body. To match words inside the body, you need `search_query_native` with a provider that indexes content, such as Gmail (`"meeting notes"`) or Microsoft `$search`. This is the single biggest reason a metadata search returns 0 results when the user swears the term is "in the email" somewhere.

### Microsoft enforces mutually exclusive parameter groups

On Microsoft Graph accounts, certain parameters can't be combined. Group 1 (`thread_id`, `unread`, `starred`) and Group 2 (`subject`, `to`, `cc`, `bcc`, `any_email`) are mutually exclusive, so you can't filter `starred` messages `to` a specific address in one request. Split it into two calls, or use `search_query_native` instead. This restriction is a Microsoft limitation, not a Nylas one.

### Search results paginate like any list

A search returns the same cursor-based pagination as an unfiltered list. The default page size is 50 messages and the maximum is 200; pass the `page_token` from each response to fetch the next page. Filters stay applied across pages automatically, so you don't re-send the search parameters on follow-up requests. For the paging pattern in code, see [List Google messages](/docs/cookbook/email/messages/list-messages-google/).

## What's next

- [List Google messages](/docs/cookbook/email/messages/list-messages-google/) for Gmail filters, labels, and pagination code
- [List Microsoft messages](/docs/cookbook/email/messages/list-messages-microsoft/) for Microsoft Graph `$filter` and KQL search
- [Read a single message or thread](/docs/cookbook/email/get-message-thread/) to fetch the full content of a search hit by ID
- [Search best practices](/docs/dev-guide/best-practices/search/) for the complete cross-provider `search_query_native` reference
- [Messages API reference](/docs/reference/api/messages/) for every query parameter and response field