OWUI-Email-MCP
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 fillsa predefined template; the server stores the finished draft behind a shortlink. With HTML_FORMAT=true the link downloads a ready-to-open .emlfile (HTML), otherwise it redirects straight to a mailto: draft (plaintext) — either way an editable draft in the user's own mail client. Nothing issent automatically, and the model never has to stream the whole email tokenby token.
✨ Highlights
- Fast by design — the model outputs a ~10-token short link; the draftis built server-side and only lives behind that link
- Two formats, one switch —
HTML_FORMAT=true:.emldownload markedX-Unsent: 1that opens as an editable HTML draft (images, inlinestyles);HTML_FORMAT=false: instantmailto:redirect (plain text) - One click to the draft — either way the draft opens in the user'sdefault mail client; links expire after a TTL
- Nothing is sent — the user's own mail client opens the draft; theuser stays in control
- Multi-user by design — JWT auth against OpenWebUI's secret, per-userrate limiting
- Stateless-ish — drafts live in a TTL cache in memory(powered by cachetools); no database, no files
🚀 Setup
uv sync
cp .env.example .env
.env.example documents every setting; the ones you must set:
JWT_SECRET→ OpenWebUI'sWEBUI_SECRET_KEYPUBLIC_BASE_URL→ URL of this server as reachable from the user'sbrowser (it is embedded in the short links), e.g.http://192.168.1.10:8000HTML_FORMAT→truefor HTML drafts via.emldownload,falseforplain text via instantmailto:redirect
🏃 Run
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 typeMCP (Streamable HTTP) with URL http://<host>:8000/mcp and authSession. The model gets list_email_templates and compose_email andis 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):
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 volumemount 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) andare read fresh on every call — edit them without restarting. HTML_FORMATselects which files are used:
HTML_FORMAT=false—*.txtfiles: first line = subject, blank line,then the body; the short link redirects to amailto:draftHTML_FORMAT=true—*.htmlfiles: the<title>is the subject; theshort link downloads an.emldraft. Use inline styles only (a<style>block's CSS braces would clash withstr.format); images referenced assrc="/static/..."are rewritten to absolutePUBLIC_BASE_URLlinks somail clients can load them{placeholders}are allowed anywhere and are filled viastr.formatwith 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 mostMAX_STORED_EMAILSare kept. A restart clearsall links. - The draft link is unauthenticated by design (the browser opening it hasno OpenWebUI session): anyone with the unguessable link can read thedraft 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'sidclaim; tool errors never leak internals(mask_error_details). - The server speaks plain HTTP — put it behind a reverse proxy for TLS.