> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mirage.strukto.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Gmail

> Mount Gmail mailboxes as a Mirage filesystem so Python agents can browse labels, messages, threads, and attachments.

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 theme={null}
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 theme={null}
/gmail/
  <label>/
    <yyyy-mm-dd>/
      <sanitized-subject>__<message-id>.gmail.json
      <sanitized-subject>__<message-id>/   # only if message has attachments
        <attachment-filename>
        ...
```

Example:

```text theme={null}
/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 theme={null}
<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 theme={null}
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 theme={null}
import asyncio
import os

from dotenv import load_dotenv

from mirage import MountMode, Workspace
from mirage.commands.cli.builtin.gws import GWS
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)
    ws.register_cli("gws", GWS, config.model_dump())

    # 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 theme={null}
# 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 theme={null}
# 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                     |

Acting on Gmail (send, reply, forward, triage, raw API calls) goes
through the [gws CLI](/python/cli/gws) when installed.
