Skip to main content

Mailhub

Mailhub is a local, MCP-first personal-operations hub for AI assistants and their humans.

Features

  • Mail — Gmail, Microsoft Graph (Outlook.com), IMAP/SMTP
  • Calendar — Google Calendar, Microsoft Graph, CalDAV
  • Contacts — Google People, Microsoft Graph, CardDAV
  • Tasks — Microsoft To Do (Graph), CalDAV VTODO
  • MCP-first — stdio MCP server for AI agents (read-only + full modes)
  • REST API — localhost-only HTTP server for scripts/CLI
  • CLI — mailhub (admin) and mail (user) commands

Quick Start

# Install
uv tool install --editable .

# Initialize config & state
mailhub init

# Configure OAuth clients (one-time)
mailhub config setup-google    # Opens Google Cloud Console, guides OAuth setup
mailhub config setup-microsoft # Opens Entra ID, guides app registration

# Add accounts
mailhub config add-account --alias me --provider gmail --capabilities mail --email me@gmail.com
mailhub config add-account --alias work --provider graph --capabilities mail --email me@outlook.com

# Authorize accounts (opens browser, paste redirect URL)
mailhub auth gmail me --code '<pasted-redirect-url>'
mailhub auth graph work --code '<pasted-redirect-url>'

# Verify health
mailhub doctor

# Start REST server (localhost only)
mailhub serve

# Or run MCP server (read-only by default)
mailhub mcp --mode ro
# Or full mode for mutations
mailhub mcp --mode full

Installation Options

# Install via uv (fast, isolated)
uv tool install zc-mailhub

# Or via pip
pip install zc-mailhub

# Or via pipx
pipx install zc-mailhub

Ephemeral / One-off Use (uvx)

# Run without installing (downloads + caches automatically)
uvx --from zc-mailhub mailhub init
uvx --from zc-mailhub mailhub mcp --mode full
uvx --from zc-mailhub mailhub doctor

# Use specific version
uvx --from zc-mailhub@0.1.2 mailhub mcp --mode full

# Force update to latest
uvx --from zc-mailhub@latest mailhub mcp --mode full

Docker

# Pull and run
docker pull ghcr.io/zeroclue/zc-mailhub:v0.1.2
docker run -p 8787:8787 ghcr.io/zeroclue/zc-mailhub:v0.1.2 serve

# Or use docker-compose (see docker-compose.yml)
docker compose up -d

Development Install

git clone https://github.com/ZeroClue/mailhub
cd mailhub
uv sync --extra dev
uv run mailhub init

Using the mail CLI

The mail command is a thin HTTP client to the REST API (requires mailhub serve running):

# List accounts
mail accounts

# List folders
mail folders me

# Search messages
mail search me "from:github.com newer_than:7d"
mail search work "hasAttachments:true"

# Get message
mail get me <message-id>

# Send (requires --confirm)
mail send me --to someone@example.com --subject "Hello" --body "Test" --confirm

# Create draft
mail draft me --to someone@example.com --subject "Draft" --body "Content"

Systemd Service (Auto-start)

