Skip to main content

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.

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.

Config

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

/gmail/
  <label>/
    <yyyy-mm-dd>/
      <sanitized-subject>__<message-id>.gmail.json
      <sanitized-subject>__<message-id>/   # only if message has attachments
        <attachment-filename>
        ...
Example:
/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:
<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:
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

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:
# 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:
# 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:
CommandNotes
lsList labels, dates, messages, attachments
catRead message JSON or attachment content
head / tailFirst/last N lines
grep / rgPattern search (file or directory level)
jqQuery message JSON fields
wcLine/word/byte counts
statFile metadata (name, size, type)
findRecursive search with -name, -maxdepth
treeDirectory tree view
basenameExtract filename from path
dirnameExtract directory from path
realpathResolve path to absolute form
nlNumber lines of output
Resource-specific commands:

gws-gmail-send

Send a new email.
gws-gmail-send --to "user@example.com" --subject "Hello" --body "Hi there"
OptionRequiredDescription
--toyesRecipient email address
--subjectyesEmail subject line
--bodyyesEmail body text
Returns the sent message JSON.

gws-gmail-reply

Reply to a message.
gws-gmail-reply --message-id msg123 --body "Thanks for the update"
OptionRequiredDescription
--message-idyesGmail message ID
--bodyyesReply body text
Returns the sent reply JSON.

gws-gmail-reply-all

Reply-all to a message.
gws-gmail-reply-all --message-id msg123 --body "Acknowledged by the team"
OptionRequiredDescription
--message-idyesGmail message ID
--bodyyesReply body text
Returns the sent reply JSON.

gws-gmail-forward

Forward a message to another recipient.
gws-gmail-forward --message-id msg123 --to "colleague@example.com"
OptionRequiredDescription
--message-idyesGmail message ID
--toyesRecipient email address
Returns the forwarded message JSON.

gws-gmail-triage

Search and triage emails using Gmail query syntax.
gws-gmail-triage --query "is:unread" --max 10
OptionRequiredDescription
--queryyesGmail search query
--maxnoMaximum number of results to return
Returns matching messages as JSON.

gws-gmail-read

Read a message by its ID.
gws-gmail-read --id msg123
OptionRequiredDescription
--idyesGmail message ID
Returns the full message JSON.