---
title: Email
description: Mount an IMAP mailbox as a Mirage filesystem and send messages through SMTP from Python workspaces.
icon: at
---
The Email resource exposes any IMAP mailbox as a virtual filesystem
mounted at some prefix such as `/email/`. Sending is handled via SMTP.

## Config

```python
import os

from mirage import MountMode, Workspace
from mirage.resource.email import EmailConfig, EmailResource

config = EmailConfig(
    imap_host=os.environ["IMAP_HOST"],
    smtp_host=os.environ["SMTP_HOST"],
    username=os.environ["EMAIL_USERNAME"],
    password=os.environ["EMAIL_PASSWORD"],
)
resource = EmailResource(config=config)
ws = Workspace({"/email": resource}, mode=MountMode.READ)
```

### Config Reference

| Field       | Type   | Default | Description              |
| ----------- | ------ | ------- | ------------------------ |
| `imap_host` | `str`  |         | IMAP server hostname     |
| `imap_port` | `int`  | `993`   | IMAP port                |
| `smtp_host` | `str`  |         | SMTP server hostname     |
| `smtp_port` | `int`  | `587`   | SMTP port                |
| `username`  | `str`  |         | Email address / login    |
| `password`  | `str`  |         | Password or app password |
| `use_ssl`   | `bool` | `True`  | Use SSL for IMAP         |

### Common Resource Settings

| Resource    | IMAP Host               | SMTP Host             | Notes                      |
| ----------- | ----------------------- | --------------------- | -------------------------- |
| Outlook/365 | `outlook.office365.com` | `smtp.office365.com`  | App password or OAuth2     |
| Yahoo       | `imap.mail.yahoo.com`   | `smtp.mail.yahoo.com` | App password required      |
| Fastmail    | `imap.fastmail.com`     | `smtp.fastmail.com`   | App password               |
| iCloud      | `imap.mail.me.com`      | `smtp.mail.me.com`    | App password               |
| ProtonMail  | `127.0.0.1` (Bridge)    | `127.0.0.1` (Bridge)  | Requires ProtonMail Bridge |
| Self-hosted | Your server hostname    | Your server hostname  | Whatever you configured    |

## Filesystem Layout

```text
/email/
  <folder>/
    <yyyy-mm-dd>/
      <sanitized-subject>__<uid>.email.json
      <sanitized-subject>__<uid>/   # only if message has attachments
        <attachment-filename>
```

Example:

```text
/email/
  INBOX/
    2026-04-14/
      Meeting_Notes__12345.email.json
      Meeting_Notes__12345/
        report.pdf
        screenshot.png
      Simple_Email__12346.email.json
    2026-04-13/
      Hello__12347.email.json
  Sent/
    2026-04-14/
      Reply__12348.email.json
  Drafts/
  Archive/
```

Folder directories appear at the root, using IMAP folder names
(e.g., `INBOX`, `Sent`, `Drafts`).

### Date Directories

Inside each folder, messages are grouped into date subdirectories
formatted as `YYYY-MM-DD`. The date is derived from the message's
`Date` header.

### Message Files

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

```text
<sanitized-subject>__<uid>.email.json
```

### Attachments

Messages that have attachments get a companion subdirectory with
the same base name (without `.email.json`):

```text
Meeting_Notes__12345.email.json    # message JSON
Meeting_Notes__12345/              # attachment directory
  report.pdf
  screenshot.png
```

## Cache

The Email resource uses `IndexCacheStore` (same as Gmail, Slack,
Discord, and other resources). Index entries store folder names,
message UIDs, and message metadata.

## Example

```python
import asyncio
import os

from dotenv import load_dotenv

from mirage import MountMode, Workspace
from mirage.resource.email import EmailConfig, EmailResource

load_dotenv(".env.development")

config = EmailConfig(
    imap_host=os.environ["IMAP_HOST"],
    smtp_host=os.environ["SMTP_HOST"],
    username=os.environ["EMAIL_USERNAME"],
    password=os.environ["EMAIL_PASSWORD"],
)
resource = EmailResource(config=config)


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

    # List folders
    r = await ws.execute("ls /email/")
    print(await r.stdout_str())

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

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

    # Read a message
    r = await ws.execute(
        "cat /email/INBOX/2026-04-14/Meeting_Notes__12345.email.json")
    print(await r.stdout_str())

    # Extract subject with jq
    r = await ws.execute(
        'jq ".subject"'
        " /email/INBOX/2026-04-14/Meeting_Notes__12345.email.json")
    print(await r.stdout_str())

    # List attachments
    r = await ws.execute("ls /email/INBOX/2026-04-14/Meeting_Notes__12345/")
    print(await r.stdout_str())

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

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

    # Triage unread messages
    r = await ws.execute("himalaya envelope list --folder INBOX --unseen --max 5")
    print(await r.stdout_str())

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


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

## Finding UIDs

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

```bash
# UID is embedded in filename after "__"
ls /email/INBOX/2026-04-14/
# -> Meeting_Notes__12345.email.json   <- uid = 12345

# Read a message then reply
himalaya message read --uid 12345 --folder INBOX
himalaya message reply --uid 12345 --folder INBOX --body "Thanks for the notes"
```

## Shell Commands

Standard commands available on the mounted email tree:

| Command         | Notes                                      |
| --------------- | ------------------------------------------ |
| `ls`            | List folders, 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 mail (list, read, send, reply, forward) goes through the
[himalaya CLI](/python/cli/himalaya) when installed.
