mail_core 是一个“Provider 可插拔 + 统一 API + 本地状态持久化”的邮箱服务模块。
它解决的问题是:上层业务只写一套调用代码,就可以在不同临时邮箱后端之间切换,并且自动处理邮箱过期、轮换和本地记录维护。
- 统一调用入口:调用方只依赖
MailService。 - 运行时多态:通过 JSON 配置切换
provider,不改业务代码。 - 明确状态管理:邮箱
jwt/address/创建时间/过期时间统一落盘。 - 强制规则:每次拉邮件前先做过期检查,必要时自动重建邮箱。
- 可扩展:新增 Provider 只需实现抽象接口并注册工厂。
- 已内置 Provider:
cloudflare_workerduckmailgpt_mailmock
- 本地存储:
JSON文件(默认./data/mailboxes.json) - 统一验证码提取:支持从邮件原文中提取 6 位验证码
- 自动轮换策略:
- TTL 过期轮换
- 鉴权失败(401/403)触发一次重建重试
mail_core/
__init__.py
api.py # 统一门面 MailService
config.py # JSON 配置加载与环境变量注入
errors.py # 统一异常
models.py # 数据模型
provider_base.py # Provider 抽象接口
provider_registry.py # Provider 注册工厂
store.py # 本地 JSON 存储
code_parser.py # 验证码提取器
providers/
cloudflare_worker.py
duckmail_provider.py
gptmail_provider.py
mock_provider.py
最低依赖(按当前代码):
- Python 3.10+
requests
可选依赖:
- 若你需要继续使用旧脚本场景中的 dotenv,可自行安装
python-dotenv;mail_core本身不强依赖。
- 复制
mail_config.example.json为mail_config.json - 根据你使用的 Provider 填入参数
- 如需密钥,设置环境变量
from mail_core import MailService
svc = MailService.from_config("mail_config.json")
# 获取一个业务键对应的邮箱记录(不存在则创建)
record = svc.acquire_mailbox("gemini_signup")
print(record.address, record.expires_at)
# 拉取邮件(内部会先检查是否过期)
record, messages = svc.get_messages("gemini_signup", limit=20)
print(record.address, len(messages))
# 轮询验证码
record, code = svc.get_verification_code(
"gemini_signup",
timeout_sec=180,
poll_interval_sec=5,
)
print("code =", code){
"default_provider_id": "cf_main",
"store": {
"path": "./data/mailboxes.json"
},
"providers": {
"cf_main": {
"type": "cloudflare_worker"
}
}
}字段说明:
default_provider_id:默认 provider 实例 ID,可省略;省略时使用providers第一个条目。store.path:本地 JSON 存储路径。相对路径按配置文件所在目录解析。providers:provider 实例集合,键是实例 ID,值是 provider 参数对象。providers.<id>.type:provider 类型,必须已在注册表中注册。
mail_core 支持两种方式:
-
${ENV_NAME}内联替换
示例:"api_key": "${DUCKMAIL_API_KEY}" -
*_env映射注入
示例:"admin_password_env": "ADMIN_PASSWORD"
加载时自动读取环境变量并映射为admin_password。
常用参数:
worker_domainemail_domainadmin_password或admin_password_envtoken_ttl_secondsrequest_timeout_secenable_prefix
特点:
- 标准的“创建邮箱 + JWT 拉信”模型
- 支持 TTL 过期轮换
常用参数:
api_base(默认https://api.duckmail.sbs)api_key(可选)default_domain(可空,空则自动拉取可用域名)token_ttl_secondsrequest_timeout_seccreate_max_retriespassword_length
特点:
- 内部自动执行“建账号 + 换 token”
- 读取邮件时优先取源内容,失败回退详情内容
- 支持 TTL 过期轮换
常用参数:
base_url(默认https://mail.chatgpt.org.uk)request_timeout_secextra_headers
关键设计(当前版本为强约束):
type仅支持gpt_mail。- 邮箱记录严格视为永不过期。
expires_at固定写入null。- 不提供
token_ttl_seconds配置入口。 jwt语义为邮箱地址本身(因为 GPTMail 接口不返回独立 token)。
示例:
{
"providers": {
"gpt_main": {
"type": "gpt_mail",
"base_url": "https://mail.chatgpt.org.uk",
"request_timeout_sec": 10,
"extra_headers": {}
}
}
}本地测试用 Provider,不依赖真实网络服务。适合单元测试和开发联调。
MailService 核心接口:
from_config(config_path)acquire_mailbox(key, provider_id=None, name_hint=None, force_recreate=False)refresh_if_expired(key, provider_id=None, name_hint=None, create_if_missing=True)get_messages(key, limit=20, offset=0, provider_id=None, name_hint=None, create_if_missing=True)get_latest_raw(key, provider_id=None, name_hint=None, create_if_missing=True)get_verification_code(key, timeout_sec=180, poll_interval_sec=5, provider_id=None, name_hint=None, create_if_missing=True, limit=20)list_mailboxes()close()
key 说明:
key是业务侧稳定标识,不是邮箱地址。- 同一个
key在轮换后仍可继续使用,调用方不需要关心地址变化。
统一策略在 MailService 执行:
- 每次
get_messages/get_latest_raw/get_verification_code前先调用refresh_if_expired。 - 若记录过期则自动重建邮箱并更新本地 JSON。
- 若拉信遇到
ProviderAuthError,会触发一次“重建 + 重试”。
Provider 级差异:
cloudflare_worker/duckmail:默认按 TTL 轮换(来自配置或 provider 默认)。gpt_mail:永不过期,不参与 TTL 轮换。
默认文件:data/mailboxes.json
{
"version": 1,
"mailboxes": {
"gemini_signup": {
"key": "gemini_signup",
"provider_id": "cf_main",
"address": "abc123@example.com",
"jwt": "xxxxx",
"created_at": "2026-03-01T10:00:00.000Z",
"expires_at": "2026-03-08T10:00:00.000Z",
"updated_at": "2026-03-01T10:00:00.000Z",
"rotation_count": 0,
"last_fetch_at": null,
"last_error": null,
"meta": {}
}
}
}写入特性:
- 进程内线程锁保护
- 临时文件 +
os.replace原子落盘
实现步骤:
- 在
mail_core/providers/新建 provider 文件并继承MailProvider - 实现以下方法:
provider_namecreate_mailboxfetch_messages
- 如需特殊过期语义:
- 覆写
default_ttl_seconds()或is_non_expiring()
- 覆写
- 在
mail_core/provider_registry.py注册type -> factory - 更新
mail_config.example.json和本 README
核心异常类型:
ConfigError:配置错误ProviderNotFoundError:provider 类型未注册ProviderRequestError:网络/响应错误ProviderAuthError:鉴权错误(会触发轮换重试)StoreError:本地存储读写失败MailboxNotFoundError:找不到业务键对应记录
- 不要把密钥明文提交到仓库,优先用环境变量注入。
- 日志中不要直接打印完整
jwt。 - 对生产环境建议限制
mailboxes.json文件权限。
Q: 为什么我没有传 provider_id 也能创建邮箱?
A: 会自动使用 default_provider_id。
Q: 为什么同一个 key 下邮箱地址变了?
A: 发生了过期轮换或鉴权失败重建。
Q: gpt_mail 为什么不轮换?
A: 这是当前版本的强设计:gpt_mail 定义为永不过期 provider。
- 已明确取消向后兼容设计。
gpt_mail仅接受type: "gpt_mail"。- 不保证旧配置中的历史字段可无改动沿用。