Guide

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 Product Manager

VerifiedCLI 3.1.1 · Gmail, Outlook, Yahoo, iCloud, IMAP · last tested May 22, 2026

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. 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, 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:

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

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:

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

IMAP SEARCH is a command defined in RFC 3501 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.

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

FeatureGmail operatorsGraph $filterIMAP SEARCHNylas CLI
Search by senderfrom:alice$filter=from/emailAddress/address eqFROM "alice"--from alice
Date filteringafter:2026/01/01receivedDateTime ge 2026-01-01SINCE 01-Jan-2026--after 2026-01-01
Attachment filterhas:attachmenthasAttachments eq trueNot supported--has-attachment
Folder scopingin:inbox/mailFolders/inbox/messagesSELECT INBOX--in INBOX
Full-text search"quarterly report"$search="quarterly report"BODY "report"nylas email search "quarterly report"
Cross-providerGmail onlyMicrosoft onlyAny IMAP serverGmail, Outlook, Yahoo, iCloud, IMAP
Auth setupOAuth + API keyAzure AD app + OAuthPassword or app passwordOne-time nylas init
Output formatJSON (API)JSON (API)Sequence numbersTable or JSON (--json)
Lines of code20-40 (Python/Node)30-50 (with token logic)15-30 (raw socket)1 command

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:

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

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

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

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

# 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