Skip to content

Latest commit

 

History

10 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Melon Mail

Melon Mail 🍉

自建邮件推送 + 收发系统:服务器盯着多个 IMAP 邮箱,新邮件经 FCM 推送到自写的安卓客户端; 客户端只跟服务器的私有 API 通信,读正文 / 回信 / 发信等 IMAP/SMTP 操作全在服务器完成。 手机不存任何邮箱凭据、不维持持久 IMAP 连接(只靠系统级 FCM 连接,省电)。

架构

                    ┌──────────── 服务器(Docker,常驻插电)────────────┐
 Gmail / 其他 IMAP ◄┤ goimapnotify(IMAP IDLE 监听)─新邮件─► push-fcm ──FCM──► 安卓 app(弹通知)
  (邮件留在原 provider)│ imaplib 连接池(读列表/正文/附件/回复模板) ◄ FastAPI ─┐                          │
                    │ smtplib(发信/回信)                              │◄── 私有 JSON/multipart API ──► 安卓 app(读/写/回)
                    └──────────────────────────────────────────────────┘
  • 监听:goimapnotify(纯 RFC2177 IDLE,兼容 Gmail)
  • 引擎:Python imaplib 持久连接池(读列表/正文/附件 + 生成回复转发模板) —— 0.2.0 起取代原 himalaya CLI 后端(见下「变更日志」)
  • 发信:Python smtplib(直连 SMTP,不碰 IMAP)
  • API:FastAPI(bearer 鉴权;须置于 HTTPS 之后)
  • 管理后台:独立的内网 web 界面(密码登录,图形化管理邮箱账户/app token/OAuth + 看状态),独立端口、仅限局域网;无桌面的 VPS 可用 server/admin-cli.sh 命令行做同样操作
  • 推送:FCM HTTP v1 + Firebase Admin SDK(data message,多设备)
  • 客户端:Kotlin + Jetpack Compose + Material 3

目录

路径 内容
server/ 服务端(Docker 镜像:goimapnotify + FastAPI + Python imaplib/smtplib)。部署见 server/README.md
android/ 安卓客户端(Compose)。构建见 android/README.md
mail-push-relay-design.md 整体设计文档
docker-design.md Docker / ZimaOS 部署设计

功能

  • 多账号收件箱 + 统一收件箱(跨账号合并,各账户并发拉取)
  • 多种邮箱:Gmail / Yahoo / 网易 163·126 等用应用专用密码;Outlook / Microsoft 365 / Hotmail 走 OAuth2(微软已停用密码登录,见快速上手)
  • 未读数:统一收件箱顶部显示总未读;账户列表每个账户显示各自未读(服务端 IMAP SEARCH UNSEEN 全量统计)
  • 查看已发邮件(自动探测各账号的「已发」文件夹,收件箱顶部一键切换)
  • 置顶(pin)邮件:右滑置顶,固定在收件箱与统一收件箱顶部;置顶内容单独缓存,不被自动清理;邮件在服务器端被删或取消置顶时自动移除
  • 下拉刷新 + 上拉无限加载;未读/已读区分(打开即标已读)
  • HTML 正文用 WebView 渲染(默认禁远程图防跟踪,可一键显示);跟随系统暗色主题
  • 查看 / 下载附件(保存到设备)
  • 写信 / 回复 / 回复全部 / 转发,均支持附件(转发自动带原附件);收件人可从手机通讯录选
  • 删除邮件(列表左滑 / 详情页,带确认,同步服务器移回收站)
  • 详情页可展开看完整发件人/收件人地址
  • 离线缓存:无网时可看已加载的收件箱与读过的正文;正文缓存有容量上限自动回收(置顶豁免)
  • FCM 推送,多设备自动注册(token 自动上报);点通知直达对应邮件
  • 内网管理后台(独立端口):图形化管理邮箱账户(增删改收发+推送账户,选 provider 预设自动填 host/port,直接在网页里设密码;每个账户可单独决定是否开推送)、管理多个 app token(每台设备一个;删某 token 连带切断其推送设备,丢设备一键同时断「读信 + 推送」)、OAuth 授权(Outlook 等);查看版本/运行状态、管理推送设备;app 端 token 输入框掩码显示

快速上手(从零跑起来)

服务端用官方预构建镜像免构建直接拉;只有安卓 app 需要你本地编译(因为要放你自己的 Firebase 配置)。

前置:Docker + docker compose;编译 app 需 Android Studio 或 Android SDK(./gradlew);一个免费 Firebase 项目,从中拿两个文件——服务端发 FCM 用的 service-account.json、安卓 app 用的 google-services.json(两者必须同一个 Firebase 项目)。