# Install service files
mkdir -p ~/.config/systemd/user
cp deploy/*.service deploy/*.timer ~/.config/systemd/user/

# Enable and start
systemctl --user daemon-reload
systemctl --user enable --now mailhub.service mailhub-doctor.timer
loginctl enable-linger  # keeps user services alive after logout

Configuration

Config lives at ~/.config/mailhub/config.toml:

[server]
host = "127.0.0.1"
port = 8787

[auth]
ro_token = "..."      # read-only token
full_token = "..."    # full-access token

[send]
allowlist = ["*@example.com"]  # glob patterns
# allow_anywhere = true        # disable allowlist

[gmail]
client_id = "..."
client_secret = "..."

[graph]
client_id = "..."

[accounts.personal]
provider = "gmail"
capabilities = ["mail", "calendar", "contacts"]

[accounts.work]
provider = "graph"
capabilities = ["mail", "calendar", "contacts", "tasks"]

Security

  • REST server binds only to 127.0.0.1 (never 0.0.0.0)
  • No hard-delete — only trash/cancel
  • Mutations require confirm=true + recipient allowlist
  • Audit log (~/.local/state/mailhub/audit.jsonl) never records tokens/bodies
  • Credentials stored with 0600 permissions, atomic writes, fcntl lock
  • OAuth PKCE with random state
  • Message bodies treated as untrusted input

Development

uv sync --extra dev
uv run pytest
uv run mailhub init
uv run mailhub status
uv run mailhub mcp --mode ro

Architecture

mailhub/
├── config.py      # TOML config, validation, provider capabilities
├── store.py       # Credential store (atomic, 0600, fcntl lock)
├── oauth.py       # PKCE OAuth for Google/Microsoft
├── core.py        # Policy engine, allowlist, audit, retry, adapters
├── util.py        # HTML→text, MIME, address parsing
├── gmail.py       # Gmail API adapter
├── graph.py       # Microsoft Graph adapter
├── gcal.py        # Google Calendar adapter
├── gcal_graph.py  # Graph Calendar adapter
├── caldav.py      # CalDAV adapter (read)
├── people.py      # Google People adapter
├── people_graph.py # Graph People adapter
├── carddav.py     # CardDAV adapter (read)
├── tasks.py       # Tasks domain
├── tasks_graph.py # Graph Tasks adapter
├── tasks_caldav.py # CalDAV VTODO adapter (read)
├── app.py         # FastAPI REST server (127.0.0.1 only)
├── cli.py         # mailhub admin CLI
├── mail_cli.py    # mail user CLI (thin REST client)
├── mcp.py         # MCP server (ro + full modes)
├── backup.py      # Backup/restore utilities
└── deploy/        # systemd user units

License

MIT

Man Pages

Install man pages system-wide:

sudo cp docs/man/mailhub.1 /usr/local/share/man/man1/
sudo cp docs/man/mail.1 /usr/local/share/man/man1/
sudo mandb

Then view with:

man mailhub
man mail

Or view locally without installing:

man -l docs/man/mailhub.1
man -l docs/man/mail.1

Shell Completions

Generate completion scripts for your shell:

mailhub --generate-completion bash  > ~/.local/share/bash-completion/completions/mailhub
mailhub --generate-completion zsh   > ${fpath[1]}/_mailhub
mailhub --generate-completion fish  > ~/.config/fish/completions/mailhub.fish

The completion script also covers the mail command (same stub).

Docker Usage

Mailhub provides official Docker images via GitHub Container Registry:

# Quick start with docker-compose
docker compose up -d

# View logs
docker compose logs -f mailhub

# Run MCP server only
docker compose run --rm mailhub-mcp

# Build locally
docker build -t zc-mailhub .
docker run -p 8787:8787 zc-mailhub serve

docker-compose.yml

The provided docker-compose.yml runs two services:

Service Description Port
mailhub REST API server 8787
mailhub-mcp MCP server (full mode) stdio

Configuration and state are persisted via Docker volumes:

volumes:
  mailhub-config:
  mailhub-state:

Building Locally

# Build image
docker build -t zc-mailhub .

# Run REST API
docker run -p 8787:8787 zc-mailhub serve

# Run MCP server
docker run -it zc-mailhub mcp --mode full

Security

The Docker image follows security best practices:

  • Multi-stage build — builder stage for dependencies, minimal runtime image
  • Non-root user — runs as mailhub user (UID/GID 999)
  • No secrets in image — configuration and credentials mounted as volumes
  • Read-only root filesystem (when deployed with read_only: true)
  • Dropped capabilities — cap_drop: ["ALL"], no-new-privileges:true

Volumes

Configuration and state are persisted via Docker volumes:

volumes:
  mailhub-config:
  mailhub-state:

These correspond to ~/.config/mailhub/ and ~/.local/state/mailhub/ on the host.

MCP Configuration

Mailhub's MCP server runs over stdio and works with all major AI clients. Configure it in your client's MCP settings:

Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json on macOS, ~/.config/claude/claude_desktop_config.json on Linux)

{
  "mcpServers": {
    "mailhub": {
      "command": "mailhub",
      "args": ["mcp", "--mode", "full"],
      "env": {
        "MAILHUB_CONFIG": "/home/youruser/.config/mailhub/config.toml"
      }
    }
  }
}

Cursor (.cursor/mcp.json in project root)

{
  "mcpServers": {
    "mailhub": {
      "command": "mailhub",
      "args": ["mcp", "--mode", "full"],
      "env": {
        "MAILHUB_CONFIG": "/home/youruser/.config/mailhub/config.toml"
      }
    }
  }
}

VS Code / GitHub Copilot (.vscode/mcp.json in project root)

{
  "mcpServers": {
    "mailhub": {
      "command": "mailhub",
      "args": ["mcp", "--mode", "full"],
      "env": {
        "MAILHUB_CONFIG": "/home/youruser/.config/mailhub/config.toml"
      }
    }
  }
}

Windsurf (~/.config/windsurf/mcp.json)

{
  "mcpServers": {
    "mailhub": {
      "command": "mailhub",
      "args": ["mcp", "--mode", "full"],
      "env": {
        "MAILHUB_CONFIG": "/home/youruser/.config/mailhub/config.toml"
      }
    }
  }
}

Continue.dev (~/.continue/config.json)

{
  "mcpServers": [
    {
      "name": "mailhub",
      "command": "mailhub",
      "args": ["mcp", "--mode", "full"],
      "env": {
        "MAILHUB_CONFIG": "/home/youruser/.config/mailhub/config.toml"
      }
    }
  ]
}

Zed (~/.config/zed/settings.json)

{
  "mcp": {
    "servers": {
      "mailhub": {
        "command": "mailhub",
        "args": ["mcp", "--mode", "full"],
        "env": {
          "MAILHUB_CONFIG": "/home/youruser/.config/mailhub/config.toml"
        }
      }
    }
  }
}

Using uvx (No Install Required)

{
  "mcpServers": {
    "mailhub": {
      "command": "uvx",
      "args": ["--from", "zc-mailhub", "mailhub", "mcp", "--mode", "full"],
      "env": {
        "MAILHUB_CONFIG": "/home/youruser/.config/mailhub/config.toml"
      }
    }
  }
}

Using pipx (Permanent Install)

{
  "mcpServers": {
    "mailhub": {
      "command": "pipx",
      "args": ["run", "--spec", "zc-mailhub", "mailhub", "mcp", "--mode", "full"],
      "env": {
        "MAILHUB_CONFIG": "/home/youruser/.config/mailhub/config.toml"
      }
    }
  }
}

Using Docker

{
  "mcpServers": {
    "mailhub": {
      "command": "docker",
      "args": ["run", "-i", "--rm", 
        "-v", "mailhub-config:/home/mailhub/.config/mailhub",
        "-v", "mailhub-state:/home/mailhub/.local/state/mailhub",
        "ghcr.io/zeroclue/zc-mailhub:v0.1.2",
        "mcp", "--mode", "full"],
      "env": {}
    }
  }
}

Sourcegraph Cody (.cody/mcp.json in project root)

{
  "mcpServers": {
    "mailhub": {
      "command": "mailhub",
      "args": ["mcp", "--mode", "full"],
      "env": {
        "MAILHUB_CONFIG": "/home/youruser/.config/mailhub/config.toml"
      }
    }
  }
}

Using Docker (Local)

{
  "mcpServers": {
    "mailhub": {
      "command": "docker",
      "args": ["run", "-i", "--rm", "zc-mailhub", "mcp", "--mode", "full"],
      "env": {
        "MAILHUB_CONFIG": "/home/mailhub/.config/mailhub/config.toml"
      }
    }
  }
}

Setting Up Mailhub (MCP / Docker)

The MCP server and Docker container need configuration before they can access your mail/calendar. Run setup commands on the host (not inside MCP), then start the MCP/Docker.

1. Local Install Setup (then use MCP)

# Install
uv tool install zc-mailhub

# Initialize config & state
mailhub init

# Configure OAuth clients (one-time)
mailhub config setup-google    # Opens Google Cloud Console
mailhub config setup-microsoft # Opens Microsoft Entra ID

# Add accounts
mailhub config add-account --alias me --provider gmail --capabilities mail --email me@gmail.com
mailhub config add-account --alias work --provider graph --capabilities mail --email me@outlook.com

# Authorize accounts (opens browser, paste redirect URL)
mailhub auth gmail me --code '<pasted-redirect-url>'
mailhub auth graph work --code '<pasted-redirect-url>'

# Verify health
mailhub doctor

# Now configure your AI client's MCP settings (see above)
# Then restart your AI client

2. Docker Setup

# Start container (creates volumes)
docker compose up -d

# Run setup INSIDE the container
docker compose exec mailhub mailhub init
docker compose exec mailhub mailhub config setup-google
docker compose exec mailhub mailhub config setup-microsoft
docker compose exec mailhub mailhub config add-account --alias me --provider gmail --capabilities mail --email me@gmail.com
docker compose exec mailhub mailhub auth gmail me --code '<pasted-redirect-url>'

# Verify
docker compose exec mailhub mailhub doctor

# Configure MCP client to use Docker (see "Using Docker" MCP config above)

3. uvx Setup (No Install)

# Run setup via uvx
uvx --from zc-mailhub mailhub init
uvx --from zc-mailhub mailhub config setup-google
uvx --from zc-mailhub mailhub config setup-microsoft
uvx --from zc-mailhub mailhub config add-account --alias me --provider gmail --capabilities mail --email me@gmail.com
uvx --from zc-mailhub mailhub auth gmail me --code '<pasted-redirect-url>'
uvx --from zc-mailhub mailhub doctor

# Configure MCP client to use uvx (see "Using uvx" MCP config above)

Key Points

Method Config Location Notes
Local install ~/.config/mailhub/ Standard XDG paths
Docker Docker volumes (mailhub-config, mailhub-state) Persists across container restarts
uvx ~/.config/mailhub/ Same as local install (uses real XDG)

The OAuth flow opens a browser on the host - you'll copy the redirect URL from the browser and paste it back to the terminal running the auth command.

Remote/VPS Setup

# On remote server (with Docker)
ssh user@vps "docker compose up -d"
ssh user@vps "docker compose exec mailhub mailhub init"
ssh user@vps "docker compose exec mailhub mailhub config setup-google"
# ... etc

# Configure local AI client to use SSH tunnel:
{
  "mcpServers": {
    "mailhub": {
      "command": "ssh",
      "args": ["user@vps", "docker", "run", "-i", "--rm", "ghcr.io/zeroclue/zc-mailhub:v0.1.2", "mcp", "--mode", "full"]
    }
  }
}

Using SSH (Remote VPS)

{
  "mcpServers": {
    "mailhub": {
      "command": "ssh",
      "args": ["user@your-vps", "mailhub", "mcp", "--mode", "full"],
      "env": {}
    }
  }
}

Using SSH + Docker (Remote VPS with Docker)

{
  "mcpServers": {
    "mailhub": {
      "command": "ssh",
      "args": ["user@your-vps", "docker", "run", "--rm", "-i", "zc-mailhub", "mcp", "--mode", "full"],
      "env": {}
    }
  }
}

Read-Only Mode

For read-only access (no mutations), change --mode full to --mode ro:

{
  "mcpServers": {
    "mailhub": {
      "command": "mailhub",
      "args": ["mcp", "--mode", "ro"],
      "env": {
        "MAILHUB_CONFIG": "/home/youruser/.config/mailhub/config.toml"
      }
    }
  }
}

MCP Server Capabilities

The MCP server exposes the following capabilities in its initialize response:

  • Tools: All mail operations (search, get, send, draft, move, trash, folders) + status
  • Resources: Not implemented (mail data accessed via tools)
  • Prompts: Not implemented
  • Logging: Supported

Server info returned on initialize:

  • server_name: "mailhub"
  • server_version: "0.1.1" (current package version)
  • instructions: Description of the server and usage hints

Metadata

Release files for zc-mailhub 0.1.5

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for zc-mailhub 0.1.5
File Size Uploaded Provenance Actions
zc_mailhub-0.1.5.tar.gz 228.5 kB With provenance Details for zc_mailhub-0.1.5.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for zc-mailhub 0.1.5
File Interpreter ABI Platform Actions
zc_mailhub-0.1.5-py3-none-any.whl Python 3 none any Details for zc_mailhub-0.1.5-py3-none-any.whl

Total release size: 297.0 kB

Release files / zc_mailhub-0.1.5.tar.gz

Download URL zc_mailhub-0.1.5.tar.gz
Size 228.5 kB
Tags Source
SHA-256 checksum
How to use checksums
34712bdb218b4027e48d6c568e91f333ab325b47d8b45710e1078b2462ded7c1
BLAKE2b-256 checksum
How to use checksums
436e8216ec0fb8cf7fbaee550eac181f77ca0135cde420b260fe77f7f5f5230c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 27, 2026.

Transparency log

Release files / zc_mailhub-0.1.5-py3-none-any.whl

Download URL zc_mailhub-0.1.5-py3-none-any.whl
Size 68.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
655ef0a7262c5b9e3b589402139a32e143ca4083ec986ec1ef6b56884a3f1a7d
BLAKE2b-256 checksum
How to use checksums
a3114184d838dfe85387ff6741335a799e3df425ad8fdfeebb66380c827d0ebb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 27, 2026.

Transparency log

Security for zc-mailhub 0.1.5

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for zc_mailhub-0.1.5.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 27, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page