email-notify
Allows sending emails through Gmail's SMTP server using an authorization code.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@email-notifysend a test email to admin@example.com"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
email-notify
基于 Docker 的轻量邮件发送微服务。同时对外提供两种调用方式:
REST HTTP 接口(给传统应用)
MCP(Model Context Protocol)服务(给 AI 智能体,外部 AI 远端连 Streamable HTTP / 本地 AI 用 stdio)
底层 SMTP 凭据通过环境变量注入,两种接口共用同一份发送逻辑。
特性
双接口单进程:一个 Starlette + uvicorn 进程同时承载 REST 与 MCP,复用
mailer.py低内存:MCP 用
stateless_http=True,会话内存随连接数零增长;mem_limit: 96m兜底轻镜像:基于
python:3.12-alpine,非 root 用户运行、文件系统read_only双传输:MCP 同时支持 Streamable HTTP(远端 AI)和 stdio(本地 AI,如 Claude Desktop)
统一鉴权:单一
API_KEY同时保护/api/send与/mcp,/healthz放行生产可用:
restart: unless-stopped、日志轮转、内置 healthcheck
Related MCP server: MCP Email Server with SendGrid
目录结构
email-notify/
├── sendmail.py # 原始脚本(保留,向后兼容,可独立运行)
├── app/
│ ├── __init__.py
│ ├── server.py # Starlette: REST 路由 + MCP 挂载 + Bearer 中间件
│ ├── mcp_tools.py # FastMCP 服务 + send_email 工具定义
│ ├── mcp_stdio.py # stdio 传输入口(本地 AI 用)
│ ├── mailer.py # SMTP 发送逻辑(REST 与 MCP 共用,env 驱动)
│ └── auth.py # Bearer Token 校验
├── requirements.txt # mcp[cli] + uvicorn(版本钉死)
├── Dockerfile # python:3.12-alpine,uvicorn 启动
├── docker-compose.yml # 资源限制 + 健康检查 + 日志轮转
├── .env.example
└── README.md快速开始
1. 准备配置
cp .env.example .env
# 生成一个 32 字节随机 API_KEY
openssl rand -hex 32编辑 .env,填入真实 SMTP 配置和生成的 API_KEY:
SMTP_SERVER=smtp.163.com
SMTP_PORT=25
SENDER_MAIL=xxx@163.com
SENDER_PW=xxx
API_KEY=<上面 openssl 生成的串>2. 构建并启动
docker compose up -d --build3. 验证
# 健康检查(无需鉴权)
curl http://localhost:8000/healthz
# {"status":"ok"}接口 1:REST HTTP
POST /api/send — 发送邮件
请求头
名称 | 必填 | 说明 |
| 是 |
|
| 是 |
|
请求体
字段 | 类型 | 必填 | 说明 |
|
| 是 | 收件人数组,元素需含 |
|
| 是 | 主题,非空 |
|
| 是 | HTML 正文,非空 |
响应
状态码 | 响应体 | 含义 |
|
| 发送成功 |
|
| 参数校验失败 / 非 JSON 体 |
|
| Token 缺失或错误 |
|
| SMTP 环境变量未配置 |
|
| SMTP 连接 / 认证 / 投递失败 |
示例
curl -X POST http://localhost:8000/api/send \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"recipients": ["dev@example.com"],
"subject": "测试邮件",
"message_body": "<h1>Hello</h1><p>from email-notify</p>"
}'GET /healthz — 健康检查
无需鉴权,返回 {"status":"ok"},不发起 SMTP 连接。
接口 2:MCP(给 AI 智能体)
服务对外暴露一个 MCP 工具:
工具 | 入参 | 返回 |
|
|
|
AI 客户端有两种连法。
方式 A:Streamable HTTP(远端 AI 推荐)
MCP 端点:http://<host>:8000/mcp
调用时在 HTTP 头里带上 Authorization: Bearer <API_KEY>(与 REST 接口共用同一个 token)。
⚠️ 远程访问必须配置 Host 白名单:SDK 默认只允许本机(
127.0.0.1/localhost)连接。外部 AI 远程连入时,需在.env里设置MCP_ALLOWED_HOSTS,否则会得到421 Invalid Host header。两种取值:
指定主机:
MCP_ALLOWED_HOSTS=notify.example.com:*,10.0.0.5:*(推荐,支持host:*通配端口)全放开:
MCP_ALLOWED_HOSTS=*(关闭 Host 校验,仅靠 Bearer Token 鉴权,适合 Docker / 反代 / 内网)详见下方环境变量;遇到 421 可参考"常见问题"一节。
在支持自定义 HTTP Header 的 MCP 客户端里,把 authentication 配成 Bearer Token 即可。例如用官方 SDK 写客户端:
import asyncio
from mcp.client.session import ClientSession
from mcp.client.streamable_http import streamablehttp_client
async def main():
async with streamablehttp_client(
"http://localhost:8000/mcp",
headers={"Authorization": "Bearer <API_KEY>"},
) as (read, write, _):
async with ClientSession(read, write) as session:
await session.initialize()
result = await session.call_tool("send_email", {
"recipients": ["dev@example.com"],
"subject": "线上告警",
"message_body": "<h1>CPU 超过 90%</h1>",
})
print(result.structuredContent)
asyncio.run(main())用 MCP Inspector 快速调试:
npx -y @modelcontextprotocol/inspector,填入 URLhttp://localhost:8000/mcp与 Bearer Token。
方式 B:stdio(本地 AI,如 Claude Desktop)
stdio 模式把服务作为子进程拉起,无需鉴权(靠进程隔离),所有 SMTP 配置从环境变量传入。
入口:python -m app.mcp_stdio
Claude Desktop 配置示例(claude_desktop_config.json):
{
"mcpServers": {
"email-notify": {
"command": "python",
"args": ["-m", "app.mcp_stdio"],
"env": {
"SMTP_SERVER": "smtp.163.com",
"SMTP_PORT": "25",
"SENDER_MAIL": "xxx@163.com",
"SENDER_PW": "xxx"
}
}
}
}也可直接用镜像跑:
command换成docker,args换成["run","-i","--rm","-e","SMTP_SERVER=...","<image>","python","-m","app.mcp_stdio"]。
环境变量
变量 | 说明 | 示例 |
| SMTP 服务器地址 |
|
| SMTP 端口 |
|
| 发件人邮箱 |
|
| 发件人密码 / 授权码(163/QQ 等需用授权码) |
|
| REST 与 MCP 共用的 Bearer Token,建议 ≥ 32 字节 |
|
| MCP 允许的 Host 头白名单(逗号分隔,支持 |
|
注意:加密模式按
SMTP_PORT自动选择——465/994走隐式 SSL(SMTP_SSL),25/587走 STARTTLS。云服务器默认封出站 25 端口,建议用465。
运维
常用命令
docker compose up -d --build # 构建并后台启动
docker compose logs -f # 跟踪日志
docker compose restart # 重启
docker compose down # 停止并删除容器
docker compose ps # 查看健康状态资源占用
指标 | 典型值 | 上限 |
运行时内存 (RSS) | 50-70 MB |
|
镜像大小 | ~80 MB | - |
启动时间 | < 2s | - |
MCP 会话内存增长 | 无 | 无( |
日志
json-file驱动,轮转10m × 3 份(最多 30MB 落盘)uvicorn access log + 应用业务日志全部打到 stdout,由 docker 收集
调参与扩展
提升 REST 并发
uvicorn 默认单 worker。SMTP 是阻塞 I/O,已在 Starlette 里通过 anyio.to_thread.run_sync 丢到线程池,单进程即可并发处理多个发送。如需更高吞吐,把 Dockerfile CMD 的 --workers 改为 2(需同步调大 mem_limit 到 160m)。
新增 MCP 工具
在 app/mcp_tools.py 加一个 @mcp.tool() 函数即可,MCP 的 HTTP 与 stdio 两条链路自动获得新工具,无需改动 server.py。
安全说明
.env已在.gitignore中,切勿提交真实凭据单一 Bearer Token:
/api/send和/mcp都要校验;stdio 模式免鉴权(本地子进程)非 root 容器 + 只读文件系统(
read_only: true+tmpfs: /tmp)建议:生产环境在前面加反向代理(nginx / traefik)做 TLS 终结和限流
MCP HTTP 安全:建议绑定内网或经反代暴露,不要直接公网无防护开放
常见问题
通常是 IPv6 优先但无 IPv6 路由 + 云厂商封出站 25 端口(腾讯云/阿里云/AWS 默认都封 25)叠加导致。解决:把 SMTP_PORT 改成 465(SSL,云厂商不封):
SMTP_PORT=465代码已按端口自动选择模式:465/994 走隐式 SSL(SMTP_SSL),25/587 走 STARTTLS。无需改代码。
若 465 也连不通,可能是服务器缺 SSL 证书校验链或防火墙问题;可临时改
.env用 587(STARTTLS)尝试。
端口和加密模式不匹配。SMTP_PORT=465 必须配合隐式 SSL(代码已自动处理);若你强行用 25 走 SSL 或 465 走 STARTTLS 会报此错。保持端口默认映射即可:465/994 → SSL,25/587 → STARTTLS。
SENDER_PW 应填邮箱服务商的授权码,不是登录密码。163/126/QQ/Gmail 等均需在邮箱后台单独生成。
MCP 端点与 REST 共用 API_KEY。确认客户端的 Authorization: Bearer <API_KEY> 头与 .env 里的值完全一致。
SDK 默认开启 DNS rebinding 防护,只允许本机(127.0.0.1/localhost)连接。两种解决方式,在 .env 里设置 MCP_ALLOWED_HOSTS:
# 方式1:指定主机白名单(推荐,精确控制,支持 host:* 通配端口)
MCP_ALLOWED_HOSTS=notify.example.com:*
MCP_ALLOWED_HOSTS=notify.example.com:*,10.0.0.5:*
# 方式2:全放开(关闭 Host 校验,仅靠 Bearer Token 鉴权,适合 Docker / 反代 / 内网)
MCP_ALLOWED_HOSTS=*改完 docker compose up -d 重启即可。注意方式1里的 Host 是客户端发来的 Host: 头值(通常是 域名:端口),不是 URL 路径。
mem_limit: 96m 过低。检查 docker compose logs,临时调到 128m 排查。
可以。sendmail.py 保留未动,可独立 python sendmail.py 运行。新服务能力在 app/ 包内,互不干扰。
设计取舍
决策 | 选择 | 理由 |
Web 框架 | Starlette + uvicorn | 单一 ASGI 进程,能同时挂 MCP(async)与 REST;轻量 |
MCP 传输 | Streamable HTTP + stdio | 远端 AI 用 HTTP,本地 AI 用 stdio;共用同一份 tool 定义 |
MCP 模式 |
| 会话内存零增长,契合低内存诉求 |
Worker 数 | 1 | 阻塞 SMTP 用线程池并发,单 worker 把内存压到最低 |
鉴权 | 单一 | REST 与 MCP 共用,少一个 env 变量 |
健康检查 | 不连 SMTP | 防止探针触发外部连接 |
邮件连接 | 每次新建 | 低内存优先,避免常驻连接池 |
License
MIT
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/achenglike/email-notify'
If you have feedback or need assistance with the MCP directory API, please join our Discord server