Source: https://cli.nylas.com/guides/search-email-from-terminal

# Search Email from Terminal

Every email provider has its own search syntax: Gmail uses operators, Microsoft uses OData $filter, and IMAP speaks RFC 3501 SEARCH. This guide compares all three approaches and shows how a single CLI command searches across providers without writing provider-specific code.

Written by [Nick Barraclough](https://cli.nylas.com/authors/nick-barraclough) Product Manager

Updated May 22, 2026

> **TL;DR:** Gmail, Microsoft Graph, and IMAP each have different search syntax. Nylas CLI wraps all three behind `nylas email search`, so one command searches Gmail, Outlook, Yahoo, iCloud, and IMAP inboxes from your terminal.

## Why search email from the terminal?

Searching email from the terminal lets you filter, extract, and pipe results into scripts without leaving your shell. GUI clients like Thunderbird or Apple Mail can't output JSON, feed results to `jq`, or run inside a cron job. A 2024 Stack Overflow survey found that 73% of developers use the terminal as their primary interface. If you already live in the shell, switching to a browser tab to search your inbox breaks the workflow.

The harder problem is that every provider speaks a different search language. Gmail has its own operator syntax. Microsoft Graph uses OData `$filter` and `$search`. Raw IMAP uses the SEARCH command from [RFC 3501, section 6.4.4](https://datatracker.ietf.org/doc/html/rfc3501#section-6.4.4). If your scripts target more than one provider, you're maintaining three sets of search logic.

## What is Gmail search syntax?

Gmail search syntax is a set of operators that filter messages by sender, date, label, attachment presence, and more. These operators work in the Gmail web UI, the Gmail API's `q` parameter, and Google Apps Script. Gmail processes over 300 billion emails per day according to [Google's own metrics](https://blog.google/products/gmail/gmail-security-phishing-spam-15th-birthday/), and its search engine indexes every message.

The operator syntax is powerful but Gmail-only. You can't use `from:` or `has:attachment` against an Outlook mailbox. Mixing operators incorrectly returns zero results with no error, which makes debugging harder than it should be. Here are the most common operators:

```text
# Gmail search operator examples (Gmail API q parameter)
from:alice@example.com
has:attachment after:2026/01/01
label:important is:unread
subject:"quarterly report"
larger:5M filename:pdf
```

The API accepts these operators as a query string. A search for unread messages from a specific sender with attachments after a certain date requires chaining 4 operators: `from:alice@example.com is:unread has:attachment after:2026/01/01`. Getting the same results from Microsoft Graph or IMAP requires completely different syntax.

## How does Microsoft Graph `$filter` work for email search?

Microsoft Graph uses OData query parameters to search Outlook and Exchange mailboxes. The `$filter` parameter matches exact field values, while `$search` runs a full-text keyword query across message bodies and subjects. Microsoft processes over 400 million commercial Outlook users, and the Graph API is the only supported programmatic access path since [Basic Auth was retired in October 2022](https://learn.microsoft.com/en-us/exchange/clients-and-mobile-in-exchange-online/deprecation-of-basic-authentication-exchange-online).

The `$filter` syntax uses OData operators like `eq`, `ge`, `le`, and `contains()`. Combining `$filter` with `$search` in a single request isn't always supported, which forces you to make two API calls and merge results client-side. Here's what a typical Graph query looks like:

```text
# Microsoft Graph API - OData $filter and $search
GET /me/messages?$filter=from/emailAddress/address eq 'alice@example.com'
  &$filter=receivedDateTime ge 2026-01-01T00:00:00Z
  &$filter=hasAttachments eq true

# Full-text search (separate request)
GET /me/messages?$search="quarterly report"

# KQL syntax for $search
GET /me/messages?$search="from:alice subject:report received>=2026-01-01"
```

Graph requires an Azure AD app registration and an OAuth 2.0 token with `Mail.Read` scope before any of this works. The token acquisition flow alone is 5 steps. That's acceptable in a production app but heavy for a terminal search.

## What is IMAP SEARCH?

IMAP SEARCH is a command defined in [RFC 3501](https://datatracker.ietf.org/doc/html/rfc3501#section-6.4.4) that filters messages on the server by criteria like sender, date, flags, and keywords. It's the most portable option since any standard IMAP server supports it. Dovecot, Courier, and Cyrus all implement the same syntax. But SEARCH hasn't changed meaningfully since RFC 3501 was published in March 2003, and it shows.

The command operates on the currently selected mailbox only. You can't search across folders in a single request. Boolean logic is implicit (AND by default), and `OR` requires prefix notation. Full-text body search depends on server-side indexing, which varies by provider. The response is a flat list of sequence numbers, not message content, so you need a second FETCH command to get actual data.

```text
# IMAP SEARCH examples (RFC 3501)
# Connect with OpenSSL
openssl s_client -connect imap.gmail.com:993

# After login and SELECT INBOX:
SEARCH FROM "alice@example.com" SINCE 01-Jan-2026
SEARCH UNSEEN SUBJECT "quarterly report"
SEARCH OR FROM "alice" FROM "bob"
SEARCH BEFORE 01-Mar-2026 LARGER 5000000
```

IMAP SEARCH returns message sequence numbers like `* SEARCH 4 8 15 16 23 42`. To see subjects or bodies, you issue `FETCH 4 (BODY[HEADER.FIELDS (SUBJECT FROM DATE)])` for each result. That's 2 round trips minimum per search, plus 1 more per message you want to read. At scale, that's slow.

## How do Gmail, Graph, IMAP, and CLI search compare?

The four search methods differ in syntax, portability, setup cost, and output format. Gmail operators are the most human-readable but only work with Google. Graph `$filter` is the most structured but requires Azure AD setup. IMAP SEARCH is the most portable but the least expressive. The CLI abstracts all three behind uniform flags. Here's a side-by-side comparison:

| Feature | Gmail operators | Graph $filter | IMAP SEARCH | Nylas CLI |
| --- | --- | --- | --- | --- |
| Search by sender | `from:alice` | `$filter=from/emailAddress/address eq` | `FROM "alice"` | `--from alice` |
| Date filtering | `after:2026/01/01` | `receivedDateTime ge 2026-01-01` | `SINCE 01-Jan-2026` | `--after 2026-01-01` |
| Attachment filter | `has:attachment` | `hasAttachments eq true` | Not supported | `--has-attachment` |
| Folder scoping | `in:inbox` | `/mailFolders/inbox/messages` | `SELECT INBOX` | `--in INBOX` |
| Full-text search | `"quarterly report"` | `$search="quarterly report"` | `BODY "report"` | `nylas email search "quarterly report"` |
| Cross-provider | Gmail only | Microsoft only | Any IMAP server | Gmail, Outlook, Yahoo, iCloud, IMAP |
| Auth setup | OAuth + API key | Azure AD app + OAuth | Password or app password | One-time `nylas init` |
| Output format | JSON (API) | JSON (API) | Sequence numbers | Table or JSON (`--json`) |
| Lines of code | 20-40 (Python/Node) | 30-50 (with token logic) | 15-30 (raw socket) | 1 command |

## How do you search email with Nylas CLI?

The `nylas email search` command sends a query to the Nylas API, which translates it into the provider's native search format. One command works the same way whether the mailbox is Gmail, Outlook, Yahoo, iCloud, or a self-hosted IMAP server. Results come back in under 2 seconds for mailboxes with up to 100,000 messages.

Search by keyword, sender, date range, attachment, or folder. Each flag maps to the provider's native search capability. The CLI handles the translation. Here are practical examples covering the most common search patterns:

```bash
# Full-text search across subject and body
nylas email search "quarterly report"

# Search by sender
nylas email search "project update" --from alice@example.com

# Messages with attachments in a date range
nylas email search "invoice" --has-attachment --after 2026-01-01 --before 2026-04-01

# Search a specific folder
nylas email search "deploy" --in INBOX

# Combine filters: unread from a sender after a date
nylas email search "urgent" --from ops@company.com --after 2026-05-01
```

You can also use `nylas email list` with its own filter flags for listing rather than searching. The `list` command returns the most recent messages by default and supports `--from`, `--unread`, `--folder`, `--starred`, and `--limit` flags. Use `search` for keyword queries and `list` for inbox browsing.

```bash
# List unread messages (no keyword needed)
nylas email list --unread --limit 20

# List messages from a sender
nylas email list --from boss@company.com --limit 10

# List starred messages as JSON
nylas email list --starred --json
```

## How do you read search results from the terminal?

After finding a message with `search` or `list`, use `nylas email read` to view its full content. The `read` command takes a message ID (returned in search results) and displays the body in your terminal. Adding `--raw` shows the full MIME source, and `--headers` includes all RFC 5322 headers. The `--mark-read` flag sets the message's read status on the server.

```bash
# Read a specific message by ID
nylas email read msg_abc123

# Show raw MIME source
nylas email read msg_abc123 --raw

# Include all headers
nylas email read msg_abc123 --headers

# Mark as read while viewing
nylas email read msg_abc123 --mark-read
```

## How do you pipe search results for scripting?

Adding `--json` to any search or list command outputs RFC 8259-compliant JSON that pipes directly into `jq`, `awk`, or any JSON-aware tool. This turns the CLI into a data source for shell scripts, monitoring dashboards, and AI agent workflows. Each message object includes 12 fields: `id`, `subject`, `from`, `to`, `date`, `unread`, `snippet`, `folders`, and more.

```bash
# Count unread messages from a sender
nylas email search "project" --from alice@example.com --json | jq 'length'

# Extract subjects from search results
nylas email search "invoice" --has-attachment --json \
  | jq -r '.[].subject'

# Find messages larger than 5 MB and list senders
nylas email list --json --limit 50 \
  | jq -r '.[] | select(.size > 5000000) | .from[0].email'

# Export search results to CSV
nylas email search "report" --after 2026-01-01 --json \
  | jq -r '.[] | [.date, .from[0].email, .subject] | @csv' > results.csv

# Feed search results to an AI agent for triage
nylas email search "urgent" --unread --json \
  | agent triage --rules ./inbox-rules.yml
```

The `--json` flag works with every `nylas email` subcommand. You can chain `search` results into `read` by extracting message IDs with `jq`, then passing them to `nylas email read` in a loop. This pattern processes search results at roughly 3 messages per second, limited by API rate limits rather than network latency.

```bash
# Search, extract IDs, read each message body
nylas email search "contract" --from legal@partner.com --json \
  | jq -r '.[].id' \
  | while read -r mid; do
      nylas email read "$mid" --mark-read
      echo "---"
    done
```

## Next steps

- [Full command reference](https://cli.nylas.com/docs/commands) — every flag and subcommand documented
- [Send email from terminal](https://cli.nylas.com/guides/send-email-from-terminal) — send over HTTPS without an SMTP server or Postfix
- [Email CLI tools compared](https://cli.nylas.com/guides/best-cli-email-tools-compared) — Nylas CLI vs mailx, mutt, msmtp, Himalaya, aerc, and swaks
- [Getting started](https://cli.nylas.com/guides/getting-started) — install, authenticate, and run your first command in under 2 minutes
