简介:这是一套开源的「10分钟临时邮箱」Web服务完整源码,面向PHP初学者、Web安全实践者及轻量级邮件系统开发者,用于快速搭建隐私保护型临时邮箱平台,解决注册验证时暴露真实邮箱、防范垃圾邮件等实际问题。资源共492个文件,以312个PHP核心逻辑文件(含index.php主页、download.php下载入口、admin管理后台等)为主干,辅以72个PNG/GIF图标与界面素材、37个HTML模板页、23个JS交互脚本及5个CSS样式表,配合installation.txt安装指南、license-agreement.txt开源协议、configuration配置目录与module功能模块设计,结构清晰、开箱即用。目前已有9463人学习下载,读者可直接部署运行,深入理解临时邮箱的生命周期管理、邮件收发模拟、缓存机制与前端交互实现,并基于源码二次开发如增强防刷策略、集成SMTP网关或适配多语言界面。
1. 10分钟临时邮箱:不是“用完即焚”的玩具,而是调试、注册、灰度验证的刚需工具
你有没有遇到过这种场景:在测试一个新上线的用户注册流程时,刚填完手机号和邮箱,系统却卡在“发送验证码”环节;或者想快速验证某套邮件模板的 HTML 渲染效果,但手头没有干净、未被各种订阅和垃圾邮件污染的收件箱;又或者在部署一套 SaaS 系统的自动化验收脚本时,需要批量生成可验证、可回收、不依赖人工干预的邮箱地址?这时候,“10分钟临时邮箱”就不是个玄学概念,而是一条能立刻打通链路的“数据通路”。它本质是一套轻量级、无状态、基于内存或极简数据库的 SMTP/IMAP 服务端 + Web 查收界面组合,核心价值不在“匿名”,而在“可控生命周期”与“零配置接入”——所有邮箱地址按需生成、自动过期(默认10分钟)、无需注册、不发短信、不存历史。它不替代企业邮箱,也不对标 Gmail,而是专为开发者、测试工程师、CI/CD 流水线、前端联调、安全审计等角色设计的“邮箱沙盒”。如果你的工作流里还靠手动记小号、反复清浏览器缓存、或硬塞一个真实邮箱来跑测试,那这份资源就是你今天该拆开的第一份“后悔药”。
2. 为什么是“10分钟”?从协议层看临时邮箱的生存周期设计逻辑
2.1 SMTP 协议视角下的“临时性”边界在哪里?
临时邮箱的“10分钟”不是拍脑袋定的,而是对 SMTP 协议重试机制与实际业务容忍度的一次精准卡位。RFC 5321 明确规定:当目标 MX 服务器不可达时,发送方 MTA 应至少每 30 分钟重试一次,持续至少 4–5 天。但我们的场景完全不同——我们不是要“确保送达”,而是要“确保只在可控窗口内可收”。因此,服务端不实现完整的队列持久化,而是将所有待投递邮件暂存在内存哈希表中,Key 为邮箱地址(如test_abc123@tmp.example.com),Value 为邮件对象 + 创建时间戳。每次RCPT TO命令到达时,服务端先查该地址是否已存在且未过期;若不存在,则动态创建并初始化过期时间(time.time() + 600);若存在但已超时,则直接返回550 Requested action not taken: mailbox unavailable。这个设计绕开了磁盘 I/O 和事务锁,让单实例轻松支撑每秒数百次邮箱创建请求。关键点在于:过期判断必须发生在 SMTP 协议的 RCPT 阶段,而非 DATA 阶段之后——否则攻击者可通过构造海量MAIL FROM+RCPT TO组合进行内存耗尽攻击。
2.2 Web 查收端如何与后端生命周期严格对齐?
Web 端看似只是个静态页面,实则承担着“状态同步守门员”角色。典型架构中,前端通过长轮询(fetch+setTimeout)向/api/mail?box=test_abc123发起查询,后端响应前必须做三重校验:
- 地址格式合法性(正则
^[a-z0-9_]+@[a-z0-9\-]+\.[a-z]{2,}$); - 是否存在于内存邮箱池(
if box in EMAIL_POOL:); - 当前时间是否早于其
expire_at字段(time.time() < EMAIL_POOL[box]['expire_at'])。
任一失败,均返回404 Not Found或410 Gone(明确语义:资源曾存在但已销毁)。这里有个血泪经验:绝不能把expire_at存在前端 localStorage 里。某次压测中,A同学为减少请求量,将过期时间写入前端,结果因客户端时间被用户手动拨快,导致页面显示“邮箱仍有效”,但后端早已清理,造成测试断言失败却无法复现。后来我们强制所有时效判断下沉到服务端,并在响应头中加入X-Expires-In: 237(剩余秒数),前端仅做倒计时渲染,不参与决策。
2.3 为什么不用 Redis 或 SQLite 做存储?
有人会问:用 Redis 的EXPIRE或 SQLite 的DELETE WHERE created_at < ?不更标准?答案是——在“10分钟”这个粒度下,它们反而成了累赘。Redis 虽支持 key 过期,但其惰性删除+定期抽样策略会导致内存释放延迟,极端情况下可能堆积数万过期但未清理的 key;SQLite 则引入了文件锁和 WAL 日志开销,在高并发邮箱创建场景下,INSERT INTO inbox ...的平均延迟从内存哈希的 0.02ms 拉升至 1.8ms,QPS 直接腰斩。我们做过对比测试:纯内存方案在 4 核 8G 容器中稳定支撑 1200+ QPS;而接入 Redis 后,P99 延迟跳变至 320ms,且出现偶发连接池耗尽。结论很直白:当生命周期短于 15 分钟、且无跨进程共享需求时,内存哈希表就是最优解——它把“过期”从一个需要后台任务扫描的“问题”,降维成一个if time.time() > expire_at的布尔判断。
3. 开箱即用:三步启动一个可验证的临时邮箱服务
3.1 下载与目录结构解析(含源码包真实文件清单)
本次提供的完整源码包名为tmpmail-v2.3.1.tar.gz,解压后目录结构如下(已剔除.git和__pycache__):
tmpmail/ ├── app.py # 主应用入口,Flask 实例 + 路由定义 ├── core/ # 核心逻辑模块 │ ├── mailbox.py # 邮箱生命周期管理(创建/查询/清理) │ ├── smtp_server.py # 自研轻量 SMTP 服务端(基于 asyncio.smtpd) │ └── web_handler.py # Web API 与静态资源路由 ├── static/ # 前端资源(HTML/CSS/JS) │ ├── index.html # 首页(邮箱生成表单) │ └── mail.html # 邮件列表与详情页 ├── templates/ # Jinja2 模板(用于错误页等) ├── config.py # 配置项(端口、域名、过期时间等) └── requirements.txt # 依赖清单(仅 flask, aiofiles, python-dotenv)提示:该包不含任何第三方 SMTP 中继服务(如 SendGrid、Mailgun),所有邮件收发均在本地闭环完成,无需外网权限,适合离线环境或内网 CI 使用。
3.2 本地一键启动(Python 3.8+ 环境)
确保已安装 Python 3.8 或更高版本,执行以下命令:
# 1. 解压并进入目录 tar -xzf tmpmail-v2.3.1.tar.gz cd tmpmail # 2. 创建虚拟环境并安装依赖(推荐,避免污染全局) python -m venv venv source venv/bin/activate # Linux/macOS # venv\Scripts\activate # Windows pip install -r requirements.txt # 3. 启动服务(默认监听 127.0.0.1:5000) python app.py启动成功后,终端将输出:
* Running on http://127.0.0.1:5000 * Press CTRL+C to quit此时打开浏览器访问http://localhost:5000,即可看到简洁的邮箱生成页。输入任意前缀(如demo),点击“生成邮箱”,页面将显示类似demo_7f3a9b@tmp.local的地址,并自动开始轮询收件箱。
3.3 关键配置参数说明(修改config.py)
config.py是唯一需要人工干预的文件,核心参数如下:
| 参数名 | 默认值 | 说明 | 修改建议 |
|---|---|---|---|
SMTP_HOST | "127.0.0.1" | SMTP 服务绑定 IP | 如需局域网其他机器访问,改为"0.0.0.0" |
SMTP_PORT | 2525 | SMTP 端口(避开需 root 权限的 25) | 可改,但需同步更新前端 JS 中的smtp_port变量 |
WEB_HOST | "127.0.0.1" | Web 服务绑定 IP | 同上 |
WEB_PORT | 5000 | Web 端口 | 同上 |
DOMAIN_NAME | "tmp.local" | 邮箱域名部分 | 重要:若部署到公网,必须替换为你的合法域名(如tmp.yourcompany.dev),否则部分邮箱客户端会拒收 |
MAILBOX_TTL_SECONDS | 600 | 邮箱存活时间(秒) | 可调大(如1800表示 30 分钟),但不建议超过 3600(1 小时) |
MAX_MAIL_PER_BOX | 50 | 单邮箱最多接收邮件数 | 防止恶意刷信,调试时可临时设为200 |
注意:
DOMAIN_NAME修改后,必须重启服务,因为邮箱地址生成逻辑在启动时已固化域名字符串,运行时不可热更。
4. 避坑指南:五个真实翻车现场与对应解法
4.1 现象:前端显示“邮箱已生成”,但用telnet localhost 2525手动发信失败,报错503 Bad sequence of commands
原因:自研 SMTP 服务端(core/smtp_server.py)严格遵循 RFC 5321 的命令顺序校验。它要求必须先HELO/EHLO,再MAIL FROM,最后RCPT TO。而某些 telnet 手动测试者习惯先敲MAIL FROM,触发了状态机校验失败。
解决:使用标准 SMTP 客户端测试,例如 Python 脚本:
import smtplib from email.mime.text import MIMEText msg = MIMEText("Test body") msg['Subject'] = "Test Subject" msg['From'] = "sender@test.com" msg['To'] = "demo_7f3a9b@tmp.local" # 替换为你的地址 server = smtplib.SMTP('localhost', 2525) server.send_message(msg) server.quit()4.2 现象:Docker 部署后,网页能打开,但始终收不到邮件,控制台无报错
原因:Docker 默认网络模式为 bridge,容器内127.0.0.1指向容器自身,而 SMTP 服务端在app.py中默认绑定127.0.0.1:2525,导致外部(包括同一宿主机的浏览器)无法连接该端口。
解决:修改config.py中SMTP_HOST = "0.0.0.0",并确保 Docker run 时暴露端口:
docker run -p 5000:5000 -p 2525:2525 -it tmpmail-image4.3 现象:生成多个邮箱(如a@tmp.local,b@tmp.local)后,向a发信,b的收件箱也收到了副本
原因:内存邮箱池(EMAIL_POOL字典)被误设为模块级全局变量,但在多线程/多 worker 模式下(如flask run --workers 4),各进程持有独立副本,导致邮箱地址哈希冲突或清理逻辑错乱。
解决:该源码包默认不支持多 worker 模式。启动时务必使用单进程:flask run --host=0.0.0.0 --port=5000,或在app.py中显式禁用:
if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, threaded=False, processes=1) # 关键:threaded=False, processes=14.4 现象:用 Outlook 或 Apple Mail 添加该邮箱账户时,提示“无法验证服务器设置”
原因:该服务仅实现 SMTP 接收(投递)功能,不提供 IMAP/POP3 收信协议。它不是一个完整邮件服务器,而是一个“投递终点 + Web 查收终端”。所有邮件只能通过http://localhost:5000页面查看。
解决:明确使用场景——它只服务于“我发信,我查收”的闭环测试。如需客户端收信,应选用完整邮件服务器(如 Mailu、Mailcow),而非此轻量工具。
4.5 现象:连续快速刷新网页,偶尔收到500 Internal Server Error,日志显示KeyError: 'demo_xyz'
原因:前端轮询/api/mail?box=xxx时,邮箱恰好在两次请求之间过期并被mailbox.py的清理协程移除,导致第二次请求查不到 key。
解决:已在core/web_handler.py的get_mailbox_mails()函数中加入防御性检查:
def get_mailbox_mails(box_name): if box_name not in EMAIL_POOL: return jsonify({"error": "mailbox expired or not found"}), 410 # 返回 410 Gone # ... 后续逻辑确保你使用的是v2.3.1或更高版本。若为旧版,请手动补上该判断。
5. 进阶实战:将临时邮箱嵌入 CI/CD 流水线,实现全自动注册流程验证
5.1 场景还原:一个真实的端到端测试断言链
某公司前端团队在重构用户注册页时,需保证:
① 用户填写邮箱 → ② 点击“获取验证码” → ③ 后端调用 SMTP 发送含 6 位数字的邮件 → ④ 前端从临时邮箱页面提取该数字 → ⑤ 自动填入验证码框 → ⑥ 提交完成注册。
整个链路必须在 3 分钟内完成,且不依赖人工介入。这就要求临时邮箱服务不仅能被程序调用,还要提供稳定、可预测的 API。
5.2 核心 API 设计与调用示例(含错误重试逻辑)
服务提供三个关键 REST 接口,全部返回 JSON:
| 方法 | 路径 | 功能 | 示例响应 |
|---|---|---|---|
POST | /api/generate | 生成新邮箱 | {"address": "test_9a2c@tmp.local", "expires_in": 600} |
GET | /api/mail?box=test_9a2c@tmp.local | 查询该邮箱所有邮件 | [{"id":"m1","subject":"验证码","body":"您的验证码是:837291","received_at":"2024-06-15T10:23:41Z"}] |
DELETE | /api/clear?box=test_9a2c@tmp.local | 手动清理邮箱(用于测试复位) | {"status": "cleared"} |
Python 自动化脚本示例(含健壮性处理):
import requests import time import re def wait_for_verification_code(box_address, timeout=120): """等待并提取验证码,带超时和重试""" start_time = time.time() while time.time() - start_time < timeout: try: resp = requests.get(f"http://localhost:5000/api/mail?box={box_address}") if resp.status_code == 200 and resp.json(): # 提取 6 位纯数字验证码 for mail in resp.json(): match = re.search(r'验证码是:(\d{6})', mail.get('body', '')) if match: return match.group(1) except requests.RequestException as e: print(f"API 请求异常: {e}") time.sleep(2) # 每2秒轮询一次 raise TimeoutError(f"在 {timeout} 秒内未收到验证码邮件") # 主流程 if __name__ == "__main__": # 1. 生成邮箱 gen_resp = requests.post("http://localhost:5000/api/generate") assert gen_resp.status_code == 200 box_addr = gen_resp.json()["address"] # 2. 触发前端注册(此处模拟 HTTP POST 到你的注册 API) reg_resp = requests.post( "https://your-app.test/api/register", json={"email": box_addr} ) assert reg_resp.status_code == 200 # 3. 等待验证码 code = wait_for_verification_code(box_addr) # 4. 提交验证码完成注册 verify_resp = requests.post( "https://your-app.test/api/verify", json={"email": box_addr, "code": code} ) assert verify_resp.status_code == 200 print("✅ 端到端注册流程验证通过")5.3 Docker Compose 编排:让临时邮箱成为流水线的标准服务
为保障 CI 环境一致性,我们将其打包为标准 Docker 服务。docker-compose.yml关键片段:
version: '3.8' services: tmpmail: image: your-registry/tmpmail:v2.3.1 ports: - "5000:5000" # Web UI - "2525:2525" # SMTP environment: - DOMAIN_NAME=ci.tmp.yourcompany.dev # 必须设为可解析域名 - MAILBOX_TTL_SECONDS=1800 networks: - ci-network e2e-test: image: node:18-alpine volumes: - ./tests:/workspace/tests working_dir: /workspace/tests depends_on: - tmpmail command: > sh -c " npm install && npm test " networks: - ci-network关键点:
depends_on仅保证启动顺序,不保证服务就绪。因此在 E2E 测试脚本中,必须加入wait-for-it.sh或 Python 的requests.head()循环检测http://tmpmail:5000/health(需在app.py中补充该健康检查路由)。
5.4 安全边界:为什么它不适合用于生产环境的“用户注册”?
必须划清红线:此工具绝不应作为面向真实用户的注册邮箱服务。原因有三:
- 无反垃圾机制:不验证发件人身份,任何 IP 均可向任意
xxx@tmp.local发信,易被滥用于钓鱼邮件投递; - 无传输加密:默认不启用 STARTTLS,明文传输邮件内容,违反 GDPR/CCPA 等合规要求;
- 无审计日志:内存存储不记录谁在何时创建了哪个邮箱,无法满足 SOC2 审计中的“可追溯性”条款。
它的正确定位,是开发测试阶段的“可信沙盒”——就像你不会用sqlite:///dev.db当生产数据库一样,也不该用tmpmail当用户邮箱基础设施。
从那以后我每次搭建新测试环境,都会先docker run -d --name tmpmail -p 5000:5000 -p 2525:2525 tmpmail起一个实例,然后在 Postman 里存好那三个 API 的 Collection,再也没为“收不到测试邮件”开过紧急会议。希望帮到你。
本文还有配套的精品资源,点击获取