Source: https://cli.nylas.com/guides/gmail-api-spam-trash

# Gmail API: List Spam and Trash Messages

Your script lists every message in the mailbox, yet the phishing sample you're hunting never shows up. That's not a bug in your code. The Gmail API excludes SPAM and TRASH from messages.list results unless you ask for them explicitly. This guide covers the three documented ways to read spam and trash: the includeSpamTrash parameter, the labelIds filter, and in:spam search queries, plus the one-flag CLI equivalent.

Written by [Aaron de Mello](https://cli.nylas.com/authors/aaron-de-mello) Senior Engineering Manager

Updated June 6, 2026

> **TL;DR:** `messages.list` skips spam and trash by default. Pass `includeSpamTrash=true` to mix them into results, `labelIds=["SPAM"]` to read spam alone, or `q="in:spam"` for search-style filtering. From the terminal, `nylas email list --folder SPAM` does the same with zero API code.

> **Disclosure:** Nylas CLI is built by Nylas, Inc. This comparison reflects our testing and product understanding as of June 6, 2026.

Command references used in this guide: [`nylas email list`](https://cli.nylas.com/docs/commands/email-list) and [`nylas email folders list`](https://cli.nylas.com/docs/commands/email-folders-list).

## Why doesn't the Gmail API return spam or trash messages?

The Gmail API excludes the SPAM and TRASH system labels from `users.messages.list` responses by default. The `includeSpamTrash` query parameter controls this behavior, defaults to `false`, and per the [messages.list reference](https://developers.google.com/workspace/gmail/api/reference/rest/v1/users.messages/list) will "include messages from `SPAM` and `TRASH` in the results" when set to `true`.

The default makes sense for inbox-style apps, but it breaks three real workloads: abuse and phishing analysis, full-mailbox backup, and false-positive recovery scripts. Each `messages.list` call costs 5 quota units and returns 100 message IDs by default (500 with `maxResults`), so the fix is a parameter change, not extra requests.

## How do you include spam and trash with includeSpamTrash?

Setting `includeSpamTrash=true` on `messages.list` returns spam and trash messages alongside everything else. It widens the result set rather than filtering it, so use it when you need a complete mailbox sweep. Combined with `maxResults=500`, one request covers 5x the default page size.

The Python example below uses `google-api-python-client` and counts how many of the first 500 results carry the SPAM or TRASH label. Note that each follow-up `messages.get` costs 20 quota units, 4x the list call itself.

```python
from googleapiclient.discovery import build

service = build("gmail", "v1", credentials=creds)

# Include spam and trash in a full-mailbox listing
results = service.users().messages().list(
    userId="me",
    includeSpamTrash=True,
    maxResults=500,
).execute()

ids = [m["id"] for m in results.get("messages", [])]
print(f"{len(ids)} messages, spam and trash included")

# Check which labels a specific message carries
msg = service.users().messages().get(
    userId="me", id=ids[0], format="minimal"
).execute()
print(msg["labelIds"])  # e.g. ['SPAM'] or ['TRASH']
```

Without the parameter, the same call silently drops any message labeled SPAM or TRASH. There's no warning and no count of what was excluded, which is why backup scripts that skip this flag under-report mailbox size and miss recoverable mail.

## How do you list only spam messages with labelIds?

Passing `labelIds=["SPAM"]` to `messages.list` returns spam messages only, no `includeSpamTrash` needed. The [API reference](https://developers.google.com/workspace/gmail/api/reference/rest/v1/users.messages/list) states the filter returns messages "with labels that match all of the specified label IDs", so a single label ID gives you exactly that folder. Use `["TRASH"]` the same way for deleted mail.

The third option is the `q` parameter, which per the same reference "supports the same query format as the Gmail search box". That makes `q="in:spam"` equivalent to the labelIds filter, and it composes with other operators like `from:` and `after:` in one string.

```python
# Spam only, via labelIds
spam = service.users().messages().list(
    userId="me", labelIds=["SPAM"], maxResults=100
).execute()

# Trash only
trash = service.users().messages().list(
    userId="me", labelIds=["TRASH"], maxResults=100
).execute()

# Search-style: spam from one sender in the last week
recent = service.users().messages().list(
    userId="me", q="in:spam from:billing@suspicious.example newer_than:7d"
).execute()
```

Pick by intent: `labelIds` for a clean single-folder read, `q` when you need to combine spam scope with sender, date, or attachment conditions. Both approaches cost the same 5 quota units per list call. SPAM and TRASH are 2 of Gmail's 13 built-in system labels, listed in the [Gmail labels guide](https://developers.google.com/workspace/gmail/api/guides/labels).

## How long does Gmail keep trash messages?

Gmail keeps a deleted message in Trash for up to 30 days. According to [Google's recovery documentation](https://support.google.com/mail/answer/7401?hl=en), "Up to 30 days after deletion: You can find the message in Trash" and after 30 days "The message is permanently deleted." Any recovery script you build against the TRASH label is racing that clock.

That window shapes how you schedule automation. A weekly cron job that exports trash to JSON has 4 chances to catch a message before Gmail purges it; a monthly one can miss messages entirely. For spam triage, the practical pattern is the same: list the SPAM label on a schedule shorter than your team's incident-response window, and persist anything you might need as evidence.

## How do you read spam and trash from the CLI?

The `nylas email list --folder SPAM` command returns Gmail spam messages as JSON with no Google Cloud project, OAuth consent screen, or Python client setup. The CLI's `--folder` flag accepts any Gmail system label ID, so `TRASH` works identically, and `--all-folders` sweeps every folder in one run. Auth takes one `nylas auth login`.

```bash
# List spam messages
nylas email list --folder SPAM --limit 20

# List trash as JSON for scripting
nylas email list --folder TRASH --json --limit 50

# Sweep every folder, spam and trash included
nylas email list --all-folders --json --limit 200

# Extract sender addresses from spam for a blocklist review
nylas email list --folder SPAM --json --limit 50 | \
  jq -r '.[].from[0].email' | sort | uniq -c | sort -rn
```

These commands were verified against a live Gmail account on CLI 3.1.16. The same `--folder` syntax works on Outlook and IMAP accounts with their folder names, so a spam-review script written for Gmail ports to other providers without touching the Gmail API's label model. Run [`nylas email folders list`](https://cli.nylas.com/docs/commands/email-folders-list) to see the exact folder IDs on any connected account.

## Next steps

- [Gmail API search queries](https://cli.nylas.com/guides/gmail-api-search-query) — every q operator, including in:, label:, and date filters
- [Gmail Labels API](https://cli.nylas.com/guides/gmail-labels-api) — system vs user labels and how to apply them programmatically
- [Gmail API batchDelete](https://cli.nylas.com/guides/gmail-api-batch-delete) — permanently remove up to 1,000 messages per request, with guardrails
- [Back up emails to JSON](https://cli.nylas.com/guides/backup-emails-to-json) — export messages before the 30-day trash window closes
- [Full command reference](https://cli.nylas.com/docs/commands) — every flag and subcommand documented
