小苹果是一套自托管邮箱管理工具,后端基于 Flask 和 PostgreSQL,通过 Microsoft OAuth 与 Graph API 读取邮件。项目默认采用服务端凭据存储、数据库会话、CSRF 防护和回环监听,避免将邮箱密码或 refresh token 暴露给浏览器持久化存储。
- 批量导入、查看、更新、删除和流式导出邮箱账号。
- 加密保存邮箱密码与 OAuth refresh token,账号列表只返回非敏感元数据。
- 读取邮件、提取验证码并检测 OpenAI 域名发件人的绑定邮件。
- 数据库会话、严格 Cookie、同源校验、CSRF 校验和登录失败限流。
- Microsoft Graph 分页、响应大小上限、超时与临时错误分类。
生产请求链路如下:
Browser --HTTPS--> Caddy --HTTP/loopback--> Gunicorn (mail-manager user)
|
+--> Flask
|--> PostgreSQL
+--> Microsoft OAuth/Graph
Caddy 负责 TLS、请求体限制和安全响应头;Gunicorn 只监听 127.0.0.1:5000。邮箱凭据使用 Fernet 加密后保存到 PostgreSQL,浏览器只使用服务端账号 ID。
环境要求:Python 3.12 或 3.13、PostgreSQL 17,以及运行前端测试时所需的 Node.js LTS。
python -m venv .venv
source .venv/bin/activate
python -m pip install --require-hashes -r requirements.lock
python -m pip install --no-build-isolation --no-deps -e .
npm ci创建专用 PostgreSQL 角色和数据库。请将数据库密码替换为独立生成的随机值,并确保 PostgreSQL 只接受本机或受信网络连接。
CREATE ROLE mail_manager LOGIN PASSWORD 'replace-with-a-random-password';
CREATE DATABASE mail_manager OWNER mail_manager;从样例创建本地环境文件,并限制其权限:
install -m 600 deploy/mail-manager.env.example .env分别生成访问密码、会话密钥和 Fernet 凭据加密密钥:
python -c "import secrets; print(secrets.token_urlsafe(24))" # ACCESS_PASSWORD
python -c "import secrets; print(secrets.token_urlsafe(48))" # SESSION_SECRET
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" # CREDENTIALS_ENCRYPTION_KEY填写 .env 中的必填配置后,将其加载到当前 shell,并初始化数据库。schema 可以安全地连续执行。
set -a
. ./.env
set +a
flask --app 'mail_manager:create_app()' init-db
flask --app 'mail_manager:create_app()' run --host 127.0.0.1 --port 5000开发环境如使用 HTTP,将 PUBLIC_ORIGIN 设为准确的本地 origin,并设置 COOKIE_SECURE=false。生产环境必须保持安全 Cookie 和 HTTPS。
必填变量:
| 变量 | 说明 |
|---|---|
DATABASE_URL |
PostgreSQL 连接 URL |
ACCESS_PASSWORD |
管理端访问密码,至少 16 个字符 |
SESSION_SECRET |
会话派生密钥,至少 32 个字符 |
CREDENTIALS_ENCRYPTION_KEY |
Fernet 密钥,用于加密邮箱凭据 |
PUBLIC_ORIGIN |
浏览器访问的唯一 HTTPS origin,不含末尾 / |
反向代理部署时设置 TRUST_PROXY=true,并将 TRUSTED_PROXY_CIDRS 严格限制为 Caddy 到达应用时使用的地址。不要把它配置为公网网段。
运行不依赖外部服务的后端测试:
python -m pytest tests -m "not postgres"
python -m ruff check .
python -m compileall -q mail_manager testsPostgreSQL 集成测试只应指向专用测试数据库:
export TEST_DATABASE_URL='postgresql://mail_manager_test:replace-me@127.0.0.1:5432/mail_manager_test'
python -m pytest tests/test_postgres_integration.py -v该测试会写入并清理随机命名的数据。禁止将 TEST_DATABASE_URL 指向生产数据库。前端测试和依赖审计:
npm test
python -m pip_audit -r requirements.lock
npm audit --audit-level=high- 创建无登录权限的
mail-manager系统用户,将仓库部署到/opt/mail-manager,并由该用户读取代码与虚拟环境。 - 将
deploy/mail-manager.env.example安装到/etc/mail-manager/mail-manager.env,所有者设为root:root,权限设为0600,再填写真实值。 - 安装
deploy/mail-manager.service到/etc/systemd/system/,执行systemctl daemon-reload和systemctl enable --now mail-manager。 - 将
deploy/Caddyfile纳入 Caddy 配置,设置MAIL_MANAGER_DOMAIN,验证配置后重载 Caddy。 - 通过
systemctl status mail-manager、journalctl -u mail-manager和 HTTPS 健康访问确认服务状态。
环境文件应由 root 独占读取。创建目标目录后使用以下命令安装样例,再通过 root 权限填写真实值:
install -d -o root -g root -m 700 /etc/mail-manager
install -o root -g root -m 600 deploy/mail-manager.env.example /etc/mail-manager/mail-manager.envsystemd 样例在每次启动前执行 init-db,以专用用户运行 Gunicorn,并设置文件系统、设备、能力集与资源限制。若安装路径不同,请同步修改 unit 中的绝对路径。
禁止将 Gunicorn 直接暴露到公网。不要把 --bind 127.0.0.1:5000 改为 0.0.0.0,也不要绕过 Caddy 的 HTTPS、2 MiB 请求体上限和安全响应头。
升级前和定期执行 PostgreSQL 逻辑备份,并将备份加密保存到与应用主机隔离的位置:
pg_dump --format=custom --no-owner --file "mail-manager-$(date +%F-%H%M%S).dump" "$DATABASE_URL"
pg_restore --list mail-manager-YYYY-MM-DD-HHMMSS.dump定期在隔离数据库中演练 pg_restore,仅有备份文件而没有恢复验证不构成可靠备份。CREDENTIALS_ENCRYPTION_KEY 必须通过独立的秘密管理渠道备份;丢失该密钥后,数据库中的邮箱凭据无法恢复。
- 记录当前 Git 提交并完成数据库备份。
- 获取目标版本,重新执行带哈希的
requirements.lock安装和npm ci。 - 执行
flask --app 'mail_manager:create_app()' init-db,然后运行后端与前端测试。 - 重启服务并检查 journal、登录、账号列表与邮件读取。
- 应用回滚时先阅读版本说明;代码可以切回已记录提交,数据库则应根据 schema 兼容性决定是否从备份恢复。
本项目采用 GNU General Public License v3.0 only,SPDX 标识为 GPL-3.0-only。