---
title: Gmail
description: Mount Gmail mailboxes as a Mirage filesystem so Python agents can browse labels, messages, threads, and attachments.
icon: envelope
---
The Gmail resource exposes a Gmail account as a virtual filesystem
mounted at some prefix such as `/gmail/`.

For Google OAuth setup, see [Google Workspace Setup](/python/setup/google).

## Config

```python
import os

from mirage import MountMode, Workspace
from mirage.resource.gmail import GmailConfig, GmailResource

config = GmailConfig(
    client_id=os.environ["GOOGLE_CLIENT_ID"],
    client_secret=os.environ["GOOGLE_CLIENT_SECRET"],
    refresh_token=os.environ["GOOGLE_REFRESH_TOKEN"],
)
resource = GmailResource(config=config)
ws = Workspace({"/gmail": resource}, mode=MountMode.READ)
```

## Filesystem Layout

```text
/gmail/
  <label>/
    <yyyy-mm-dd>/
      <sanitized-subject>__<message-id>.gmail.json
      <sanitized-subject>__<message-id>/   # only if message has attachments
        <attachment-filename>
        ...
```

Example:

```text
/gmail/
  INBOX/
    2026-04-12/
      Meeting_Notes__msg123.gmail.json
      Meeting_Notes__msg123/
        report.pdf
        screenshot.png
      Simple_Email__msg456.gmail.json
    2026-04-11/
      Hello__msg789.gmail.json
  SENT/
    2026-04-12/
      Reply__msg012.gmail.json
  STARRED/
  My_Custom_Label/
```

Label directories appear at the root. System labels use the Gmail
label ID (e.g., `INBOX`, `SENT`, `STARRED`). User-created labels
use their label name with spaces replaced by underscores.

### Date Directories

Inside each label, messages are grouped into date subdirectories
formatted as `YYYY-MM-DD`. The date is derived from the message's
`internalDate` (epoch milliseconds) converted to a calendar date.

### Message Files

Each message is stored as a `.gmail.json` file. The filename shape is:

```text
<sanitized-subject>__<message-id>.gmail.json
```

Subjects are sanitized for filesystem safety and truncated when
necessary. The message ID is embedded after `__` and before
`.gmail.json`.

### Attachments

Messages that have attachments get a companion subdirectory with
the same base name (without `.gmail.json`). Decoded attachment
files are placed inside:

```text
Meeting_Notes__msg123.gmail.json    # message JSON
Meeting_Notes__msg123/              # attachment directory
  report.pdf
  screenshot.png
```

## Cache

The Gmail resource uses `IndexCacheStore` (same as Slack, Discord,
and other resources). Index entries store label IDs, message IDs,
and message metadata. There is no separate content cache -- file
content caching is handled by the workspace `IOResult` mechanism.

## Example

```python
import asyncio
import os

from dotenv import load_dotenv

from mirage import MountMode, Workspace
from mirage.resource.gmail import GmailConfig, GmailResource

load_dotenv(".env.development")

config = GmailConfig(
    client_id=os.environ["GOOGLE_CLIENT_ID"],
    client_secret=os.environ["GOOGLE_CLIENT_SECRET"],
    refresh_token=os.environ["GOOGLE_REFRESH_TOKEN"],
)
resource = GmailResource(config=config)


async def main():
    ws = Workspace({"/gmail": resource}, mode=MountMode.READ)

    # List labels
    r = await ws.execute("ls /gmail/")
    print(await r.stdout_str())

    # List date directories in INBOX
    r = await ws.execute("ls /gmail/INBOX/")
    print(await r.stdout_str())

    # List messages for a specific date
    r = await ws.execute("ls /gmail/INBOX/2026-04-12/")
    print(await r.stdout_str())

    # Read a message
    r = await ws.execute(
        "cat /gmail/INBOX/2026-04-12/Meeting_Notes__msg123.gmail.json")
    print(await r.stdout_str())

    # Extract subject with jq
    r = await ws.execute(
        'jq ".subject"'
        " /gmail/INBOX/2026-04-12/Meeting_Notes__msg123.gmail.json")
    print(await r.stdout_str())

    # List attachments
    r = await ws.execute("ls /gmail/INBOX/2026-04-12/Meeting_Notes__msg123/")
    print(await r.stdout_str())

    # Search across all messages
    r = await ws.execute('rg "quarterly" /gmail/INBOX/')
    print(await r.stdout_str())

    # Tree view
    r = await ws.execute("tree -L 2 /gmail/INBOX/")
    print(await r.stdout_str())

    # Triage unread messages
    r = await ws.execute('gws gmail +triage --query "is:unread" --max 5')
    print(await r.stdout_str())

    # Send an email
    r = await ws.execute(
        'gws gmail +send --to "user@example.com"'
        ' --subject "Hello from MIRAGE"'
        ' --body "This email was sent via the MIRAGE Gmail resource."')
    print(await r.stdout_str())


if __name__ == "__main__":
    asyncio.run(main())
```

See `examples/gmail/gmail.py` for the full working example.

## Finding IDs