# 1) 克隆
git clone https://github.com/bidabrain/mailpush && cd mailpush

# 2) 编译安卓 app(放入你自己的 google-services.json 覆盖占位文件)
cp /path/to/google-services.json android/app/google-services.json
( cd android && ./gradlew assembleDebug )      # 产物:android/app/build/outputs/apk/debug/app-debug.apk → 装到手机

# 3) 服务端:准备密钥(都放 server/config,已被 .gitignore 忽略)
cd server
mkdir -p config/secrets data
cp .env.sample .env
#   编辑 .env:取消注释 CONFIG_DIR=./config 与 DATA_DIR=./data(让配置落在 server/config、server/data)
printf '管理后台密码'    > config/secrets/admin.pass    # 内网管理后台登录用(必需)
cp /path/to/service-account.json config/service-account.json   # Firebase 服务端密钥(发推送必需)
#   邮箱账户:本步【先不用配】—— 起服务后在管理后台网页里加(方式 B,见下);
#   想用文件配置的看「两种配置账户的方式 · 方式 A」。

# 4) 拉官方镜像并运行(docker-compose.dist.yml 默认就指向 bidabrain/mailpush:latest)
docker compose -f docker-compose.dist.yml pull
docker compose -f docker-compose.dist.yml up -d

# 5) 内网浏览器开 http://<服务器内网IP>:8098,用管理密码登录:
#   - 「邮箱账户」加你的邮箱(填地址+应用专用密码,选 provider 预设)→ 立刻能收发,几秒内起推送
#   - 「App Token」给每台手机「新建 Token」→ app 设置里填:服务器地址 + 该 Token
#   (服务器地址生产环境务必走 HTTPS,见「安全」)

完成后:发封测试邮件,手机应收到 FCM 推送;点开能读正文、回信、发信。

  • 没有图形界面?(VPS / 纯 SSH):管理后台只绑本机、打不开浏览器时,用 server/admin-cli.sh 在命令行做与网页完全一样的操作(登录、增删邮箱账户 / app token、OAuth 授权等)。见 server/README.md 的「命令行管理后台」
  • 架构匹配:官方镜像是 linux/amd64。arm64 机器(树莓派等)需自行 buildx 构建,详见 server/README.md
  • 自己构建服务端:docker compose up -d --build,或 buildx --push 发到自己的 Docker Hub,见 server/README.md
  • ⚠️ 上线前务必读「安全」:API(8099)要放 HTTPS 之后,管理后台(8098)只能内网。

两种配置账户的方式(可并存)

收发邮箱账户有两种配置途径,互相兼容、可混用:同名时以 webui 为准,异名各自生效。

