Skip to main content
Glama
README.md
# Agent Mail Gateway

[![Tests](https://img.shields.io/github/actions/workflow/status/dominikamann/agent-mail-gateway/ci.yml?branch=main&label=tests&style=flat-square)](https://github.com/dominikamann/agent-mail-gateway/actions/workflows/ci.yml)
[![Release](https://img.shields.io/github/v/release/dominikamann/agent-mail-gateway?style=flat-square)](https://github.com/dominikamann/agent-mail-gateway/releases)
[![Docker image](https://img.shields.io/badge/docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/dominikamann/agent-mail-gateway/pkgs/container/agent-mail-gateway)
[![MCP](https://img.shields.io/badge/MCP-server-6E56CF?style=flat-square)](https://modelcontextprotocol.io)
[![Node](https://img.shields.io/badge/node-%E2%89%A524-339933?style=flat-square&logo=node.js&logoColor=white)](package.json)
[![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178C6?style=flat-square&logo=typescript&logoColor=white)](tsconfig.json)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](LICENSE)
[![MCP server on Glama](https://glama.ai/mcp/servers/dominikamann/agent-mail-gateway/badges/score.svg)](https://glama.ai/mcp/servers/dominikamann/agent-mail-gateway)
[![Sponsor](https://img.shields.io/badge/support-me-ff5e5b?style=flat-square&logo=ko-fi)](https://github.com/sponsors/dominikamann)

**Give every AI agent its own email address — without giving it the keys to the mailbox.**

Agent Mail Gateway is a small self-hosted service (one Docker container) that sits between your
AI agents and ordinary mailboxes on your existing mail server. Each agent gets **one API key for
exactly one mailbox**. Through the gateway it can read and answer mail, send attachments and
manage calendar invitations — but only with the people you allow, and every outgoing message is
checked before it leaves.

Agents talk to it over **MCP** (Model Context Protocol) or a plain **REST API**. Mail arrives as
**Markdown** instead of HTML, which saves a lot of tokens.

---

## Why

Giving an agent the IMAP/SMTP password of a mailbox means it can read everything, write to
anyone, and a single prompt injection in an incoming email can make it leak data or spam people.
The gateway keeps the password to itself and enforces your rules on every request:

| Without the gateway | With the gateway |
|---|---|
| Agent knows the mailbox password | Agent only has an API key for its mailbox |
| Reads every mail, incl. spam and phishing | Sees only mail from senders you allow |
| Can write to anyone | Can only write to recipients you allow |
| Sloppy or wrong mails go out | Every mail is checked before sending (rules, optional LLM, your policies) |
| Malware and prompt injections reach the agent | Attachments and content are checked on arrival: hidden or flagged |
| HTML mails cost thousands of tokens | Mail arrives as compact Markdown |

## Features

**Mailbox access**
- List, search, read, mark, delete; attachments in and out — as text, base64 or straight from
  a received message, so agents never have to juggle base64 for a CSV.
- PDFs and images as text for the agent (`get_attachment` with `format: "text"`): PDF text
  layer built in, images via a vision model or local OCR (Tesseract).
- Reply (incl. reply-all) and forward — the gateway fills in recipients, `Re:`/`Fwd:` and threading.
- HTML → Markdown for reading, Markdown → HTML for sending.

**Calendar**
- Send, update and cancel invitations; they update cleanly in Outlook, Gmail and Apple Calendar.
- See who accepted or declined your invitations.
- Read invitations you receive and accept, decline or tentatively accept them.

**Control and safety**
- Allow lists per mailbox for who the agent may receive mail from and write to
  (`name@domain` or `*@domain`). Everything else is invisible to the agent and moved to Trash
  (or kept).
- Forged senders are rejected (SPF/DKIM/DMARC as checked by your mail server).
- Attachment and content checks on incoming mail, each set to hide, flag or off and skippable
  per sender: dangerous files (programs, macros, disguised file types — on by default), virus
  scan with ClamAV, and a prompt injection check of text, PDFs and images (details
  [below](#incoming-mail-checks)).
- Send limit per hour, audit log of every send, rejection and deletion.
- No fast retries on wrong passwords, so your server's fail2ban never blocks the gateway.

**Review before sending** (details [below](#review-before-sending))
- Built-in rules catch empty mails, attachment-only mails, "see attached" without attachment,
  leftover placeholders and accidental duplicates — on by default.
- Optional LLM review (e.g. a local model in Ollama) for "is this mail complete and sensible?".
- Optional **policies**: your own rules in plain language, for everyone or for specific
  recipients — e.g. *"never share financial information"*, *"never mention gifts to Alex"*.

**Integration**
- MCP over Streamable HTTP, a stdio bridge for stdio-only clients, and a REST API with
  OpenAPI docs at `/docs`.
- Signed webhooks when new mail arrives (compatible with Hermes Agent webhook routes).
- [Hermes Agent](https://hermes-agent.nousresearch.com/) plugin with a skill that teaches the
  agent how to use its mailbox.
- Works with any IMAP/SMTP server: Plesk, IONOS, Outlook, Postfix/Dovecot, …

## How it works

```
 Agent ──MCP / REST + API key──▶ ┌────────────────────────────────────────┐
                                 │ Auth       key → exactly one mailbox   │
 Agent ◀──signed webhook──────── │ Policy     who may write / be written  │
                                 │ Review     rules · LLM · your policies │
                                 │ Inbound    file types · virus · inject.│
                                 │ Converter  HTML ⇄ Markdown             │
                                 │ Calendar   invitations and replies     │
                                 │ Mailbox    IMAP + instant new-mail push│──IMAP──▶ your mail server
                                 │ Sender     SMTP + copy to "Sent"       │──SMTP──▶
                                 └────────────────────────────────────────┘
                                   config.yaml + .env (read-only)
```

Mail stays on your mail server; the gateway only keeps a small SQLite file with its own state.

## Quick start

> **Use a dedicated mailbox for each agent** (for example `assistant@yourmailserver.eu`), not your
> personal one: by default, mail from senders that are not on the allow list is moved to Trash.

1. Get the files:
   ```bash
   mkdir agent-mail-gateway && cd agent-mail-gateway
   curl -LO https://raw.githubusercontent.com/dominikamann/agent-mail-gateway/main/docker-compose.yml
   curl -L -o config.yaml https://raw.githubusercontent.com/dominikamann/agent-mail-gateway/main/config.example.yaml
   curl -L -o .env https://raw.githubusercontent.com/dominikamann/agent-mail-gateway/main/.env.example
   ```
2. Edit `config.yaml`: one entry per agent with its mail server, login and allow lists.
3. Put the secrets into `.env` (`openssl rand -hex 32` makes a good API key).
4. Start it and check:
   ```bash
   docker compose up -d
   curl http://localhost:8080/health   # {"status":"ok"}; with a key also that mailbox's state
   curl -H "Authorization: Bearer <the AGENT_API_KEY from .env>" http://localhost:8080/v1/mailbox
   ```

A minimal mailbox entry:

```yaml
mailboxes:
  - name: assistant
    address: youragent@yourmailserver.eu
    api_key: ${AGENT_API_KEY}
    imap: { host: mail.yourmailserver.eu, port: 993, security: tls }
    smtp: { host: mail.yourmailserver.eu, port: 465, security: tls }
    username: youragent@yourmailserver.eu
    password: ${AGENT_MAIL_PASSWORD}
    allow_receive_from: [you@yourmailserver.eu, "*@yourcompany.eu"]
    allow_send_to: [you@yourmailserver.eu]
```

Every option is explained in [docs/configuration.md](docs/configuration.md).

## Review before sending

Agents are sometimes sloppy — an email with only an attachment, "please find attached" without
a file, `Hello {name}`. The gateway checks every outgoing email and invitation **before** it is
sent. If something is wrong, nothing is sent and the agent gets a clear error with the reasons
(`review_rejected`), so it can fix the message and try again. Rejected attempts don't count
towards the send limit.

There are three layers; you choose per mailbox:

| Layer | Default | What it does |
|---|---|---|
| **Rules** | on (`block`) | Fixed checks without AI: empty text, only an attachment, missing subject, attachment mentioned but missing, leftover placeholders, the same mail twice within 10 minutes, invitations in the past. |
| **LLM review** | off | Asks a language model whether the message is complete and makes sense (e.g. does the reply actually answer the question?). |
| **Policies** | off | Your own rules in plain language, checked by the language model — for all recipients or only for specific ones. |

Rules and the LLM review can block the message, only warn (send anyway and report it), or be
switched off; each policy rule either blocks or warns. The check covers new mails, replies,
forwards, invitations and their changes, and comments in invitation answers.

```yaml
    review:
      rules: block                       # block | warn | off
      llm:                               # any OpenAI-compatible endpoint, e.g. a local Ollama
        url: http://ollama:11434/v1
        model: llama3.1:8b
        mode: warn                       # quality check: warn | block | off
      policies:
        mode: block                      # what a violation does: block | warn
        rules:
          - rule: Never share financial information such as revenue, prices, invoices, bank details or salaries.
          - rule: Never mention gifts, presents or surprise plans.
            recipients: [alex@yourmailserver.eu]
          - rule: Never send calendar invitations.
            recipients: [sam@yourmailserver.eu]
```

Policies also read attachments: text files, PDFs, and images when `attachments.read_images` is
set. A rule with `recipients` applies only when one of those people receives the message (To, Cc,
Bcc or invitation attendee). With [Ollama](https://ollama.com) the review runs entirely on your
own machine; no mail content leaves your server. Long messages are checked in parts — nothing
is cut off; part size and limits are configurable to fit your model's context window. The built-in review prompt can be replaced or
extended — see [docs/configuration.md](docs/configuration.md#review-before-sending).

## Incoming mail checks

Allow lists decide *who* can reach your agent; these checks look at *what* arrives. For each
check you choose what a finding does: **hide** the message (like a sender that is not allowed),
**flag** it (the agent sees it with a warning and cannot download dangerous files), or **off**.
With `skip_senders` a check is skipped for trusted senders — so you can still send your agent
instructions by mail.

| Check | Default | What it does |
|---|---|---|
| `dangerous_attachment` | `flag` | Programs and scripts (by name and content), Office files with macros, disguised files (`invoice.pdf` that is not a PDF), ZIPs with programs, password-protected archives. Applies to outgoing files too. |
| `virus` | `hide` | Scans every attachment with [ClamAV](https://www.clamav.net) (optional container); also outgoing. |
| `prompt_injection` | `flag` | The model from `review.llm` checks text, PDFs and images for instructions aimed at an AI. |
| `scan_failed` | `flag` | A check could not run (scanner or model unreachable). |

```yaml
    attachments:
      clamav: { host: clamav, port: 3310 }          # optional, see docker-compose.yml
      read_images: { engine: ocr, languages: [eng, deu] }   # or a vision model
    inbound_checks:
      prompt_injection:
        action: flag
        skip_senders: [you@yourmailserver.eu]       # your own instructions are not flagged
      dangerous_attachment: { action: hide }
```

Images are read by a vision model (e.g. `qwen2.5vl` in Ollama) or by OCR inside the gateway
([Tesseract](https://github.com/naptha/tesseract.js)); PDFs by [pdf.js](https://mozilla.github.io/pdf.js/).
Everything runs on your own machine if you want it to. Details:
[docs/configuration.md](docs/configuration.md#attachments-and-incoming-mail-checks).

## Using it

### MCP tools

Point any MCP client at `http://<host>:8080/mcp` with the header
`Authorization: Bearer <api key>`, or use the [stdio bridge](docs/stdio.md).

| Tool | What it does |
|---|---|
| `get_mailbox_info` | Own address, current date and time, allow lists, limits and the review rules/policies |
| `list_messages` | Received mail, newest first; filter by unread, or search by text, sender, subject, date |
| `read_message` | One message as Markdown, incl. attachments list and received invitations |
| `get_attachment` | Download an attachment, or read a PDF/image as text (`format: "text"`) |
| `mark_message` / `delete_message` | Mark read/unread; move to Trash (if allowed) |
| `send_message` | Send a new email (Markdown, attachments) |
| `reply_message` / `forward_message` | Reply (or reply-all) in the thread; forward with attachments |
| `create_event` / `update_event` / `cancel_event` | Send, change and cancel invitations |
| `list_events` / `get_event` | Own events with attendee responses |
| `respond_to_invitation` | Accept, decline or tentatively accept a received invitation |

### REST

```bash
# send a message
curl -X POST http://localhost:8080/v1/messages \
  -H "Authorization: Bearer $AGENT_API_KEY" -H "Content-Type: application/json" \
  -d '{"to":["you@yourmailserver.eu"],"subject":"Daily report","body_markdown":"All **green** today."}'

# unread mail
curl -H "Authorization: Bearer $AGENT_API_KEY" "http://localhost:8080/v1/messages?unread=true"
```

All endpoints, error codes and the webhook format: [docs/api.md](docs/api.md).

### Hermes Agent

Connect the MCP server in `~/.hermes/config.yaml` and install the plugin that teaches your
agents to use their mailbox safely:

```bash
hermes plugins install dominikamann/agent-mail-gateway/integrations/hermes/agent-mail-gateway --enable
```

Step by step, including waking the agent on new mail: [docs/hermes.md](docs/hermes.md).

## Documentation

- [Configuration](docs/configuration.md) — every option, allow lists, review, ports, sender authentication
- [REST API, webhooks and MCP tools](docs/api.md)
- [Hermes Agent integration and plugin](docs/hermes.md)
- [stdio clients](docs/stdio.md)
- [Security model](docs/security.md)
- [Contributing](CONTRIBUTING.md) · [Changelog](CHANGELOG.md)

[![Agent Mail Gateway on Glama](https://glama.ai/mcp/servers/dominikamann/agent-mail-gateway/badges/card.svg)](https://glama.ai/mcp/servers/dominikamann/agent-mail-gateway)

## Security

Run the gateway on a private network or behind a reverse proxy with TLS — API keys travel in
the `Authorization` header. Report vulnerabilities privately as described in
[SECURITY.md](SECURITY.md).

## License

[MIT](LICENSE)

---

<p align="center">Proudly provided by <a href="https://amannlabs.eu">amannlabs.eu</a></p>

TDQS

A4.6/5.0

Scored across 15 tools

Disambiguation5/5

Each tool maps to a distinct resource+action: send/reply/forward are clearly separated with explicit guidance on when to use which, and the event tools (create/update/cancel/get/list) plus respond_to_invitation have unambiguous boundaries. mark_message vs delete_message and read_message vs list_messages are also well differentiated.

Naming Consistency5/5

Nearly all names follow a consistent verb_noun pattern (list_messages, read_message, send_message, create_event, update_event, cancel_event). Minor variations like get_mailbox_info and respond_to_invitation still fit the same verb-first convention.

Tool Count5/5

15 tools cleanly cover two related domains (mail and calendar) without redundancy; each tool has a clear, non-overlapping role. The count sits comfortably in the well-scoped range.

Completeness5/5

Mail lifecycle is fully covered (list/search, read, attachment, mark, delete, send, reply, forward) and calendar coverage spans create/update/cancel/get/list plus invitation responses. No obvious dead ends for the stated mail-and-calendar gateway purpose.

Maintenance

ActivityMaintained
ResponsivenessWithin a week