Resource-specific commands require message IDs. These can be
extracted from the filesystem:

```bash
# Message ID -- embedded in filename after "__"
ls /gmail/INBOX/2026-04-12/
# -> Meeting_Notes__msg123.gmail.json   <- message_id = msg123

# Extract message ID from filename
basename /gmail/INBOX/2026-04-12/Meeting_Notes__msg123.gmail.json .gmail.json
# -> Meeting_Notes__msg123
# The part after "__" is the message ID: msg123

# Read a message then reply
gws gmail +read --id msg123
gws gmail +reply --message-id msg123 --body "Thanks for the notes"
```

## Working with Large Labels

Labels with many messages are split into date directories. Tips
for efficient access:

```bash
# List available dates
ls /gmail/INBOX/

# Check message count for a specific date
ls /gmail/INBOX/2026-04-12/ | wc -l

# Read only the most recent date
ls /gmail/INBOX/ | tail -n 1

# Search across all dates in a label
rg "keyword" /gmail/INBOX/

# Extract specific fields to reduce output
jq -r '.subject' /gmail/INBOX/2026-04-12/*.gmail.json | head -n 20

# Find messages with attachments
find /gmail/INBOX/2026-04-12/ -type d

# Tree view of a label
tree -L 2 /gmail/INBOX/
```

## Shell Commands

Standard commands available on the mounted Gmail tree:

| Command         | Notes                                      |
| --------------- | ------------------------------------------ |
| `ls`            | List labels, dates, messages, attachments  |
| `cat`           | Read message JSON or attachment content    |
| `head` / `tail` | First/last N lines                         |
| `grep` / `rg`   | Pattern search (file or directory level)   |
| `jq`            | Query message JSON fields                  |
| `wc`            | Line/word/byte counts                      |
| `stat`          | File metadata (name, size, type)           |
| `find`          | Recursive search with `-name`, `-maxdepth` |
| `tree`          | Directory tree view                        |
| `basename`      | Extract filename from path                 |
| `dirname`       | Extract directory from path                |
| `realpath`      | Resolve path to absolute form              |
| `nl`            | Number lines of output                     |

Resource-specific commands follow the official
[Google Workspace CLI](https://github.com/googleworkspace/cli) syntax:
ergonomic `gws gmail +<helper>` commands for common tasks, plus a raw
`gws gmail <resource> <method>` passthrough for any Gmail API method.

### `gws gmail +send`

Send a new email.

```bash
gws gmail +send --to "user@example.com" --subject "Hello" --body "Hi there"
```

| Option      | Required | Description             |
| ----------- | -------- | ----------------------- |
| `--to`      | yes      | Recipient email address |
| `--subject` | yes      | Email subject line      |
| `--body`    | yes      | Email body text         |

Returns the sent message JSON.

### `gws gmail +reply`

Reply to a message.

```bash
gws gmail +reply --message-id msg123 --body "Thanks for the update"
```

| Option         | Required | Description      |
| -------------- | -------- | ---------------- |
| `--message-id` | yes      | Gmail message ID |
| `--body`       | yes      | Reply body text  |

Returns the sent reply JSON.

### `gws gmail +reply-all`

Reply-all to a message.

```bash
gws gmail +reply-all --message-id msg123 --body "Acknowledged by the team"
```

| Option         | Required | Description      |
| -------------- | -------- | ---------------- |
| `--message-id` | yes      | Gmail message ID |
| `--body`       | yes      | Reply body text  |

Returns the sent reply JSON.

### `gws gmail +forward`

Forward a message to another recipient.

```bash
gws gmail +forward --message-id msg123 --to "colleague@example.com"
```

| Option         | Required | Description             |
| -------------- | -------- | ----------------------- |
| `--message-id` | yes      | Gmail message ID        |
| `--to`         | yes      | Recipient email address |

Returns the forwarded message JSON.

### `gws gmail +triage`

Search and triage emails using Gmail query syntax.

```bash
gws gmail +triage --query "is:unread" --max 10
```

| Option    | Required | Description                         |
| --------- | -------- | ----------------------------------- |
| `--query` | yes      | Gmail search query                  |
| `--max`   | no       | Maximum number of results to return |

Returns matching messages as JSON.

### `gws gmail +read`

Read a message by its ID.

```bash
gws gmail +read --id msg123
```

| Option | Required | Description      |
| ------ | -------- | ---------------- |
| `--id` | yes      | Gmail message ID |

Returns the full message JSON.

### Raw API passthrough

Every Gmail Discovery method is also reachable directly. `--params`
carries the path and query parameters (JSON), `--json` the request body,
and the output is the raw API response. The user id is always `me`.

```bash
# List labels
gws gmail users labels list --params '{"userId": "me"}'

# List message ids matching a query
gws gmail users messages list --params '{"userId": "me", "q": "is:unread"}'

# Fetch one message
gws gmail users messages get --params '{"userId": "me", "id": "msg123"}'

# Move a message to Trash
gws gmail users messages trash --params '{"userId": "me", "id": "msg123"}'
```