方式 A · 手写配置文件 方式 B · webui 管理后台(:8098)
存放 config/config.toml + config/imapnotify.yaml + config/secrets/*.pass(/config,只读挂载) /data/accounts.json + /data/secrets/<name>.pass(可写,网页里增删改)
加账户 复制 *.sample 改账号,改完重启 mail-api/mail-watch 网页「邮箱账户」填表保存,收发即时生效,推送自动重启监听
设密码 写进 secrets/<name>.pass 网页密码框直接填(编辑时留空=不改)
只收发不推送 config 写、imapnotify 不写 取消勾选「启用推送监听」
适合 批量/版本化/CI、想把配置纳入 git 日常增删、不想 SSH、图形化操作
  • 方式 B 的底层:mail-api 读 config.toml 后把 webui 账户叠加进来(accounts.pyoverlay_config);mail-watch 用 render_imapnotify.py 把 webui 账户与手写 base 合并成 /data/imapnotify.generated.yaml 再跑 goimapnotify,改动经 /data/watch.reload 触发自动重启。/config 始终只读,安全边界不变。
  • 可以完全不写 config 文件:只放 admin.pass + service-account.json 就能起服务,账户全在 webui 加(零账户时 mail-watch 空转等待,不报错)。
  • 详见 server/README.md

接入 Outlook / Microsoft 365 / Hotmail(OAuth2,不是密码)

微软已对 Outlook.com 个人账户与多数 M365 停用密码登录(Basic Auth) —— 没有"应用专用密码"这条路,只能走 OAuth2。Gmail / Yahoo / 网易等不受影响,仍用上面的 .pass;只有微软账户要多做下面这套(一次性,约 10 分钟):

  1. Azure 免费注册一个应用:用那个 Outlook 邮箱登录 entra.microsoft.comApp registrations → New registration:
    • 账户类型选**含「个人 Microsoft 账户」**的那项(outlook.com 个人号必需);
    • AuthenticationAllow public client flows = Yes(设备码流需要);
    • API permissions 加**委托(Delegated)**权限:IMAP.AccessAsUser.AllSMTP.Sendoffline_access;
    • 记下 Application (client) ID(不要建 client secret)。
  2. 在内网管理后台授权(最省事):浏览器开 http://<内网IP>:8098 → 「OAuth」区填账号名(如 outlook)+ 上面的 client_id → 保存 → 点「授权」→ 按提示在浏览器输设备码、用 Outlook 账户登录同意。凭据会自动存到 /data/oauth/(refresh token 后续自动静默刷新)。
  3. 加账号(任选其一,见上「两种配置账户的方式」):
    • 方式 B(推荐):授权后,在管理后台「邮箱账户」用相同账号名添加一条(provider 选 Outlook、鉴权选 OAuth2、密码留空),保存即生效;
    • 方式 A:在 config.tomlimapnotify.yaml 各加一个 Outlook 账号块(照 *.sample 的 Outlook 段:auth.type = "oauth2" / xoauth2: true,auth.cmdpasswordCMD 都调 oauth_token.py),重启 mail-apimail-watch
    • ⚠️ 邮箱地址必须填真实域名:个人号可能是 @live.com / @hotmail.com / @outlook.com 任一;域名写错会报 User is authenticated but not connected(OAuth 授权能成功、token 也有效,但 IMAP 按错地址找不到邮箱)。host 统一是 outlook.office365.com,只地址要对。

安卓 app 无需任何改动 —— Outlook 只是多一个账号。详细 Azure 步骤与排错见 server/README.md

详细说明见 server/README.mdandroid/README.md

版本控制

仓库根的 VERSION 文件是唯一版本号来源,server 和 app 构建时都从它读取,二者版本号保持一致。

  • 安卓 app:android/app/build.gradle.kts 在构建时读 VERSION → 自动设为 versionName,并由 x.y.z 推导出 versionCode(x*10000 + y*100 + z)。正常 ./gradlew assembleDebug / installDebug 即可,无需手改。
  • 服务端:版本经 Docker build-arg 注入镜像(环境变量 MAILPUSH_VERSION),并由 GET /versionGET /healthz 暴露。构建时带上 build-arg:
    # buildx 发布镜像
    docker buildx build --platform linux/amd64 \
        --build-arg APP_VERSION=$(cat ../VERSION) \
        -t <user>/mailpush:latest --push .
    # 本地 compose 构建
    export APP_VERSION=$(cat ../VERSION) && docker compose up -d --build
    (不传 build-arg 时默认 dev,不影响运行。)
  • app 设置页会显示当前 app 版本所连服务器的版本,方便核对两端是否一致。

升版本:改 VERSION 一个文件 → 重新构建 app 与镜像即可。

变更日志

0.3.3

  • 管理后台可视化管理邮箱账户(收发 + 推送):不再必须手写 config.toml / imapnotify.yaml
    • 新增 app/accounts.py:webui 账户存可写的 /data/accounts.json + /data/secrets/<name>.pass,overlay_config() 把它们叠加进 himalaya schema 供 mail-api 读(现读现生效);提供 provider 预设(Gmail/Outlook/Yahoo/网易/Fastmail),网页里直接设密码;
    • 新增 app/render_imapnotify.py:把 webui 账户与 /config 的手写 base 合并渲染成 /data/imapnotify.generated.yaml;watch-entrypoint.sh 监听 /data/watch.reload,改账户后自动重启 goimapnotify,零账户时空转等待不报错;
    • 每个账户可单独开关推送(只用 API 收发、不进推送监听);
    • 两种配置方式可并存:/config 始终只读、手写账户作为 base 兼容保留,同名时 webui 覆盖;可完全不写 config 文件、纯靠 webui 起服务;
    • imap_pool / mailsend / api.py 读配置处统一走 overlay 并容忍 config.toml 缺失;新增依赖 pyyaml

0.2.2

  • 接入 Outlook / Microsoft 365 / Hotmail(OAuth2 / XOAUTH2):微软停用密码登录后唯一可行路径。
    • 新增 app/oauth.py(MSAL 设备码授权 + 静默刷新,flock 防两容器并发)、oauth_token.py(输出裸 access token,供 config 的 auth.cmd / imapnotify 的 passwordCMD)、oauth_enroll.py(CLI 一次性授权);
    • imap_pool(收/读)与 mailsend(发信)增加 auth.type=oauth2 分支走 SASL XOAUTH2;监听端 goimapnotify 用 xoauth2: true 原生支持;
    • 内网管理后台新增「OAuth」区:填 client_id、点「授权」走设备码流(无需 SSH 进容器);凭据存可写的 /data/oauth/;
    • 现有密码账户零改动(Gmail/Yahoo/网易仍 auth.type=password);安卓端零改动。

0.2.0

  • 收信引擎从 himalaya(Rust CLI)切换为 Python imaplib 持久连接池(server/app/imap_pool.py + imap_client.py)。动机与收益:
    • 提速:连接复用,免去每次新建连接的握手开销(对会做反向 DNS、新连接要等数十秒的 IMAP 服务器尤其明显)。
    • 解锁网易 163/126 读信:在代码层登录后发 IMAP ID(RFC 2971),解决 himalaya v1.2.0 不支持 ID 导致的读信失败。
    • 构建更快更小:去掉 Rust 编译阶段,Docker 镜像构建从约 20 分钟降到约 1 分钟。
    • 零迁移:config.toml 等用户配置与 API 形状不变,无需改配置、安卓端无需改动。
  • 安卓端体验:
    • 统一收件箱并发拉取各账户(整轮耗时≈最慢账户,而非各账户之和);
    • 未读数:统一收件箱顶部总未读 + 账户列表各账户未读(新增 /unread/unread-all 端点);
    • 统一/单账户共享新鲜度:统一刷过的账户点进去不再重复拉;loadUnified 防并发 + 新鲜度跳过;
    • 双击底部「统一」图标回到列表顶部;根页连按两次返回彻底退出;
    • 「已发」文件夹探测失败可自愈(不缓存 null,下次重试)。
  • 内网管理后台(mail-admin)+ 多 app token:
    • 新增独立容器 mail-admin(默认端口 8098,仅限内网),密码登录后可新建/删除多个 app token(每台设备一个,丢设备直接吊销该 token,无需改配置/全量轮换),并显示版本与运行状态;
    • 推送设备与 app token 绑定:FCM 推送 token 记录其注册时所用的 app token;后台删 app token 会连带删除其名下推送设备 → 丢设备一键同时切断「读信」和「推送」(否则旧 FCM token 仍会把发件人/主题推到丢失设备);后台也可单删/清空推送设备;
    • require_auth 接受后台管理的多 token,兼容旧的单 /config/api-token;默认不再预设固定 token(走后台管理);
    • 关闭 FastAPI 自动文档(/docs/redoc/openapi.json),减少接口结构泄露;
    • app 端「API Token」输入框改为掩码显示。
  • himalaya 已从代码与镜像中移除。

0.1.0

  • 首个 beta:goimapnotify 监听 + himalaya 引擎 + smtplib 发信 + FCM 推送 + 安卓客户端。

安全

凭据(邮箱密码、token、Firebase 私钥、管理密码)只存服务器/本机,不进 Git(见 .gitignore)。

两个端口,安全要求完全不同,务必分清:

  • 🔒 API(mail-api,默认 8099)必须放在 HTTPS 之后。它能读全部邮件、以你名义发信,且 uvicorn 跑的是明文 HTTP——直接裸暴露公网会让 bearer token 和邮件内容被明文嗅探。用 Cloudflare Tunnel(提供 HTTPS + 出站连接)对外暴露,严禁把 8099 经路由器端口转发直连公网。token 用强随机值(后台生成的即是),走 HTTPS 传输。
  • 🚫 管理后台(mail-admin,默认 8098)只能内网访问,绝不可暴露公网。它能增删 token、看状态,是你的"钥匙串管理处"。不要给它配 Cloudflare Tunnel、不要在路由器上转发 8098。只想本机访问可在 compose 把端口写成 127.0.0.1:8098:8098

多 token + 吊销:给每台设备建独立 token;设备丢失时内网登录后台删除该 token 即可(即时生效,不影响其他设备),无需全量轮换。 注意:后台仅内网 → 人在外面丢手机需回到局域网(或 VPN 回家)才能吊销。

状态

服务端 + 安卓主体功能均已实现并跑通(本地编译 / 真机验证)。

致谢

本项目站在这两个优秀开源项目的肩膀上,在此致谢:

  • goimapnotify —— 纯 RFC 2177 IMAP IDLE 监听,新邮件实时触发;兼容 Gmail,无 QRESYNC 依赖。本项目用它做推送监听端。
  • himalaya(Pimalaya)—— 命令行 IMAP/SMTP 客户端。本项目 0.1.x 的邮件引擎(读列表 / 读正文 / 列附件 / 生成回复转发模板);0.2.0 起改用 Python imaplib 自实现连接池,不再依赖 himalaya。感谢它在早期阶段让项目快速跑通。

感谢以上项目的作者与维护者。

Star History

Star History Chart

About

selfhosted android FCM email push solution

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages