- 项目概述 / Project Overview
- 技术栈 / Technology Stack
- 项目结构 / Project Structure
- 系统架构 / System Architecture
- 数据模型 / Data Models
- 配置系统 / Configuration System
- API Key 鉴权体系 / API Key Authentication
- API 接口 / API Endpoints
- SMTP 服务 / SMTP Service
- Worker 服务 / Worker Service
- JWT 认证与安全 / JWT Authentication & Security
- 缓存层 / Cache Layer
- 消息队列 / Message Queue
- 对象存储 / Object Storage
- 数据库持久化 / Database Persistence
- 实时推送 / Real-time Push (SSE)
- 邮箱前缀生成器 / Email Prefix Generator
- 多 IP 与分布式部署 / Multi-IP & Distributed Deployment
- 管理脚本 / Management Script
- 防御性工程 / Defense Engineering
- 测试体系 / Test Suite
- 部署说明 / Deployment
中文:
MailAPI 是一个对标 mail.tm 的高并发、可分布式部署的临时邮件后端服务,兼容 DuckMail API 标准。采用微服务架构与事件驱动设计(EDA),通过 SMTP 接收邮件、异步解析、实时推送的流水线处理模式,支撑千万级每日邮件吞吐量。
系统由三个独立可部署的无状态服务组成:
- API 服务:RESTful API,提供账号管理、邮件查询、实时推送(SSE),支持 API Key(默认
sk_,兼容dk_)+ JWT 统一 Bearer 鉴权 - SMTP 服务:接收外部发来的邮件,支持多 IP 监听和按域名过滤,做快速校验后入队
- Worker 服务:从队列消费原始邮件,解析 MIME、存储附件、写入数据库
核心增强特性:
- DuckMail 兼容 API Key:默认使用
sk_前缀(兼容dk_),通过统一Authorization: Bearer头传递 - 三层速率限制:全局 IP 限制 + 每 API Key 限制 + 每 API Key 每域名限制
- 人类化邮箱前缀生成:智能生成仿真人类商用/昵称邮箱前缀,单域名超 50 亿种独立组合
- 配置化域名管理:直接在 config.yaml 中定义多个域名,启动时自动同步到 MongoDB
- 多 IP 绑定:不同域名可绑定到不同 IP 的 SMTP 监听器
- 多服务器分布式:各节点自动检测本机 IP,仅启动自己负责的监听器
- 一键管理脚本:安装、配置、启停、监控一站式管理
English:
MailAPI is a high-concurrency, distributed temporary email backend service benchmarked against mail.tm, compatible with the DuckMail API standard. It adopts a microservices architecture with Event-Driven Architecture (EDA), processing emails through a pipeline of SMTP reception, asynchronous parsing, and real-time push — capable of handling tens of millions of daily email throughput.
The system comprises three independently deployable, stateless services:
- API Service: RESTful API providing account management, message queries, and real-time push (SSE), with API keys (default
sk_,dk_also accepted) + JWT unified Bearer authentication - SMTP Service: Receives inbound emails with multi-IP listener support and per-domain filtering, performs fast validation and enqueues
- Worker Service: Consumes raw emails from the queue, parses MIME, stores attachments, and writes to the database
Core enhanced features:
- DuckMail-Compatible API Keys: Uses
sk_prefix by default (dk_also accepted) via unifiedAuthorization: Bearerheader - Three-Layer Rate Limiting: Global per-IP + per-API-key + per-API-key-per-domain limits
- Human-Like Email Prefix Generation: Intelligent realistic human-like email prefix generation, 5+ billion unique combinations per domain
- Config-based Domain Management: Define multiple domains directly in config.yaml, auto-synced to MongoDB on startup
- Multi-IP Binding: Different domains can bind to different IP-based SMTP listeners
- Multi-Server Distributed: Each node auto-detects its local IPs and only starts matching listeners
- One-click Management Script: Install, configure, start/stop, and monitor in one place
| 组件 / Component | 技术选型 / Technology | 版本 / Version | 用途说明 / Purpose |
|---|---|---|---|
| 编程语言 / Language | Go | 1.26.1 | 高并发网络 I/O,Goroutine 处理海量长连接 / High-concurrency network I/O with goroutines |
| Web 框架 / Web Framework | Gin | v1.12.0 | REST API 服务器 / REST API server |
| SMTP 库 / SMTP Library | emersion/go-smtp | v0.24.0 | SMTP 协议实现,多监听器 / SMTP protocol, multi-listener |
| MIME 解析 / MIME Parser | emersion/go-message | v0.18.2 | RFC 822 邮件流式解析 / RFC 822 email stream parsing |
| 认证 / Authentication | golang-jwt/jwt | v5.3.1 | JWT 令牌签发与验证 / JWT token generation & validation |
| 数据库 / Database | MongoDB | v2.5.0 (driver) | 文档存储,TTL 索引自动过期 / Document storage with TTL auto-expiry |
| 缓存 / Cache | Redis | v9.18.0 (driver) | 地址缓存、Pub/Sub、三层速率限制 / Address cache, Pub/Sub, three-layer rate limiting |
| 消息队列 / Message Queue | NATS JetStream | v1.50.0 | 异步邮件处理解耦 / Async email processing decoupling |
| 对象存储 / Object Storage | MinIO (S3 兼容) | v7.0.99 | 附件分布式存储 / Distributed attachment storage |
| 密码加密 / Password Crypto | golang.org/x/crypto | v0.49.0 | Bcrypt 密码哈希 / Bcrypt password hashing |
| 配置解析 / Config Parser | gopkg.in/yaml.v3 | v3.0.1 | YAML 配置文件解析 / YAML config file parsing |
mailapi/
├── cmd/ # 入口文件 / Entry points
│ ├── api/main.go # REST API 服务入口(含域名同步)/ API entry (with domain sync)
│ ├── smtp/main.go # SMTP 服务入口(多监听器)/ SMTP entry (multi-listener)
│ └── worker/main.go # Worker 服务入口 / Worker entry
│
├── internal/ # 内部包 / Internal packages
│ ├── model/model.go # 数据模型定义 / Data model definitions
│ │
│ ├── config/ # 配置管理 / Configuration management
│ │ ├── config.go # 域名·sk_/dk_ API Key·多IP·速率限制 / Domains, sk_/dk_ keys, IPs, rate limits
│ │ └── config_test.go # 配置测试 / Config tests
│ │
│ ├── auth/ # JWT 认证 / JWT authentication
│ │ ├── auth.go # 令牌签发与验证 / Token generation & validation
│ │ └── auth_test.go # 认证测试 / Auth tests
│ │
│ ├── cache/ # Redis 缓存层 / Redis cache layer
│ │ ├── cache.go # 缓存实现(含三层限流)/ Cache (with 3-layer rate limiting)
│ │ └── interface.go # 接口定义 / Interface definition
│ │
│ ├── queue/ # NATS 消息队列 / NATS message queue
│ │ ├── queue.go # 队列实现 / Queue implementation
│ │ └── interface.go # 接口定义 / Interface definition
│ │
│ ├── storage/ # MinIO 对象存储 / MinIO object storage
│ │ ├── storage.go # 存储实现 / Storage implementation
│ │ └── interface.go # 接口定义 / Interface definition
│ │
│ ├── store/ # MongoDB 数据访问层 / MongoDB data access layer
│ │ ├── store.go # 数据操作(含 SyncDomains)/ CRUD (with SyncDomains)
│ │ └── interface.go # 接口定义 / Interface definition
│ │
│ ├── handler/ # HTTP 请求处理器 / HTTP request handlers
│ │ ├── handler.go # 路由注册(统一 Bearer 鉴权)/ Route setup (unified Bearer auth)
│ │ ├── account.go # 账号增删查(域名隔离·自动前缀)/ Account CRUD (domain-scoped, auto-prefix)
│ │ ├── token.go # 登录认证 / Login auth
│ │ ├── domain.go # 域名查询(按 Key 过滤)/ Domains (filtered by key)
│ │ ├── message.go # 邮件增删改查 / Message CRUD & download
│ │ ├── sse.go # SSE 实时推送 / SSE real-time push
│ │ └── *_test.go # 各处理器测试 / Handler tests
│ │
│ ├── smtp/ # SMTP 多监听器实现 / Multi-listener SMTP
│ │ ├── server.go # 多IP绑定·域名过滤·本机IP检测 / Multi-IP, domain filter, local IP detection
│ │ └── server_test.go # SMTP 测试 / SMTP tests
│ │
│ ├── worker/ # 邮件解析工作者 / Email parsing worker
│ │ ├── worker.go # MIME 解析与持久化 / MIME parsing & persistence
│ │ └── worker_test.go # Worker 测试 / Worker tests
│ │
│ ├── prefix/ # 人类化邮箱前缀生成器 / Human-like email prefix generator
│ │ ├── generator.go # 生成器核心逻辑(三大类别·15种模式)/ Generator core (3 categories, 15 patterns)
│ │ ├── data.go # 国际化姓名数据库 / International name database
│ │ └── generator_test.go # 生成器测试 / Generator tests
│ │
│ └── middleware/ # HTTP 中间件 / HTTP middleware
│ ├── middleware.go # 统一 Bearer 鉴权 + 域名隔离 + 三层限流
│ └── middleware_test.go # 中间件测试 / Middleware tests
│
├── config.yaml # 配置文件(域名·sk_/dk_ Key·多IP·限流)/ Config
├── mailapi.sh # 一键管理脚本 / One-click management script
├── go.mod # Go 模块定义 / Go module definition
└── go.sum # 依赖校验和 / Dependency checksums
[外部发件方 / External Senders] [前端/API 调用方 / Frontend/API Clients]
(Gmail, QQ Mail, etc.) (Web, Mobile)
│ │
TCP Port 25 HTTPS Port 8080
(多 IP 监听 / Multi-IP) │
│ ┌──────┴──────┐
▼ ▼ ▼
+--------------+ +-------------------+
| SMTP 多监听器 | | API Service |
| Multi-listener| | Bearer 统一鉴权 |
| 域名过滤 | | sk_/dk_ Key + JWT |
+--------------+ +-------------------+
│ │ ▲
│ 原始 EML / Raw EML │ 读写 │ Pub/Sub
▼ ▼ │
+------------------+ +-------------------+
| NATS JetStream | | Redis |
| 消息队列 | | 缓存 / 事件 / 限流 |
+------------------+ +-------------------+
│ ▲
│ 消费 / Consume │
▼ │
+------------------+ +-------------------+
| Worker Cluster | ───────────────▶ | MongoDB |
| MIME 解析 | 写入 / Write | 账号/邮件/域名数据 |
+------------------+ +-------------------+
│
│ 上传 / Upload
▼
+------------------+
| MinIO (S3) |
| 附件/原始 .eml 存储 |
+------------------+
中文:
- 邮件接收:外部邮件服务器通过 SMTP 协议将邮件发送到 SMTP 服务。多监听器模式下,不同 IP 接收不同域名的邮件
- 域名过滤:每个 SMTP 监听器知道自己负责哪些域名,在
RCPT TO阶段拒绝不属于本监听器的域名 - 地址校验:通过 Redis 微秒级验证收件地址是否存在,拒绝无效地址
- 异步入队:校验通过后,原始邮件数据入 NATS JetStream 队列,SMTP 连接立即释放
- MIME 解析:Worker 从队列消费消息,解析 MIME 结构,提取正文和附件
- 对象存储:原始
.eml(RFC 822)与附件上传到 MinIO(raw 上传失败则回退写入 MongorawMessage) - 数据写入:邮件元数据与正文(text/html)写入 MongoDB
- 实时通知:Worker 通过 Redis Pub/Sub 发布新邮件事件
- SSE 推送:API 服务监听 Redis 频道,通过 SSE 长连接推送到客户端
English:
- Email Reception: External mail servers send emails to the SMTP service. In multi-listener mode, different IPs receive emails for different domains
- Domain Filtering: Each SMTP listener knows which domains it serves, rejecting domains not assigned to it during the
RCPT TOphase - Address Validation: Microsecond Redis validation checks if the recipient address exists, rejecting invalid addresses
- Async Enqueue: After validation, raw email data is pushed to the NATS JetStream queue, SMTP connection released immediately
- MIME Parsing: Workers consume messages from the queue, parse MIME structures, extract body and attachments
- Object Storage: Raw
.eml(RFC 822) and attachments are uploaded to MinIO (fallback to MongorawMessageif raw upload fails) - Data Writing: Email metadata and body content (text/html) are written to MongoDB
- Real-time Notification: Workers publish new email events via Redis Pub/Sub
- SSE Push: The API service listens on Redis channels and pushes events to clients via SSE
源码位置 / Source:
internal/model/model.go
| 字段 / Field | 类型 / Type | 说明 / Description |
|---|---|---|
id |
ObjectID | MongoDB 文档 ID / MongoDB document ID |
domain |
string | 域名(如 example.com)/ Domain name |
isActive |
bool | 是否启用 / Whether active |
isPrivate |
bool | 是否私有 / Whether private |
createdAt |
time.Time | 创建时间 / Creation timestamp |
updatedAt |
time.Time | 更新时间 / Update timestamp |
配置化管理 / Config-based Management:
域名可直接在 config.yaml 的 domains 节定义,API 启动时自动同步到 MongoDB(upsert 模式,不会删除手动添加的域名)。
Domains can be defined in the domains section of config.yaml. On API startup, they are auto-synced to MongoDB (upsert mode — manually added domains are not removed).
| 字段 / Field | 类型 / Type | 说明 / Description |
|---|---|---|
id |
ObjectID | MongoDB 文档 ID |
address |
string | 唯一邮箱地址(可自动生成)/ Unique email address (can be auto-generated) |
password |
string | Bcrypt 哈希密码(JSON 不返回)/ Bcrypt hash (omitted from JSON) |
quota |
int64 | 存储配额(默认 40MB)/ Storage quota (default 40MB) |
used |
int64 | 已使用存储量 / Used storage |
isDeleted |
bool | 软删除标记 / Soft delete flag |
createdAt |
time.Time | 创建时间(TTL 基准)/ Creation time (TTL base) |
updatedAt |
time.Time | 更新时间 / Update timestamp |
| 字段 / Field | 类型 / Type | 说明 / Description |
|---|---|---|
id |
ObjectID | MongoDB 文档 ID |
accountId |
ObjectID | 所属账号 ID / Owning account ID |
msgid |
string | 邮件 Message-ID 头 / Email Message-ID header |
from |
Address | 发件人 / Sender |
to |
[]Address | 收件人列表 / Recipient list |
cc |
[]Address | 抄送列表 / CC list |
bcc |
[]Address | 密送列表 / BCC list |
subject |
string | 邮件主题 / Subject |
intro |
string | 正文前 100 字符摘要 / First 100 chars of body |
text |
string | 纯文本正文 / Plain text body |
html |
[]string | HTML 正文(可多段)/ HTML body (multiple parts) |
hasAttachments |
bool | 是否有附件 / Has attachments flag |
attachments |
[]Attachment | 附件元数据列表 / Attachment metadata list |
size |
int64 | 原始邮件大小 / Raw message size |
seen |
bool | 是否已读 / Read status |
isDeleted |
bool | 软删除标记 / Soft delete flag |
keep |
bool | 长期保留(不受 message TTL 自动过期影响;默认关闭,需 API 进程启用)/ Long-term retention (excluded from message TTL; gated by API env; disabled by default) |
rawMessage |
[]byte | 原始 RFC 822 邮件(JSON 不返回)/ Raw RFC 822 (omitted from JSON) |
createdAt |
time.Time | 创建时间 / Creation timestamp |
updatedAt |
time.Time | 更新时间 / Update timestamp |
| 字段 / Field | 类型 / Type | 说明 / Description |
|---|---|---|
id |
string | 附件唯一标识 / Unique attachment ID |
filename |
string | 文件名 / Filename |
contentType |
string | MIME 类型 / MIME content type |
disposition |
string | 内容处置方式 / Content disposition |
transferEncoding |
string | 传输编码 / Transfer encoding |
size |
int64 | 文件大小(字节)/ File size in bytes |
DomainConfig — 域名配置 / Domain configuration:
| 字段 / Field | 类型 / Type | 说明 / Description |
|---|---|---|
domain |
string | 域名 / Domain name |
isActive |
bool | 是否启用 / Whether active |
isPrivate |
bool | 是否私有 / Whether private |
ips |
[]string | SMTP 监听 IP 列表(空=所有接口)/ SMTP listener IPs (empty = all interfaces) |
APIKeyConfig — API Key 配置 / API key configuration:
| 字段 / Field | 类型 / Type | 说明 / Description |
|---|---|---|
key |
string | API 密钥(默认 sk_ 前缀,兼容 dk_)/ API key string (default sk_, dk_ also accepted) |
name |
string | 名称 / Descriptive name |
domains |
[]string | 可访问域名(["*"] 为全部)/ Allowed domains (["*"] for all) |
rpmLimit |
int64 | 每分钟请求数限制(0=不限)/ Requests per minute limit (0=unlimited) |
domainLimits |
map[string]int64 | 每域名每分钟请求数限制 / Per-domain RPM overrides |
RateLimitConfig — 全局速率限制 / Global rate limiting:
| 字段 / Field | 类型 / Type | 说明 / Description |
|---|---|---|
global |
int64 | 全局每 IP 每分钟请求数(0=默认100)/ Global RPM per IP (0=default 100) |
CreateAccountRequest — 创建账号请求 / Create account request:
| 字段 / Field | 类型 / Type | 说明 / Description |
|---|---|---|
address |
string | 邮箱地址(可选,为空时需提供 domain)/ Email address (optional if domain provided) |
domain |
string | 目标域名(提供时自动生成前缀)/ Target domain (auto-generates prefix when provided) |
password |
string | 密码(最少 6 位)/ Password (min 6 chars, required) |
源码位置 / Source:
internal/config/config.go,config.yaml
- 硬编码默认值 / Hardcoded Defaults:
config.go中定义的安全默认值 - YAML 配置文件 / YAML Config File:
config.yaml覆盖默认值 - 容错机制 / Fail-Safe: 配置文件不存在时使用默认值,系统仍可启动
# =============================================================================
# MailAPI Configuration
# =============================================================================
# -----------------------------------------------------------------------------
# Domains — define email domains directly in config (synced to MongoDB on start)
# Each domain can optionally bind to specific IPs for multi-IP / distributed
# deployment. If 'ips' is empty, SMTP listens on 0.0.0.0 (all interfaces).
# -----------------------------------------------------------------------------
domains:
- domain: "example.com"
isActive: true
isPrivate: false
ips: [] # listen on all interfaces
# - domain: "example.org"
# isActive: true
# isPrivate: false
# ips: ["192.168.1.100"] # bind to specific IP
# - domain: "example.net"
# isActive: true
# isPrivate: true
# ips: ["192.168.1.101"] # different IP for this domain
# -----------------------------------------------------------------------------
# API Keys — default "sk_" prefix (legacy "dk_" also accepted), domain-level auth and isolation
#
# Each key grants access to the listed domains. Use ["*"] for all domains.
# If no API keys are defined, the API runs without key authentication.
# Clients pass the key via: Authorization: Bearer sk_xxx (or dk_xxx)
#
# Rate limiting:
# rpmLimit — total requests per minute for this key (0 = unlimited)
# domainLimits — per-domain RPM overrides for this key
# -----------------------------------------------------------------------------
apiKeys:
- key: "sk_change_me_to_a_random_key"
name: "Admin"
domains: ["*"] # access to all domains
rpmLimit: 0 # unlimited (uses global limit only)
# - key: "sk_partner_key_here"
# name: "Partner"
# domains: ["example.com"] # access restricted to one domain
# rpmLimit: 200 # 200 requests per minute for this key
# domainLimits:
# "example.com": 100 # further limit: 100 RPM on example.com
# -----------------------------------------------------------------------------
# Rate Limiting — global limits applied per client IP
# -----------------------------------------------------------------------------
rateLimit:
global: 100 # requests per minute per IP (0 = default 100, -1 = disable)
# -----------------------------------------------------------------------------
# Server
# -----------------------------------------------------------------------------
server:
api:
host: "0.0.0.0"
port: 8080
# 可信代理(影响 ClientIP 解析与 IP 限流)。生产环境强烈建议显式配置,避免被伪造 X-Forwarded-For 绕过。
# 为空表示不覆盖 Gin 默认行为(保持向后兼容)。示例:
#trustedProxies: ["127.0.0.1", "10.0.0.0/8"]
# 多 API 风格(dialect)路由:通过 `<dialect>.<baseHost>` 的子域前缀选择不同 API 兼容层。
# 例如:`cfworker.api.mailapi.com` 使用 cfworker 风格;`duck.api.mailapi.com` 使用 duck 风格;
# `yyds.api.mailapi.com` 使用 YYDS Mail 公共临时邮箱风格。
# baseHost 为空表示禁用(保持向后兼容:所有请求使用单一默认风格)。
#baseHost: "api.mailapi.com"
#defaultDialect: "duck" # api.mailapi.com 与 localhost/IP 访问时采用的默认风格
#enabledDialects: ["duck", "cfworker", "yyds"] # 为空表示不限制(允许所有已注册 dialect)
#unknownDialect: "reject" # reject(推荐) 或 fallback(未知 dialect 回退到默认)
# bcrypt 并发闸门:创建账号/登录会触发 bcrypt(CPU 密集),高并发时建议限制并发避免 CPU 打满导致整体雪崩
maxConcurrentBcrypt: 0 # 0=自动(按 GOMAXPROCS);建议 4/8/16 视机器与压测结果调整
# 访问日志(高并发强烈建议开启采样/仅错误/仅慢请求,否则日志格式化与 IO 会成为 CPU 热点)
accessLog:
enabled: true
sampleEvery: 100 # 1=全量;100=百分之一;建议在压测/生产场景保持 >= 10
slowThreshold: 500ms # 慢请求阈值(>= 阈值将强制记录);0s 表示禁用
errorsOnly: false # true=仅记录 status>=400 的请求
# Debug HTTP server(健康检查/指标/pprof)。默认关闭;建议仅绑定 127.0.0.1 并置于防火墙/LB 后。
# 注意:debug server 不做鉴权,若暴露到公网可能泄露运行时信息。
debug:
enabled: false
host: "127.0.0.1"
port: 6060
metrics: true # GET /metrics(Prometheus)
pprof: false # GET /debug/pprof/(强烈建议不要公网暴露)
smtp:
port: 25 # default port for all SMTP listeners
host: "0.0.0.0" # fallback host (used only when no domains configured)
domain: "mail.example.com" # EHLO banner domain
maxMessageBytes: 20971520 # 20MB
maxRecipients: 50
readTimeout: 60s
writeTimeout: 60s
# Debug HTTP server(健康检查/指标/pprof)。默认关闭;建议仅绑定 127.0.0.1。
debug:
enabled: false
host: "127.0.0.1"
port: 6061
metrics: true
pprof: false
# Worker 没有业务监听端口,但可以开启 debug HTTP server 用于 ready/metrics/pprof。
worker:
debug:
enabled: false
host: "127.0.0.1"
port: 6062
metrics: true
pprof: false
# -----------------------------------------------------------------------------
# Dialects(可选)— 各 API 风格的专属配置
#
# 注意:这里不是“如何选择 dialect”(那在 server.api.baseHost 下配置),而是每个 dialect 自己的参数。
# 未配置 cfworker.upstream 时,访问 `cfworker.<baseHost>` 将返回 503(表示上游未就绪)。
# yyds 的临时邮箱/消息接口不依赖额外上游;其配置只影响公开元数据端点与 llms 文本。
# -----------------------------------------------------------------------------
dialects:
cfworker:
# 上游 Worker 的 Base URL(示例: https://temp-mail.example.workers.dev 或 http://127.0.0.1:8787 )
upstream: ""
# 代理超时(包含请求与响应体传输);0 表示不额外设置(仅受请求 ctx 影响)
timeout: 15s
yyds:
publicBaseURL: ""
plans: []
pricing:
currency:
code: "CNY"
suffix: ""
symbol: "¥"
packages: []
rateLimits: []
domainReward:
creditExpireDays: 0
creditsPerCycle: 0
runHour: 0
usagePerCredit: 0
stats:
totalUsers: 0
totalDomains: 0
verifiedDomains: 0
publicDomains: 0
totalInboxes: 0
anonInboxes: 0
totalStoredMessages: 0
totalHistoricalMessages: 0
totalCreatedInboxes: 0
totalMessages: 0
todayApiCalls: 0
topDomains: []
hourlyActivity: []
dailyTrend: []
# -----------------------------------------------------------------------------
# Infrastructure
# -----------------------------------------------------------------------------
mongodb:
uri: "mongodb://localhost:27017"
database: "mailapi"
# MongoDB 驱动调参(建议按压测逐步调整)
# maxPoolSize:
# -1: 不覆盖驱动默认(通常为 100)
# 0: 自动(按 CPU 规模估算,上限保护,适合高并发)
# >0: 固定值
maxPoolSize: 0
# minPoolSize:
# -1: 不覆盖驱动默认
# >=0: 固定值(通常 0 即可;需要预热连接可提高)
minPoolSize: -1
# maxConnecting:
# -1: 不覆盖驱动默认
# 0: 自动(适当提高“建连并发”以降低突发流量抖动)
# >0: 固定值
maxConnecting: 0
# 连接/选主超时(0 表示使用驱动默认)
connectTimeout: 10s
serverSelectionTimeout: 10s
# 空闲连接回收(<=0 表示不设置,使用驱动默认)
maxConnIdleTime: 5m
# 便于在 Mongo 端定位来源连接(可选)
appName: "mailapi"
redis:
addr: "localhost:6379"
password: ""
db: 0
nats:
url: "nats://localhost:4222"
stream: "EMAILS"
subject: "emails.incoming"
consumerAckWait: 30s
consumerMaxDeliver: 3
minio:
endpoint: "localhost:9000"
accessKey: "minioadmin"
secretKey: "minioadmin"
bucket: "attachments"
useSSL: false
# -----------------------------------------------------------------------------
# Auth & TTL
# -----------------------------------------------------------------------------
jwt:
secret: "change-me-to-a-random-secret"
expiry: 1h
account:
ttl: 168h # 7 days
message:
ttl: 168h # 7 days补充 / Note:
message.ttl控制默认的自动过期删除(TTL)。若需要对单条邮件长期保留,可通过PATCH /messages/:id设置keep=true(该字段会让 TTL 索引跳过该文档)。keep参数默认禁用,需要在 API 服务进程 显式开启环境变量MAILAPI_API_ALLOW_MESSAGE_KEEP=1(或MAILAPI_ALLOW_MESSAGE_KEEP=1)。
中文:
API 服务启动时,会将 config.yaml 中定义的域名 upsert 到 MongoDB 的 domains 集合。已存在的域名会更新 isActive 和 isPrivate 字段;不存在的域名会新建。配置文件中未列出的域名(如手动通过 MongoDB 添加的)不会被删除。
English:
On API service startup, domains defined in config.yaml are upserted to MongoDB's domains collection. Existing domains have their isActive and isPrivate fields updated; missing domains are created. Domains not listed in the config (e.g., manually added via MongoDB) are not removed.
源码位置 / Source:
internal/middleware/middleware.go
中文:
系统默认使用 sk_ 前缀 API Key(兼容 DuckMail 标准 dk_),通过统一的 Authorization: Bearer 头传递。API Key 和 JWT 令牌共享同一个 Authorization 头,系统通过 sk_/dk_ 前缀自动区分二者。API Key 提供域名级别的访问控制和可配置的速率限制,实现完整的多租户隔离。
English:
API keys use sk_ prefix by default (dk_ also accepted), passed through a unified Authorization: Bearer header. API keys and JWT tokens share the same Authorization header; the system auto-detects them via the sk_/dk_ prefix. API keys provide domain-level access control and configurable rate limiting, achieving full multi-tenant isolation.
Authorization: Bearer <token>
│
├─▶ sk_/dk_ 前缀? / sk_/dk_ prefix?
│ ├─ 是 / Yes → API Key 认证 / API Key auth
│ │ └─ 设置域名范围 + Key 信息 + 速率限制配置
│ │ Sets domain scope + key info + rate limit config
│ └─ 否 / No → JWT 认证 / JWT auth
│ └─ 设置用户身份 + 自动限定邮箱域名
│ Sets user identity + auto-scope to email domain
│
▼
┌─────────────────────────────┐
│ Layer 1: 全局 IP 速率限制 │ Redis INCR + EXPIRE
│ Global per-IP rate limit │ Key: rl:<ip>
└─────────────────────────────┘
│
▼
┌─────────────────────────────┐
│ Layer 2: API Key 速率限制 │ Redis INCR + EXPIRE
│ Per-key rate limit │ Key: rl:key:<apiKey>
│ (仅当 rpmLimit > 0) │
└─────────────────────────────┘
│
▼
┌─────────────────────────────┐
│ Layer 3: Key+域名 速率限制 │ 由 handler 在操作域名时检查
│ Per-key-per-domain limit │ Key: rl:key:<apiKey>:d:<domain>
│ (仅当 domainLimits 已配置) │ Checked by handler at domain operations
└─────────────────────────────┘
│
▼
处理器 / Handler
| 中间件 / Middleware | 作用范围 / Scope | 说明 / Description |
|---|---|---|
BearerAuth |
全部路由 / All routes | 解析 Authorization: Bearer 头,区分 sk_/dk_ API Key 和 JWT。设置域名范围和用户身份 / Parses Bearer header, distinguishes sk_/dk_ keys from JWT. Sets domain scope and identity |
APIKeyRateLimit |
全部路由 / All routes | 检查每 API Key 的 RPM 限制(当 rpmLimit > 0 时生效)/ Checks per-key RPM (active when rpmLimit > 0) |
RequireAPIKey |
公开路由 / Public routes | 确保存在 API Key 或 JWT(当系统配置了 API Key 时)/ Ensures key or JWT present (when keys configured) |
AuthRequired |
认证路由 / Auth routes | 验证请求包含有效 JWT 身份 / Validates JWT identity is present |
DomainScopeCheck |
认证路由 / Auth routes | 验证 JWT 用户的域名在允许范围内 / Verifies JWT user's domain is permitted |
RateLimit |
全部路由 / All routes | 全局 IP 速率限制 / Global per-IP rate limiting |
| 键 / Key | 类型 / Type | 说明 / Description |
|---|---|---|
apiKeyDomains |
[]string |
当前 Key/JWT 的允许域名列表 / Allowed domains for current key/JWT |
apiKeyName |
string |
API Key 名称 / API key name |
apiKey |
string |
API Key 字符串 / API key string |
apiKeyInfo |
*APIKeyInfo |
API Key 完整信息(含速率限制配置)/ Full key info (with rate limit config) |
accountId |
string |
JWT 用户账号 ID / JWT user account ID |
address |
string |
JWT 用户邮箱地址 / JWT user email address |
如果 config.yaml 中未配置任何 apiKeys,系统会以通配符 ["*"] 的方式运行,所有公开域名对所有请求开放,与引入 API Key 之前的行为基本一致。
但若你启用了私有域名(isPrivate=true),则该私有域名仍需要“显式授权”(建议启用 API Key 并在 apiKeys[].domains 中列出该私有域名),否则私有域名对匿名/通配符请求不可见也不可用。
If no apiKeys are configured in config.yaml, the system runs with wildcard ["*"] and opens all public domains to all requests, largely identical to the behavior before API keys were introduced.
However, if you enable private domains (isPrivate=true), the private domain still requires explicit authorization (recommended: enable API keys and list the private domain in apiKeys[].domains), otherwise it will not be visible/usable for anonymous/wildcard requests.
| 函数 / Function | 说明 / Description |
|---|---|
DomainFromAddress(address) |
从邮箱地址提取域名部分 / Extracts domain from email address |
IsDomainAllowed(ctx, domain) |
检查域名是否在当前 Key 允许范围内 / Checks if domain is allowed by current key |
GetAllowedDomains(ctx) |
获取当前 Key/JWT 的允许域名列表 / Gets allowed domain list for current key/JWT |
HasWildcardAccess(ctx) |
当前 Key 是否有通配符权限 / Whether current key has wildcard access |
GetAPIKeyInfo(ctx) |
获取完整 API Key 信息(含限流配置)/ Gets full API key info (with rate limit config) |
CheckDomainRateLimit(ctx, ca, domain) |
检查 Key+域名维度的速率限制 / Checks per-key-per-domain rate limit |
源码位置 / Source:
internal/handler/OpenAPI(duck 主风格)/ OpenAPI (duck dialect):
openapi.yaml
所有认证统一通过 Authorization: Bearer 头传递。当配置了 API Key 时,公开接口需要 sk_/dk_ API Key 或 JWT;认证接口需要 JWT。
All authentication goes through the Authorization: Bearer header. When API keys are configured, public routes require a sk_/dk_ API key or JWT; authenticated routes require JWT.
MailAPI 支持通过 API 域名前缀(子域)选择不同的 API 风格(兼容层),例如:
cfworker.api.mailapi.com:cfworker 风格(Cloudflare Worker / cloudflare_temp_email 兼容)duck.api.mailapi.com:DuckMail 风格api.mailapi.com:默认风格(可配置,建议默认duck以保持向后兼容)
注意:上面的 “统一 Authorization: Bearer” 约定针对 duck 风格成立;其他风格可能有不同鉴权头(如 x-admin-auth 等),请以对应 dialect 文档为准。
具体规则、DNS/TLS 与反向代理部署要点参见:API_DIALECTS.md。
返回当前 API Key/JWT 有权访问的所有已启用域名。
Returns all active domains the current API key/JWT has access to.
创建临时邮箱。支持两种模式:
- 指定地址:
{"address": "user@example.com", "password": "..."}— 直接指定完整邮箱地址 - 自动生成:
{"domain": "example.com", "password": "..."}— 系统自动生成人类化前缀
校验域名在 API Key/JWT 允许范围内,且域名在 MongoDB 中处于 active 状态。自动生成模式下如果发生地址冲突,会自动重试最多 5 次。
Creates a temporary email account. Two modes:
- Specified address:
{"address": "user@example.com", "password": "..."}— specify full address - Auto-generated:
{"domain": "example.com", "password": "..."}— system auto-generates human-like prefix
Validates domain is within API key/JWT scope and active in MongoDB. In auto-generate mode, retries up to 5 times on address collision.
使用邮箱地址和密码登录,返回 JWT 令牌。
Login with email and password, returns JWT token.
生成随机人类化邮箱地址(不创建账号)。查询参数:domain(必填)、count(可选,1-50,默认 5)。
Generate random human-like email addresses (without creating accounts). Query params: domain (required), count (optional, 1-50, default 5).
| 方法 / Method | 路径 / Path | 说明 / Description |
|---|---|---|
GET |
/me |
获取当前用户 / Get current user |
GET |
/accounts/:id |
获取账号信息 / Get account |
DELETE |
/accounts/:id |
删除账号(级联删除邮件和附件)/ Delete account (cascades) |
GET |
/messages |
分页邮件列表(支持 cursor/after + nextCursor,可选 seen=true/false 过滤)/ List messages (cursor pagination + optional seen filter) |
PATCH |
/messages |
批量更新 flags(seen/keep)/ Bulk update flags (seen/keep) |
DELETE |
/messages |
按账号批量软删(可选 seen=true/false)/ Bulk soft delete by account (optional seen filter) |
POST |
/messages/bulk-delete |
按 ids 批量软删 / Bulk soft delete by IDs |
GET |
/messages/:id |
邮件详情(含完整正文)/ Message detail (with full body) |
PATCH |
/messages/:id |
更新邮件状态 / Update message status |
DELETE |
/messages/:id |
删除邮件 / Delete message |
GET |
/messages/:id/download |
下载原始 .eml / Download raw .eml |
GET |
/messages/:id/attachments/:attachmentId |
下载附件 / Download attachment |
GET |
/sse |
实时推送 / Real-time SSE |
域名隔离说明 / Domain Isolation:
所有认证接口都经过 DomainScopeCheck 中间件,确保 JWT 中用户的邮箱域名在允许范围内。JWT 用户被自动限定在其邮箱地址的域名范围内。
All authenticated routes pass through the DomainScopeCheck middleware, ensuring the JWT user's email domain is permitted. JWT users are automatically scoped to their email address's domain.
源码位置 / Source:
internal/smtp/server.go,cmd/smtp/main.go
中文:
SMTP 服务支持同时在多个 IP 地址上监听。每个监听器知道自己负责哪些域名,在 RCPT TO 阶段进行域名过滤——不属于该监听器的域名直接返回 550 拒绝。
English:
The SMTP service supports listening on multiple IP addresses simultaneously. Each listener knows which domains it serves, performing domain filtering at the RCPT TO phase — domains not assigned to that listener are rejected with 550.
MailAPI 基于 go-smtp 支持 STARTTLS(RFC 3207):在 server.smtp.tls.enabled=true 时会在 EHLO capability 中广播 STARTTLS,并接受客户端升级到 TLS。
配置项(config.yaml):
server:
smtp:
tls:
enabled: true
certFile: "certs/smtp.crt" # 允许相对路径(相对 config.yaml 目录)
keyFile: "certs/smtp.key"
requireTLS: false # true 时强制 MAIL/RCPT/DATA 前先 STARTTLS(否则 530)
minVersion: "1.2" # 可选:1.2/1.3注意事项:
requireTLS=true会拒绝不支持 STARTTLS 的客户端(返回530 Must issue STARTTLS first),可能影响可达性;默认建议保持false。- 证书与私钥路径会在配置校验时做可读性检查;并支持按 config 文件目录解析相对路径,便于容器/打包部署。
config.yaml 中的域名配置 / Domain config in config.yaml
│
│ 提取每个域名的 IP 列表
│ Extract IP list per domain
▼
┌─────────────────────────────────┐
│ BuildListeners() │
│ 按 IP 分组域名 │ example.com → 192.168.1.100
│ Group domains by IP │ example.org → 192.168.1.100
│ │ example.net → 192.168.1.101
└─────────────────────────────────┘
│
│ 检测本机 IP / Detect local IPs
│ IsLocalIP() → net.InterfaceAddrs()
▼
┌─────────────────────────────────┐
│ 过滤非本机 IP │ 10.0.0.2 不是本机 → 跳过
│ Filter non-local IPs │ 10.0.0.2 not local → skip
└─────────────────────────────────┘
│
▼
监听器 1: 192.168.1.100:25 → [example.com, example.org]
监听器 2: 192.168.1.101:25 → [example.net]
RCPT TO: <user@example.com>
│
├──▶ 域名在监听器允许列表中?/ Domain in listener's allowed list?
│ ├─ 否 → 550 "Domain not accepted on this server"
│ └─ 是 → 继续 / Yes → continue
│
├──▶ Redis 地址校验 / Redis address check
│ ├─ Redis 错误 → 451 临时错误(安全拒绝)/ Redis error → 451 (safe reject)
│ ├─ 不存在 → 550 "User does not exist"
│ └─ 存在 → 250 OK
│
▼
接收邮件数据 → 入队 NATS / Receive DATA → enqueue to NATS
如果 config.yaml 中未定义 domains,SMTP 服务退回到单监听器模式,使用 server.smtp.host:port 配置,接受所有域名——与引入多监听器之前的行为一致。
If no domains are defined in config.yaml, the SMTP service falls back to single-listener mode using server.smtp.host:port, accepting all domains — identical to the behavior before multi-listener was introduced.
源码位置 / Source:
internal/worker/worker.go,cmd/worker/main.go
Worker 是邮件处理的核心引擎。它从 NATS JetStream 队列中消费原始邮件,执行完整的 MIME 解析,将原始 .eml(RFC 822)与附件上传到 MinIO,MongoDB 保存邮件元数据与正文(text/html),并通过 Redis Pub/Sub 发布实时通知。如果对象存储不可用,rawMessage 会回退写入 Mongo 以保证正确性。Worker 不受域名配置或 API Key 的影响——如果邮件通过了 SMTP 的验证,Worker 就会处理它。
The Worker is the core email processing engine. It consumes raw emails from the NATS JetStream queue, performs full MIME parsing, uploads the raw .eml (RFC 822) and attachments to MinIO, stores metadata and body content (text/html) in MongoDB, and publishes real-time notifications via Redis Pub/Sub. If object storage is unavailable, rawMessage falls back to Mongo to preserve correctness. Workers are not affected by domain configuration or API keys — if an email passed SMTP validation, the Worker processes it.
| 防护措施 / Protection | 限制值 / Limit |
|---|---|
| MIME 嵌套深度 / MIME Nesting Depth | 50 层 / 50 levels |
| 内联部分大小 / Inline Part Size | 10MB |
| 附件大小 / Attachment Size | 20MB |
| 内存管理 / Memory | 流式 io.LimitReader / Stream-based |
源码位置 / Source:
internal/auth/auth.go,internal/middleware/middleware.go
| 属性 / Property | 值 / Value |
|---|---|
| 签名算法 / Algorithm | HMAC-SHA256 |
| 令牌载荷 / Claims | accountId, address, exp, iat, nbf |
| 默认有效期 / Default Expiry | 1 小时 / 1 hour |
| 层级 / Layer | 限制 / Limit | Redis Key | 实现 / Implementation |
|---|---|---|---|
| 全局 IP / Global per-IP | 可配置(默认 100/min)/ Configurable (default 100/min) | rl:<ip> |
Redis INCR + EXPIRE |
| API Key / Per-key | 每 Key 可配置 / Per-key configurable | rl:key:<apiKey> |
Redis INCR + EXPIRE |
| Key+域名 / Per-key-per-domain | 每 Key 每域名可配置 / Per-key-per-domain | rl:key:<apiKey>:d:<domain> |
Redis INCR + EXPIRE |
| SMTP / SMTP Layer | 100 连接/min/IP | smtp_rl:<ip> |
Redis INCR + EXPIRE |
| 措施 / Measure | 说明 / Description |
|---|---|
| 统一 Bearer 鉴权 / Unified Bearer Auth | sk_/dk_ API Key(域名级)+ JWT(用户级)统一通过 Bearer 头 / sk_/dk_ Key (domain) + JWT (user) via Bearer header |
| 密码哈希 / Password | Bcrypt(10 轮)/ Bcrypt (10 rounds) |
| 域名隔离 / Domain Isolation | API Key + JWT 域名自动限定 / API Key + JWT domain auto-scoping |
| 所有权校验 / Ownership | 每个接口校验资源归属 / Per-endpoint resource checks |
| 三层限流 / Three-Layer Rate Limit | 全局 IP + 单 Key + 单 Key 单域名 / Global IP + per-key + per-key-per-domain |
| MIME 防护 / MIME Protection | 深度限制 50 + LimitReader / Depth 50 + LimitReader |
| 邮件限制 / Size Limit | 20MB |
源码位置 / Source:
internal/cache/cache.go,internal/cache/interface.go
| 键模式 / Key Pattern | 用途 / Purpose | TTL |
|---|---|---|
addr:<email> |
邮件地址存在性缓存 / Address existence cache | 与账号 TTL 相同 / Same as account TTL |
rl:<ip> |
全局 IP 速率限制 / Global IP rate limit counter | 1 分钟 / 1 minute |
rl:key:<apiKey> |
API Key 速率限制 / Per-key rate limit counter | 1 分钟 / 1 minute |
rl:key:<apiKey>:d:<domain> |
Key+域名速率限制 / Per-key-per-domain rate limit counter | 1 分钟 / 1 minute |
smtp_rl:<ip> |
SMTP 速率限制计数器 / SMTP rate limit counter | 1 分钟 / 1 minute |
email:events:<accountId> |
SSE Pub/Sub 频道 / SSE Pub/Sub channel | N/A |
type Interface interface {
SetAddress(ctx, addr, ttl) // 设置地址缓存 / Set address cache
HasAddress(ctx, addr) // 检查地址是否存在 / Check if address exists
RemoveAddress(ctx, addr) // 移除地址缓存 / Remove address cache
Publish(ctx, accountID, payload) // 发布 SSE 事件 / Publish SSE event
Subscribe(ctx, accountID) // 订阅 SSE 频道 / Subscribe SSE channel
CheckRateLimit(ctx, ip, limit, window) // 全局 IP 限流 / Global IP rate limit
CheckSMTPRateLimit(ctx, ip, limit, window) // SMTP 限流 / SMTP rate limit
CheckKeyRateLimit(ctx, apiKey, limit, window) // Key 限流 / Per-key rate limit
CheckKeyDomainRateLimit(ctx, apiKey, domain, limit, window) // Key+域名限流 / Per-key-per-domain rate limit
Client() // 获取 Redis 客户端 / Get Redis client
Close() // 关闭连接 / Close connection
}源码位置 / Source:
internal/queue/queue.go
| 参数 / Parameter | 值 / Value |
|---|---|
| Stream 名称 | EMAILS |
| Subject | emails.incoming |
| 持久化 / Persistence | JetStream (at-least-once) |
| 最大存储 / Max Storage | 1GB |
| 消息保留 / Retention | 24 小时 / 24 hours |
| 消费者 / Consumer | email-worker (durable) |
| AckWait | nats.consumerAckWait(默认 30s,可配置)/ default 30s, configurable |
| MaxDeliver | nats.consumerMaxDeliver(默认 3,可配置)/ default 3, configurable |
源码位置 / Source:
internal/storage/storage.go
| 属性 / Property | 值 / Value |
|---|---|
| 存储桶 / Bucket | attachments(自动创建 / auto-created) |
| 对象键 / Object Key | messages/{messageID}/{attachmentID}/{filename}(附件 / attachments);原始 .eml 使用固定键 messages/{messageID}/_raw/message.eml |
| 删除策略 / Deletion | 按邮件 ID 前缀批量删除(附件 + 原始 .eml);Worker 还会后台扫描并清理“Mongo 文档已过期/删除但对象仍存在”的孤儿对象 / Batch delete by message ID prefix (attachments + raw .eml), plus background GC for orphaned objects |
源码位置 / Source:
internal/store/store.go
| 集合 / Collection | 索引 / Indexes | TTL |
|---|---|---|
domains |
domain (unique), isActive |
— |
accounts |
address (unique), createdAt (TTL) |
7 天 / 7 days |
messages |
{accountId, isDeleted, createdAt: -1, _id: -1} (compound), createdAt (TTL, partial keep!=true), {accountId, ingestStream, ingestSeq} (unique partial) |
7 天 / 7 days(keep=true 的文档不受 TTL 影响 / keep=true excluded) |
API 启动时调用 store.SyncDomains(),对 config 中每个域名执行 MongoDB UpdateOne with upsert: true:
$set: 更新domain,isActive,isPrivate,updatedAt$setOnInsert: 仅在新建时设置createdAt
On API startup, store.SyncDomains() executes MongoDB UpdateOne with upsert: true for each config domain:
$set: Updatesdomain,isActive,isPrivate,updatedAt$setOnInsert: SetscreatedAtonly on creation
源码位置 / Source:
internal/handler/sse.go,internal/handler/sse_hub.go
Worker 解析完邮件 / Worker finishes parsing
│
│ Redis PUBLISH "email:events:<accountId>" JSON
▼
Redis Pub/Sub ──▶ 所有 API 节点 / All API nodes
│
│ SSE: data: {"@type":"Message","id":"...","subject":"..."}
▼
客户端 / Client
SSE 连接同样受 JWT 认证保护,DomainScopeCheck 确保用户只能监听自己域名下的事件。
SSE connections are also protected by JWT authentication. DomainScopeCheck ensures users can only listen for events in their own domain.
高并发优化要点 / High-concurrency notes:
- Redis Pub/Sub 订阅连接做了进程内复用:从“每个 SSE 连接一个订阅”降为“每个 account 一个订阅 + 本地 fan-out”。这能显著降低 Redis 连接数与 goroutine 数量。
- SSE 会定期发送注释行心跳(
: ping),帮助代理/LB 保持连接并更快发现断链。
源码位置 / Source:
internal/prefix/
中文:
内置智能邮箱前缀生成器,生成高度仿真的人类商用或昵称邮箱前缀。所有前缀使用小写 ASCII 字符(a-z、0-9、.、_、-),最短 3 个字符,适用于标准邮件地址。
English:
Built-in intelligent email prefix generator producing highly realistic human-like commercial or nickname email prefixes. All prefixes use lowercase ASCII characters (a-z, 0-9, ., _, -), minimum 3 characters, suitable for standard email addresses.
| 类别 / Category | 权重 / Weight | 模式数 / Pattern Count | 说明 / Description |
|---|---|---|---|
| 个人姓名 / Personal | 55% | 15 种 | first.last, firstlast, first_last, flast, f.last, last.first 等 / etc. |
| 昵称 / Nickname | 30% | 6 种 | nick+num, adj+noun, adj_noun, adj.noun, pre+nick, nick.tag |
| 商务 / Business | 15% | 4 种 | biz, biz.dept, biz_dept, biz+num |
| 模式 / Pattern | 权重 / Weight | 示例 / Example |
|---|---|---|
first.last |
~27% | john.smith, maria.garcia |
firstlast |
~13% | johnsmith, zhangwei |
first_last |
~7% | john_smith |
first-last |
~7% | john-smith |
flast (initial+last) |
~13% | jsmith, zweig |
f.last |
~7% | j.smith |
first.l |
~7% | john.s |
last.first |
~7% | smith.john |
lastfirst |
~7% | smithjohn |
first+suffix |
~7% | john456 |
个人姓名模式支持 6 种后缀类型:
| 后缀 / Suffix | 概率 / Probability | 范围 / Range | 示例 / Example |
|---|---|---|---|
| 无 / None | 30% | — | john.smith |
| 1-2 位 / 1-2 digits | 20% | 0-99 | john.smith42 |
| 2 位年份 / 2-digit year | 15% | 50-09 | john.smith92 |
| 4 位年份 / 4-digit year | 10% | 1960-2026 | john.smith1992 |
| 3 位 / 3 digits | 13% | 100-999 | john.smith456 |
| 4 位 / 4 digits | 12% | 1000-9999 | john.smith6789 |
国际化多元姓名数据库(全部小写 ASCII):
Internationally diverse name database (all lowercase ASCII):
| 数据集 / Dataset | 数量 / Count | 语言 / Languages |
|---|---|---|
| 名字 / First Names | ~420 | 英、中拼音、西、法、德、意、日、韩、阿拉伯、印度等 / English, Chinese pinyin, Spanish, French, German, Italian, Japanese, Korean, Arabic, Indian, etc. |
| 姓氏 / Last Names | ~420 | 同上 / Same |
| 昵称 / Nicknames | ~170 | 英语网络昵称 / English internet nicknames |
| 形容词 / Adjectives | ~120 | 英语 / English |
| 名词 / Nouns | ~120 | 英语 / English |
| 商务前缀 / Business | ~50 | 英语 / English |
| 部门 / Departments | ~35 | 英语 / English |
- 个人姓名:~420 × ~420 × 15 模式 × ~1200 后缀 ≈ 32 亿 / ≈ 3.2 billion
- 昵称:~170 × 8 模式 × ~1200 后缀 + ~120 × ~120 × 3 模式 × ~1200 ≈ 18 亿 / ≈ 1.8 billion
- 商务:~50 × ~35 × 4 模式 × ~100 ≈ 70 万 / ≈ 700 thousand
- 总计 / Total: > 50 亿 / > 5 billion 种独立前缀每域名
| 接口 / Endpoint | 说明 / Description |
|---|---|
GET /addresses/random?domain=xxx&count=N |
生成 N 个随机地址(不创建账号,最多 50)/ Generate N random addresses (max 50) |
POST /accounts with {"domain": "xxx"} |
自动生成前缀并创建账号(碰撞重试 5 次)/ Auto-generate prefix and create account (5 retries on collision) |
中文:
一台服务器拥有多个 IP 地址(如 192.168.1.100 和 192.168.1.101),通过在域名配置中指定 ips 字段,可以让不同域名监听在不同 IP 上。MX 记录将各域名指向对应的 IP 地址。
English:
A single server with multiple IPs (e.g., 192.168.1.100 and 192.168.1.101) can bind different domains to different IPs via the ips field in domain config. MX records point each domain to its corresponding IP.
domains:
- domain: "a.com"
isActive: true
ips: ["192.168.1.100"]
- domain: "b.com"
isActive: true
ips: ["192.168.1.101"]中文:
多台服务器使用完全相同的配置文件。每台服务器启动 SMTP 服务时,BuildListeners() 会调用 IsLocalIP() 检测本机网卡上绑定的 IP 地址,只有匹配的监听器会被启动。所有服务器共享同一套 MongoDB / Redis / NATS / MinIO 后端。速率限制状态存储在共享的 Redis 中,因此限流在所有节点上一致。
English:
Multiple servers use the exact same config file. When each server starts the SMTP service, BuildListeners() calls IsLocalIP() to detect IPs bound to local network interfaces — only matching listeners are started. All servers share the same MongoDB / Redis / NATS / MinIO backends. Rate limit state is stored in shared Redis, so rate limiting is consistent across all nodes.
Server A (IP: 10.0.0.1) Server B (IP: 10.0.0.2)
┌──────────────────────┐ ┌──────────────────────┐
│ SMTP: 10.0.0.1:25 │ │ SMTP: 10.0.0.2:25 │
│ → a.com, shared.com │ │ → b.com, shared.com │
│ │ │ │
│ API: 0.0.0.0:8080 │ │ API: 0.0.0.0:8080 │
│ Worker │ │ Worker │
└──────────┬───────────┘ └──────────┬───────────┘
│ │
└──────────┬───────────────────────────────┘
▼
共享后端 / Shared Backends
MongoDB / Redis / NATS / MinIO
(速率限制状态在 Redis 中共享 / Rate limit state shared in Redis)
IsLocalIP() 通过 net.InterfaceAddrs() 枚举所有网络接口的 IP 地址,判断给定 IP 是否为本机地址。特殊地址 0.0.0.0 和 :: 总是被视为本地。
IsLocalIP() enumerates all network interface IPs via net.InterfaceAddrs() to determine if a given IP is local. Special addresses 0.0.0.0 and :: are always considered local.
文件位置 / File:
mailapi.sh
| 命令 / Command | 说明 / Description |
|---|---|
install |
完整安装:检查依赖、编译、Docker 基础设施、配置生成、systemd 服务 / Full install |
build |
编译三个 Go 二进制 / Build three Go binaries |
config |
配置生成(交互/环境变量,生成域名、API Key、JWT Secret)/ Config generation (interactive/env) |
infra-up |
启动 Docker Compose 基础设施 / Start Docker infra |
infra-down |
停止 Docker Compose 基础设施 / Stop Docker infra |
infra-clean |
停止基础设施并删除数据卷(危险)/ Stop infra and remove volumes (DANGEROUS) |
compose <args...> |
透传 docker compose 子命令(便于排查 infra)/ Pass-through docker compose (for infra troubleshooting) |
doctor |
环境/依赖/配置自检(也可用 check 作为别名)/ Doctor check (check alias) |
upgrade [pull] |
编译并重启服务(可选拉取 infra 镜像)/ Build and restart (optional pull images) |
uninstall [purge] |
卸载服务(可选清理数据/目录,危险)/ Uninstall (optional purge, dangerous) |
setcap-smtp |
为 SMTP 设置 setcap,允许非 root 绑定 25(Linux)/ setcap for SMTP (Linux) |
smoke |
冒烟测试:部署后快速验证 API/依赖可用性 / Smoke test |
start [service] |
启动服务(api/smtp/worker/all)/ Start services |
stop [service] |
停止服务 / Stop services |
restart [service] |
重启服务 / Restart services |
status [service] |
显示服务和基础设施状态 / Show status |
logs [service] [lines] |
查看日志(默认 200 行)/ View logs (default 200 lines) |
genkey [name] [prefix] |
生成 API Key(默认 sk_,也可生成 dk_)/ Generate API key (default sk_, dk_ optional) |
list-domains |
列出配置中的域名 / List configured domains |
list-apikeys |
列出配置中的 API Key(密钥已脱敏)/ List API keys (redacted) |
脚本自动生成 Docker Compose 文件并管理 MongoDB、Redis、NATS、MinIO 四个容器。默认所有端口仅绑定到 127.0.0.1(可通过环境变量 MAILAPI_INFRA_BIND_ADDR 调整,或通过 MAILAPI_SKIP_INFRA=1 完全跳过)。
The script auto-generates a Docker Compose file managing MongoDB, Redis, NATS, and MinIO containers. By default ports bind to 127.0.0.1 (tune via MAILAPI_INFRA_BIND_ADDR, or set MAILAPI_SKIP_INFRA=1 to skip infra management).
- root 用户 / Root: 自动安装 systemd 服务文件(
mailapi-api、mailapi-smtp、mailapi-worker) - 非 root / Non-root: 使用 PID 文件 + nohup 管理后台进程
三个服务(API / SMTP / Worker)都可以按需开启独立的 debug HTTP server(默认关闭,建议仅绑定 127.0.0.1 并置于防火墙/LB 后)。debug server 提供:
GET /healthz:进程存活检查 / LivenessGET /readyz:依赖就绪检查(会探测后端连通性)/ ReadinessGET /metrics:Prometheus 指标(可选)/ Prometheus metrics (optional)GET /debug/pprof/:pprof(可选,不要公网暴露)/ pprof (optional, do not expose publicly)
通过 config.yaml 的 server.api.debug / server.smtp.debug / server.worker.debug 开启并设置监听地址(建议仅绑定 127.0.0.1)。
运维便捷:也可以用 --debug-addr host:port 临时覆盖 debug server 地址并隐式启用(仅影响当前进程,不会改配置文件)。
server:
api:
debug:
enabled: true
host: "127.0.0.1"
port: 6060
metrics: true
pprof: falseCLI(所有服务通用)/ CLI (all services):
# 检查配置 / Validate config
./bin/mailapi-api --check-config ./config.yaml
./bin/mailapi-smtp --check-config ./config.yaml
./bin/mailapi-worker --check-config ./config.yaml
# 打印生效配置(默认脱敏)/ Print effective config (redacted by default)
./bin/mailapi-api --print-effective-config ./config.yaml
./bin/mailapi-smtp --print-effective-config ./config.yaml
./bin/mailapi-worker --print-effective-config ./config.yaml
# 输出未脱敏配置(谨慎)/ Print unredacted config (be careful)
./bin/mailapi-api --print-effective-config --full ./config.yaml
./bin/mailapi-smtp --print-effective-config --full ./config.yaml
./bin/mailapi-worker --print-effective-config --full ./config.yaml| 层级 / Layer | 防御 / Defense |
|---|---|
| SMTP 协议层 | RCPT TO 域名过滤 + Redis 地址校验 / Domain filtering + address validation |
| SMTP 连接层 | 100 连接/分钟/IP 速率限制 / 100 conn/min/IP rate limit |
| API 全局层 | 可配置 RPM/IP 速率限制(默认 100)/ Configurable RPM/IP rate limit (default 100) |
| API Key 层 | 每 Key 可配置 RPM 限制 / Per-key configurable RPM limit |
| API Key+域名层 | 每 Key 每域名可配置 RPM 限制 / Per-key-per-domain configurable RPM limit |
| API 认证层 | sk_/dk_ API Key + JWT 域名隔离 / sk_/dk_ API Key + JWT domain isolation |
| 防护 / Protection | 限制 / Limit |
|---|---|
| SMTP MaxMessageBytes | 20MB |
| MIME 嵌套深度 | 50 层 / 50 levels |
| 内联部分 / Inline Part | 10MB |
| 附件 / Attachment | 20MB |
go test ./... # 运行所有测试 / Run all tests
go test -cover ./... # 带覆盖率 / With coverage
go test -v ./... # 详细输出 / Verbose项目采用接口驱动的 Mock 测试模式。所有外部依赖(数据库、缓存、存储、队列)都定义了接口(interface.go),测试中使用函数指针注入来模拟各种行为。
The project uses an interface-driven mock testing pattern. All external dependencies have defined interfaces (interface.go), and tests use function pointer injection to simulate various behaviors.
| 模块 / Module | 测试重点 / Focus |
|---|---|
auth |
JWT 生成与验证、过期、错误 / JWT generation, validation, expiry, errors |
config |
YAML 解析、默认值、速率限制配置 / YAML parsing, defaults, rate limit config |
handler |
HTTP 处理器、域名隔离、自动前缀生成、随机地址 / HTTP handlers, domain isolation, auto-prefix, random addresses |
middleware |
Bearer 统一鉴权(sk_/dk_ Key + JWT)、域名限定、三层限流 / Unified Bearer auth, domain scoping, 3-layer rate limiting |
prefix |
最小长度、字符合法性、唯一性、分布、域名生成 / Min length, valid chars, uniqueness, distribution, domain generation |
smtp |
会话、RCPT TO 校验、数据大小、队列错误 / Session, RCPT TO validation, data size, queue errors |
worker |
MIME 解析、附件处理、错误恢复 / MIME parsing, attachment handling, error recovery |
# 一键安装 / One-click install
sudo ./mailapi.sh install
# 启动 / Start
sudo ./mailapi.sh start
# 状态 / Status
./mailapi.sh status| 项目 / Item | 说明 / Description |
|---|---|
jwt.secret |
必须修改为强随机字符串 / MUST change to strong random |
apiKeys[].key |
必须修改,默认使用 sk_ 前缀(兼容 dk_),用 ./mailapi.sh genkey 生成 / MUST change, default sk_ prefix (dk_ also accepted), use genkey |
minio.accessKey/secretKey |
必须修改 / MUST change |
rateLimit.global |
根据业务调整全局限速 / Adjust global rate limit per business needs |
apiKeys[].rpmLimit |
为每个 Key 设置合适的限速 / Set appropriate RPM per key |
| SMTP 端口 25 | 云环境需开放 / Must be open in cloud |
| TLS/HTTPS | 在反向代理终止 / Terminate at reverse proxy |
| MongoDB | 建议副本集 / Recommend replica set |
| Redis | 建议集群(共享速率限制状态)/ Recommend cluster (shared rate limit state) |
| 监控 / Monitoring | Prometheus + Grafana |
| DNS | 每个域名配置 MX 记录指向对应 IP / MX records for each domain to its IP |
| 方法 | 路径 | 认证 / Auth | 说明 |
|---|---|---|---|
GET |
/domains |
Bearer sk_* / dk_* or JWT | 域名列表(按 Key/JWT 过滤)/ List domains |
POST |
/accounts |
Bearer sk_* / dk_* or JWT | 创建账号(支持自动前缀)/ Create account (auto-prefix) |
POST |
/token |
Bearer sk_* / dk_* (optional) | 登录获取 JWT / Login for JWT |
GET |
/addresses/random |
Bearer sk_* / dk_* or JWT | 生成随机地址 / Generate random addresses |
GET |
/me |
Bearer JWT | 当前用户 / Current user |
GET |
/accounts/:id |
Bearer JWT | 账号信息 / Account info |
DELETE |
/accounts/:id |
Bearer JWT | 删除账号 / Delete account |
GET |
/messages |
Bearer JWT | 邮件列表 / List messages |
GET |
/messages/:id |
Bearer JWT | 邮件详情 / Message detail |
PATCH |
/messages/:id |
Bearer JWT | 更新邮件 / Update message |
DELETE |
/messages/:id |
Bearer JWT | 删除邮件 / Delete message |
GET |
/messages/:id/download |
Bearer JWT | 下载 .eml / Download .eml |
GET |
/messages/:id/attachments/:aid |
Bearer JWT | 下载附件 / Download attachment |
GET |
/sse |
Bearer JWT | 实时推送 / SSE stream |
认证说明 / Auth Note: 所有认证通过
Authorization: Bearer <token>头传递。sk_/dk_前缀令牌被识别为 API Key,其他被视为 JWT。未配置apiKeys时,公开接口无需认证。All auth goes through
Authorization: Bearer <token>.sk_/dk_-prefixed tokens are identified as API keys; others as JWT. WhenapiKeysis not configured, public routes require no auth.
| 参数 / Parameter | 值 / Value |
|---|---|
| 密码最小长度 / Min Password Length | 6 字符 / 6 characters |
| 用户名最小长度 / Min Username Length | 3 字符 / 3 characters |
| Bcrypt Cost | 10 |
| JWT 有效期 / JWT Expiry | 1 小时 / 1 hour |
| 账号 TTL / Account TTL | 7 天 / 7 days |
| 邮件 TTL / Message TTL | 7 天 / 7 days |
| 全局 IP 限速 / Global IP Rate Limit | 可配置(默认 100/min)/ Configurable (default 100/min) |
| API Key 限速 / Per-Key Rate Limit | 可配置(默认不限)/ Configurable (default unlimited) |
| Key+域名限速 / Per-Key-Per-Domain | 可配置 / Configurable |
| SMTP 速率限制 / SMTP Rate Limit | 100/min/IP |
| 最大邮件大小 / Max Email Size | 20MB |
| MIME 最大深度 / Max MIME Depth | 50 |
| 默认配额 / Default Quota | 40MB |
| 自动前缀碰撞重试 / Auto-prefix Collision Retries | 5 次 / 5 times |
| 随机地址最大生成数 / Max Random Addresses | 50 |
| 前缀唯一组合数 / Prefix Unique Combinations | > 50 亿/域名 / > 5 billion/domain |