OWUI-Email-MCP
by Th3R3alDuk3
README.md
# OWUI-Email-MCP
[](https://github.com/Th3R3alDuk3/OWUI-Email-MCP/actions/workflows/docker.yml)
[](https://github.com/Th3R3alDuk3/OWUI-Email-MCP/tags)
[](pyproject.toml)
[](LICENSE)
> Email drafts via MCP for OpenWebUI. Template-driven, plain text or HTML, nothing sent.
Email drafting for OpenWebUI over the Model Context Protocol. The model fills
a predefined template; the server stores the finished draft behind a **short
link**. With `HTML_FORMAT=true` the link downloads a ready-to-open `.eml`
file (HTML), otherwise it redirects straight to a `mailto:` draft (plain
text) — either way an editable draft in the user's own mail client. Nothing is
sent automatically, and the model never has to stream the whole email token
by token.
---
## ✨ Highlights
- **Fast by design** — the model outputs a ~10-token short link; the draft
is built server-side and only lives behind that link
- **Two formats, one switch** — `HTML_FORMAT=true`: `.eml` download marked
`X-Unsent: 1` that opens as an editable HTML draft (images, inline
styles); `HTML_FORMAT=false`: instant `mailto:` redirect (plain text)
- **One click to the draft** — either way the draft opens in the user's
default mail client; links expire after a TTL
- **Nothing is sent** — the user's own mail client opens the draft; the
user stays in control
- **Multi-user by design** — JWT auth against OpenWebUI's secret, per-user
rate limiting
- **Stateless-ish** — drafts live in a TTL cache in memory
(powered by cachetools); no database, no files
## 🚀 Setup
```bash
uv sync
cp .env.example .env
```
`.env.example` documents every setting; the ones you must set:
- `JWT_SECRET` → OpenWebUI's `WEBUI_SECRET_KEY`
- `PUBLIC_BASE_URL` → URL of this server as reachable **from the user's
browser** (it is embedded in the short links), e.g. `http://192.168.1.10:8000`
- `HTML_FORMAT` → `true` for HTML drafts via `.eml` download, `false` for
plain text via instant `mailto:` redirect
## 🏃 Run
```bash
uv run python main.py
```
The server listens on `0.0.0.0:8000`:
| Endpoint | Auth | Purpose |
|---|---|---|
| `/mcp` | OpenWebUI JWT | MCP (streamable HTTP) for OpenWebUI's External Tools |
| `/m/{id}` | none | the draft: `mailto:` redirect (txt) or `.eml` download (html) |
| `/static/*` | none | static assets (e.g. the logo referenced by the email templates) |
In OpenWebUI: Admin Settings → External Tools → add server of type
**MCP (Streamable HTTP)** with URL `http://<host>:8000/mcp` and auth
**Session**. The model gets `list_email_templates` and `compose_email` and
is instructed to reply with the short link only.
## 🐳 Docker (optional)
Prebuilt images are published to **ghcr.io** on pushes to `main` (`latest`)
and on version tags (`X.Y.Z`):
```bash
docker run -d -p 8000:8000 \
--restart unless-stopped \
--env-file .env \
-v ./templates:/app/templates \
--name owui-email-mcp \
ghcr.io/th3r3alduk3/owui-email-mcp:latest
```
Or build the image locally: `docker build -t owui-email-mcp .` — the volume
mount keeps the templates editable without rebuilding the image.
## 🛠️ Tools
| Tool | Description |
|---|---|
| `compose_email` | fill a template, store the draft, return the short link |
| `list_email_templates` | list templates (name → template text) |
## 📝 Templates
Templates live in `templates/` (name = filename without suffix) and
are read fresh on every call — edit them without restarting. `HTML_FORMAT`
selects which files are used:
- `HTML_FORMAT=false` — `*.txt` files: first line = subject, blank line,
then the body; the short link redirects to a `mailto:` draft
- `HTML_FORMAT=true` — `*.html` files: the `<title>` is the subject; the
short link downloads an `.eml` draft. Use inline styles only (a `<style>`
block's CSS braces would clash with `str.format`); images referenced as
`src="/static/..."` are rewritten to absolute `PUBLIC_BASE_URL` links so
mail clients can load them
- `{placeholders}` are allowed anywhere and are filled via `str.format`
with the values from the compose call; literal braces are written `{{`
and `}}`
## ⚠️ Limits
- Drafts are held in memory only and expire after `LINK_TTL`
(default 24 h); at most `MAX_STORED_EMAILS` are kept. A restart clears
all links.
- The draft link is unauthenticated by design (the browser opening it has
no OpenWebUI session): anyone with the unguessable link can read the
draft until it expires. Don't put secrets in templates.
- MCP requests are rate-limited per user (`RATE_LIMIT_RPS`/`RATE_LIMIT_BURST`),
keyed on the JWT's `id` claim; tool errors never leak internals
(`mask_error_details`).
- The server speaks plain HTTP — put it behind a reverse proxy for TLS.
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues