“Weave”寓意将邮件、AI 智能体、即时通讯平台、自定义钩子编织成一张统一的信息网络。名称简洁、国际化,且与项目“连接一切通讯渠道”的核心理念高度契合。|作者个人站:https://softwarescience.top/
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 11 天前 | ||
| 11 天前 | ||
| 15 天前 | ||
| 15 天前 | ||
| 11 天前 | ||
| 15 天前 | ||
| 15 天前 | ||
| 11 天前 | ||
| 15 天前 | ||
| 11 天前 | ||
| 15 天前 | ||
| 15 天前 | ||
| 15 天前 |
MailWeave
作者个人站:https://softwarescience.top/ · 本项目主页:https://softwarescience.top/projects/mailweave(版本、截图与下载都在那儿)
“Weave”寓意将邮件、AI 智能体、即时通讯平台、自定义钩子编织成一张统一的信息网络。名称简洁、国际化,且与项目“连接一切通讯渠道”的核心理念高度契合。
备选名称:
- PostPilot —— 强调 AI 作为邮件副驾驶的角色
- MailNexus —— 强调多平台连接枢纽的定位
- Kotlin MailHub —— 直白体现技术栈与枢纽属性
二、整体技术架构
2.1 技术栈总览
| 层次 | 技术选型 | 说明 |
|---|---|---|
| 跨端 UI | Compose Multiplatform | 一套 Compose UI 代码运行于 Android、iOS、Desktop、Web |
| 鸿蒙适配 | MYKMP / ovCompose | 补充 HarmonyOS 的 Kotlin/Native target 支持 |
| 业务逻辑 | Kotlin Multiplatform | 共享 ViewModel、Repository、UseCase 层 |
| AI 智能体 | Koog + LangChain4j | Koog 负责 Agent 编排与工具调用,LangChain4j 负责 LLM 接入与 RAG |
| 邮件协议 | Jakarta Mail / Kourrier | IMAP/SMTP/POP3 协议栈 |
| 本地存储 | SQLDelight | 跨平台 SQLite 访问 |
| 网络层 | Ktor Client | HTTP/WebSocket 通信 |
| 序列化 | kotlinx.serialization | JSON 处理 |
2.2 分层架构
┌─────────────────────────────────────────────────────┐
│ UI Layer (Compose MP) │
│ MailList │ MailDetail │ Compose │ Settings │ AI Chat │
├─────────────────────────────────────────────────────┤
│ Presentation Layer (KMP) │
│ ViewModels │ UI State │ Navigation │
├─────────────────────────────────────────────────────┤
│ Domain Layer (KMP) │
│ UseCases │ Models │ Repository Interfaces │
├─────────────────────────────────────────────────────┤
│ Data Layer (KMP + Platform) │
│ MailRepository │ AI Agent Service │ IM Bridge │
│ SQLDelight DAO │ Ktor Client │ Token Store │
├─────────────────────────────────────────────────────┤
│ Platform Layer (expect/actual) │
│ Android │ iOS │ Desktop │ Web │ HarmonyOS │
└─────────────────────────────────────────────────────┘
架构要点:UI 层使用 Compose Multiplatform 实现最大程度的代码复用,业务逻辑层完全共享。平台差异通过 expect/actual 机制处理,例如文件系统访问、通知推送、后台同步策略等。鸿蒙端需要补充 ohos_arm64 编译目标,并将共享 Kotlin 代码编译为动态库接入现有工程,Compose UI 经 Skia 绘制到 XComponent。
三、AI 智能体编排设计
3.1 Koog Agent 架构
Koog 是基于 Kotlin 的 AI Agent 框架,目标平台覆盖 JVM、Android、iOS、WebAssembly 和浏览器应用,擅长图策略编排,支持循环、分支、回退和并行分支执行。在本项目中,Koog 承担以下角色:
- 邮件分诊 Agent:自动分类邮件(重要/普通/垃圾/待跟进),提取关键信息
- 智能回复 Agent:基于上下文生成草稿回复,支持多轮对话
- 日程解析 Agent:从邮件中识别会议邀请、截止日期,自动创建日历事件
- 跨平台通知 Agent:判断哪些邮件需要推送到飞书/钉钉/微信
Koog 的 Agent 本质上是结构化的类型安全状态机,核心概念包括 Prompt Executor、工具节点、记忆存储等。Agent 策略以图的形式定义:
val mailTriageStrategy = strategy("mail-triage") {
val analyze = node<String, MailAnalysis>("analyze") { input ->
llmClient.execute(input.prompt)
}
val classify = node<MailAnalysis, MailCategory>("classify") { analysis ->
when {
analysis.hasActionItem -> MailCategory.ACTION_REQUIRED
analysis.isUrgent -> MailCategory.URGENT
else -> MailCategory.NORMAL
}
}
val notify = node<MailCategory, Unit>("notify") { category ->
if (category == MailCategory.URGENT) {
imBridge.broadcast(NotificationPayload(...))
}
}
analyze then classify then notify
}
3.2 工具注册与 MCP 集成
Koog 支持将任意 Kotlin 函数转化为 AI Agent 可调用的工具,支持串行和并行执行。同时,Koog 原生支持 MCP 集成,可通过 Kotlin MCP SDK 连接任何 MCP 服务器。
本项目中,将邮件操作为 MCP Tool 暴露给 Agent:
| 工具名 | 功能 | 对应实现 |
|---|---|---|
searchEmails |
全文搜索邮件 | SQLDelight FTS |
sendEmail |
发送邮件 | SMTP Client |
draftReply |
生成回复草稿 | LangChain4j LLM |
addLabel |
添加标签 | 本地数据库 |
forwardToIM |
转发到 IM 平台 | IM Bridge |
createCalendarEvent |
创建日历事件 | Calendar API |
3.3 A2A 跨 Agent 通信
Koog 从 0.5.0 版本开始集成 A2A 协议,使多个 Agent 能够通过标准化协议直接通信。在本项目中,邮件 Agent 可以将任务委托给日程 Agent、通知 Agent 等,实现插拔式的 Agent 协作。
3.4 LangChain4j 集成
LangChain4j 提供统一的 LLM 抽象层,原生支持 RAG、Agent、工具调用等高级功能,并且对 Kotlin 有专门的扩展支持,包括协程和挂起函数,以及类型安全构建器。
LangChain4j 在本项目中承担:
- LLM 统一接入:支持 OpenAI、Claude、通义千问等模型的无缝切换
- RAG 管道:将历史邮件作为知识库,为 AI 提供上下文增强
- 记忆管理:维护多轮对话的 ChatMemory
四、跨平台方案
4.1 平台覆盖策略
Kotlin Multiplatform 已处于稳定阶段,Google 正式支持其在 Android 和 iOS 之间共享业务逻辑。Compose Multiplatform 允许跨平台共享 UI 代码,包括 iOS、Android、桌面和 Web。
| 平台 | UI 方案 | 业务逻辑 | 备注 |
|---|---|---|---|
| Android | Compose MP | KMP 共享 | 原生 target |
| iOS | Compose MP | KMP 共享 | 原生 target,Kotlin/Native |
| Desktop (Win/Mac/Linux) | Compose MP | KMP 共享 | JVM target |
| Web | Compose MP (Wasm) | KMP 共享 | Kotlin/Wasm |
| HarmonyOS | Compose MP + MYKMP | KMP 共享 | 需 ohos_arm64 target |
4.2 鸿蒙适配方案
目前 JetBrains 或华为官方尚未提供 Compose Multiplatform 到鸿蒙的直接迁移文档,需要基于双方技术特性手动适配。可选方案包括:
方案 A:MYKMP(支付宝开源) 支付宝已正式开源基于 KMP + Compose Multiplatform 的三端方案 MYKMP,业务逻辑与 Compose UI 一次编写即可在 Android、iOS、HarmonyOS 三端复用。HarmonyOS 作为 Kotlin/Native target 参与多平台构建,共享 Kotlin 代码编译为动态库接入现有工程。
方案 B:ovCompose(腾讯开源) 腾讯视频团队基于 Compose Multiplatform 生态推出的跨平台框架,已在腾讯视频鸿蒙平台全面应用,是首个实现全跨端覆盖的 App。
建议:优先采用 MYKMP 方案,因其直接开源了 ohos_arm64 编译目标及完整的平台互操作定义,社区活跃度较高。
五、多平台 IM 集成设计
5.1 统一消息桥接层(IM Bridge)
设计一个统一的 IMBridge 抽象层,通过适配器模式对接各 IM 平台。核心接口定义:
interface IMChannel {
val channelId: String
suspend fun sendMessage(target: String, message: IMMessage): Result<Unit>
suspend fun onMessageReceived(handler: suspend (IncomingMessage) -> Unit)
suspend fun validateCredentials(): Boolean
}
// 适配器实现
class FeishuChannel : IMChannel { ... }
class DingTalkChannel : IMChannel { ... }
class WeComChannel : IMChannel { ... }
class CustomWebhookChannel : IMChannel { ... }
5.2 各平台集成要点
飞书:支持自定义机器人 Webhook 推送(https://open.feishu.cn/open-apis/bot/v2/hook/xxx),也支持机器人应用通过事件订阅接收消息。自定义机器人无需管理员审核,适合快速接入;机器人应用支持群管理、用户信息获取等完整能力。频率限制为单租户单机器人 100 次/分钟,消息体不超过 20KB。
钉钉:支持通过接口发送消息和通过 Webhook 发送消息两种方式。钉钉 Stream Mode 提供持久 WebSocket 连接用于接收机器人消息,并通过 Webhook/OAPI 发送回复,推荐使用 Stream Mode 以避免回调 URL 的配置复杂性。
企业微信:通过加密回调机制连接 Agent,需配置企业 ID、Token 和 EncodingAESKey,接收回调消息后通过 /cgi-bin/message/send 接口发送回复。需要域名主体与企业微信认证主体一致。
自定义通讯 App / 钩子函数:设计一个通用的 WebhookChannel,允许用户配置任意 HTTP 端点作为消息接收和发送通道。同时提供 Kotlin DSL 让开发者编写自定义钩子:
class CustomHookChannel : IMChannel {
override val channelId = "custom-hook"
override suspend fun sendMessage(target: String, message: IMMessage): Result<Unit> {
return httpClient.post(target) {
setBody(message.toJson())
}.toResult()
}
}
5.3 消息路由规则
AI Agent 根据邮件内容和用户预设规则,决定将哪些邮件通知路由到哪些 IM 平台:
| 触发条件 | 路由目标 | 消息格式 |
|---|---|---|
| 标记为“紧急” | 飞书 + 钉钉 | 交互式卡片 |
| 含会议邀请 | 企业微信 | 文本 + 日历按钮 |
| 来自 VIP 发件人 | 所有渠道 | 富文本摘要 |
| 用户自定义规则 | 自定义 Webhook | JSON 原始数据 |
六、邮件协议与数据层设计
6.1 邮件协议栈
| 协议 | 用途 | 推荐库 |
|---|---|---|
| IMAP | 接收邮件、文件夹同步 | Jakarta Mail / Kourrier |
| SMTP | 发送邮件 | Jakarta Mail |
| POP3 | 旧邮箱兼容 | Jakarta Mail |
| JMAP | 现代邮件协议(可选) | 自建 |
Kotlin 生态中,Kourrier 是 JavaMail API 的 Kotlin 封装库,支持 IMAP、SMTP 等协议。也可使用 SimpleKotlinMail,这是基于协程的 Kotlin 邮件 API,支持客户端和服务端场景。
6.2 本地存储设计
选用 SQLDelight 作为跨平台数据库方案,相比 Room KMP 在 KMP 场景下更成熟,性能表现更好。
核心数据表设计:
-- 账户表
CREATE TABLE account (
id TEXT PRIMARY KEY,
email TEXT NOT NULL,
display_name TEXT,
imap_host TEXT, imap_port INTEGER,
smtp_host TEXT, smtp_port INTEGER,
auth_type TEXT, -- PASSWORD / OAUTH2
created_at INTEGER
);
-- 邮件表
CREATE TABLE email (
id TEXT PRIMARY KEY,
account_id TEXT NOT NULL,
folder TEXT NOT NULL,
uid INTEGER NOT NULL,
subject TEXT,
from_address TEXT,
to_addresses TEXT,
date INTEGER,
body_preview TEXT,
body_html TEXT,
has_attachments INTEGER DEFAULT 0,
is_read INTEGER DEFAULT 0,
is_flagged INTEGER DEFAULT 0,
ai_category TEXT, -- URGENT / ACTION / NORMAL / SPAM
ai_summary TEXT
);
-- AI 分析结果
CREATE TABLE ai_analysis (
email_id TEXT PRIMARY KEY,
category TEXT,
summary TEXT,
action_items TEXT, -- JSON array
sentiment REAL,
analyzed_at INTEGER
);
-- IM 通知记录
CREATE TABLE im_notification (
id TEXT PRIMARY KEY,
email_id TEXT,
channel TEXT, -- FEISHU / DINGTALK / WECOM / CUSTOM
status TEXT, -- PENDING / SENT / FAILED
sent_at INTEGER
);
6.3 OAuth2 认证
现代邮件服务(Gmail、Outlook 等)已逐步弃用密码认证,需支持 OAuth2。可选用 KMPAuth 等跨平台认证库,支持 Sign In with Google、Microsoft 等 OAuth Provider。
七、核心模块划分
mailweave/
├── shared/ # KMP 共享模块
│ ├── core/
│ │ ├── model/ # 数据模型
│ │ ├── repository/ # 仓库接口
│ │ └── util/ # 工具类
│ ├── mail/ # 邮件协议模块
│ │ ├── imap/ # IMAP 客户端
│ │ ├── smtp/ # SMTP 客户端
│ │ └── parser/ # MIME 解析
│ ├── ai/ # AI 智能体模块
│ │ ├── agent/ # Koog Agent 定义
│ │ ├── llm/ # LangChain4j 集成
│ │ ├── rag/ # RAG 管道
│ │ └── tools/ # MCP Tool 注册
│ ├── im/ # IM 桥接模块
│ │ ├── bridge/ # 统一接口
│ │ ├── feishu/ # 飞书适配器
│ │ ├── dingtalk/ # 钉钉适配器
│ │ ├── wecom/ # 企业微信适配器
│ │ └── custom/ # 自定义 Webhook
│ ├── storage/ # 数据存储
│ │ └── sqldelight/ # SQLDelight DAO
│ └── sync/ # 同步引擎
│ ├── engine/ # 同步调度
│ └── conflict/ # 冲突解决
├── ui/ # Compose Multiplatform UI
│ ├── mail-list/ # 邮件列表
│ ├── mail-detail/ # 邮件详情
│ ├── compose/ # 撰写邮件
│ ├── ai-chat/ # AI 对话界面
│ ├── settings/ # 设置
│ └── theme/ # 主题
├── platform/ # 平台特定实现
│ ├── android/
│ ├── ios/
│ ├── desktop/
│ ├── web/
│ └── harmony/ # 鸿蒙适配
└── server/ # 可选:后端服务
├── sync-gateway/ # 多设备同步网关
└── push-service/ # 推送服务
八、建议开发路线
Phase 1:核心邮件功能(4-6 周)
- KMP 项目脚手架搭建
- IMAP/SMTP 协议栈接入
- SQLDelight 数据层
- Compose Multiplatform UI 基础框架
- 邮件列表、详情、撰写三个核心页面
Phase 2:AI 能力集成(4-6 周)
- LangChain4j 接入,配置 LLM Provider
- Koog Agent 基础框架搭建
- 邮件分诊 Agent + 智能回复 Agent
- MCP Tool 注册
- AI 对话界面
Phase 3:多平台 IM 集成(3-4 周)
- IMBridge 统一抽象层
- 飞书/钉钉/企业微信适配器
- 自定义 Webhook 通道
- 消息路由规则引擎
Phase 4:全平台适配(4-6 周)
- iOS 平台适配与测试
- Desktop 平台适配
- Web (Wasm) 平台适配
- 鸿蒙适配(基于 MYKMP 或 ovCompose)
- 各平台通知推送集成
Phase 5:高级功能与优化(持续)
- 多设备同步引擎
- 离线优先架构优化
- RAG 知识库构建
- A2A 多 Agent 协作
- 性能优化与内存管理
九、关键技术风险与应对
| 风险 | 影响 | 应对策略 |
|---|---|---|
| Compose MP 鸿蒙适配不成熟 | 鸿蒙端开发受阻 | 优先采用 MYKMP,或先以 Android 端验证,鸿蒙端通过 WebView 桥接过渡 |
| Koog 框架较新,API 可能变化 | 开发成本增加 | 封装一层抽象接口,隔离框架变更影响 |
| 多 IM 平台 API 差异大 | 适配工作量大 | 统一抽象层 + 适配器模式,分阶段接入 |
| 邮件协议兼容性 | 部分邮箱无法连接 | 支持 OAuth2 + 应用密码双模式,充分测试主流邮箱 |
| AI 推理成本 | 运营成本高 | 支持本地模型(Ollama) + 云端模型混合模式 |
十、项目核心创新点
- 全 Kotlin 技术栈统一:从 UI 到 AI Agent 到邮件协议,全部使用 Kotlin 编写,通过 KMP 实现真正的全平台覆盖
- AI 原生邮件管理:不是简单地在邮件客户端上叠加 AI,而是将 Koog Agent 作为核心编排引擎
- 可编程的 IM 桥接:用户可以通过钩子函数和自定义 Webhook 连接任意通讯平台
- MCP 生态兼容:通过 MCP 协议,第三方开发者可以为 MailWeave 编写扩展工具
- 离线优先 + 多设备同步:本地数据优先,支持多设备通过自建同步网关保持一致
十一、实现状态(Desktop 端已可用)
本节描述仓库里已经跑起来的东西,与上文的目标架构对照阅读。上文是设计蓝图,本节是当前代码的真实边界。
11.1 技术栈落地版本
| 层次 | 设计选型 | 实际使用 |
|---|---|---|
| 跨端 UI | Compose Multiplatform | Compose Multiplatform 1.12.1(当前只启用 Desktop/JVM target) |
| 业务逻辑 | Kotlin Multiplatform | Kotlin 2.4.21,共享代码放在 shared/src/commonMain |
| 邮件协议 | Jakarta Mail | Eclipse Angus Mail 2.0.5(IMAP/SMTP,实现位于 jvmMain) |
| 本地存储 | SQLDelight | SQLDelight 2.4.1 + SQLite JDBC |
| 网络层 | Ktor Client | Ktor 3.6.0(Client: IM/LLM 出站;Server: 本机入站钩子) |
| 序列化 | kotlinx.serialization | 1.11.0 |
| AI 接入 | LangChain4j | LangChain4j 1.22.0(OpenAI 兼容 + Ollama 两种 ChatModel) |
| Agent 编排 | Koog | 暂未引入依赖(镜像站解析不到 Koog 1.3.0 产物)。MailAgent 抽象已就位,当前实现为 LangChain4j 直连 + 自研工具循环,契约见 11.5 |
| 协程 | — | kotlinx.coroutines 1.11.0 |
11.2 目录结构
shared/ KMP 共享模块(commonMain + jvmMain)
commonMain/kotlin/com/mailweave/
core/model/ MailAccount / EmailMessage / MailFolder / AI 与 IM 模型 / AppSettings
core/repository/ AccountRepository / MailRepository / ImRepository / SettingsRepository
core/credential/ CredentialVault(凭据库,独立于数据库)
mail/ MailGateway 契约、MailSender、HtmlTextExtractor、CalendarExporter(expect)
storage/ SQLDelight 仓库实现 + 行映射 + 布尔↔整数适配
ai/ LlmClient 抽象、MailPrompts、MailAnalysisParser、MailAgent、
ReplyAgent / ScheduleAgent / ChatAgent、MailTool 注册表(6 个工具)
im/ IMChannel 抽象、飞书/钉钉/企微/自定义 Webhook 适配器、
RoutingRules、NotificationRouter、ImCommandHandler
sync/ MailSyncEngine(增量同步 + 按需拉正文 + 回写已读)
commonMain/sqldelight/ 8 张表:account / folder / email / ai_analysis /
im_notification / im_channel / routing_rule / app_setting
jvmMain/kotlin/com/mailweave/
mail/ JakartaMailGateway(IMAP 抓取、MIME 解析、SMTP 发送、\Seen 回写)、IcsBuilder
ai/ LangChain4jLlmClient(含工具调用循环)
im/ InboundWebhookServer(本机 HTTP 钩子)
platform/ 文件系统、HMAC 签名、时间格式化、文件选择框
app/AppContainer 组合根(数据库 + 凭据库 + 仓库 + 网关 + AI + IM 的装配)
composeApp/ Compose UI
commonMain/kotlin/com/mailweave/ui/
AppViewModel.kt 单一状态容器(StateFlow + Flow 订阅)
MailWeaveApp.kt 顶栏 / 导航 / 状态栏 / 写信面板挂载
screens/ MailScreen(三栏)、ChatScreen、SettingsScreen、ComposeMailPanel
components/Widgets.kt 统一组件(卡片、字段、枚举选择、分类徽标、邮件行)
jvmMain/kotlin/com/mailweave/Main.kt 桌面入口(含 `-Dmailweave.smoke` / `-Dmailweave.screenshot` 开关)
11.3 构建、运行与打包
# 本机 JAVA_HOME 默认指向 JDK 25,Kotlin/Compose 未验证;先切到 JDK 21
export JAVA_HOME="D:/Develop/runtimes/Javas/jdk-21.0.11-full"
./gradlew :composeApp:run # 启动桌面端
./gradlew test # 全部测试(含 GreenMail 端到端)
./gradlew :composeApp:packageDistributionForCurrentOS # 生成 Windows msi 安装包
./gradlew :composeApp:run -Pmailweave.smoke=true # 冒烟:启动后自动退出并打印 MAILWEAVE_SMOKE_OK
依赖仓库固定走国内镜像(settings.gradle.kts 中的 Google/Central 镜像),首次构建需要下载较久。
11.4 已实现能力(可直接在桌面端使用)
| 能力 | 说明 |
|---|---|
| 多账号管理 | IMAP/SMTP 主机、端口、SSL/STARTTLS、密码或 XOAUTH2 令牌;内置 QQ/163/阿里云/Gmail/Outlook/飞书预设,可按域名自动猜测 |
| 连接自检 | 「测试连接」同时验证 IMAP 与 SMTP,分别报告失败原因 |
| 增量同步 | 按 IMAP UID 游标拉取最新邮件(默认每文件夹 50 封),文件夹未读数/总数落库;单文件夹同步与全量同步并存 |
| 邮件阅读 | 三栏布局;列表显示发件人/主题/摘要/分类/标签/附件标记;打开时按需下载正文:HTML 邮件按 HTML 排版渲染(标题/列表/表格/加粗/斜体/引用/链接可点),只有纯文本时才按纯文本显示,两者都有时可一键切换;正文里声明的 charset 与实际字节不符(国内邮件常见)也能纠正解码 |
| 附件预览 | 正文下方列出全部附件;左键或右键点开菜单,用系统默认程序打开(先下载到 <tmp>/mailweave-attachments,随看随下,不占本地库) |
| 检索 | 本地缓存全文检索(主题/发件人/预览/正文/AI 摘要/标签) |
| 写信与发送 | 收件人/抄送/密送、附件(系统文件选择框)、签名;SMTP 发送;依赖服务器 Sent 文件夹保留副本 |
| 标记与标签 | 已读/未读(回写 IMAP \Seen)、星标、本地标签 |
| AI 分诊 | 分类(紧急/待处理/普通/垃圾)+ 摘要 + 待办 + 情感 + 是否需要回复 + 日程线索,结果落库并在列表/详情展示;支持批量补跑未分诊邮件 |
| AI 回复草稿 | 结合来信上下文生成草稿并填入写信面板,发送前人工确认 |
| AI 日程抽取 | 从邮件抽取会议/截止时间,可导出 .ics(createCalendarEvent 工具) |
| AI 对话 + 工具调用 | Agent 循环可调用 6 个工具:searchEmails / sendEmail / draftReply / addLabel / forwardToIM / createCalendarEvent |
| IM 通知桥接 | 飞书(文本 + 交互式卡片 + 加签)、钉钉(Markdown/文本 + 加签)、企业微信(access_token + 应用消息)、自定义 Webhook(模板 + 自定义请求头);一键测试发送 |
| 路由规则 | 条件类型:AI 分类 / VIP 发件人 / 会议邀请 / 主题关键字 / 发件人关键字 / 始终;按优先级匹配,同一邮件同一渠道幂等只推一次;失败原因落库可查 |
| 入站钩子 | 本机 POST /hooks/{通道ID} 执行命令:/search、/recent、/summary、/notify、/status,兼容飞书/钉钉事件文本字段 |
| 本地模型支持 | Ollama 直连(默认 http://localhost:11434),无云端 API Key 也能跑全流程 |
11.5 已知边界与未实现项(明确声明,避免误解)
- 平台:只启用 Desktop(JVM)。Android/iOS/Web/鸿蒙 target 未添加(需要各自 SDK 与主机环境);共享代码已按
expect/actual组织,可增量启用。 - POP3:可用,但只有单一收件箱、无服务器端标志位(已读/星标写不回去),UID 由 UIDL 派生;由 Jakarta 引擎承载(自研协议栈暂无 POP3,切到内置栈时 POP3 账号会自动回落到 Jakarta)。
- OAuth2 的边界:授权流程本身已打通(细节见 §16),但应用不带任何内置客户端凭据——需要你自己注册应用、把客户端 ID(Google 还要客户端密钥)填进设置;另外 Google 的授权端点(
accounts.google.com/oauth2.googleapis.com)在境内网络不可直连,需要自备代理。 - Koog:
MailAgent(分诊)与ChatAgent(工具循环)抽象、提示词、6 个工具清单都已就位,但 Koog 依赖尚未引入——本次构建环境中华为云/阿里云镜像均无法解析ai.koog:koog-agents:1.3.0的实现 jar(只有 POM/metadata)。因此当前运行的是 LangChain4j + 自研工具循环实现,Koog 图策略可作为MailAgent的另一种实现增量接入,业务代码无需改动。 - 日历:只做抽取与
.ics导出,不写入系统日历、不调用 Google/Outlook Calendar API。 - IM 入站:仅自定义通道提供本机 HTTP 入站;飞书/钉钉/企微的收消息(事件订阅 / Stream Mode)未实现,三者当前为出站通知。
- 发送副本:不在本地伪造「已发送」,依赖服务器自动归档;IMAP APPEND 到 Sent 未实现(避免与服务器重复归档冲突)。
- 凭据存储:明文 JSON 存于
<用户目录>/.mailweave/vault.json(不进仓库、不进日志);系统钥匙串(Windows Credential Manager 等)属于后续工作。 - 同步调度:
syncIntervalMinutes已落库并在设置页可配,但后台定时器尚未接入,目前由「启动时同步」与手动同步触发。
11.6 验证方式
shared/src/jvmTest:153 个测试(3 个LiveMailTest需要真实账号环境变量,未提供时跳过),其中JakartaMailGatewayTest/NativeAttachmentTest/Pop3AndPresetTest用 GreenMail 起真实 IMAP/POP3/SMTP 端口,覆盖文件夹列举、增量抓取、正文/附件解析与按需下载、SMTP 发送、\Seen回写、连接自检失败路径、charset 错标纠正;其余覆盖数据层(含重复同步不丢 AI 结果与标签)、路由匹配、通知幂等与失败记录、IM 协议报文(飞书/钉钉/企微/webhook,MockEngine)、入站钩子协议、同步引擎(假网关)、HTML 转文本、OAuth2 两条授权流程、ICS 生成。composeApp/src/jvmTest:15 个测试,AppRenderTest用ImageComposeScene离屏渲染真实 UI(邮件/设置/对话/写信/账号/提醒/数据/OAuth 配置),断言渲染尺寸与画面内容复杂度,并把 PNG 输出到composeApp/build/screenshots/;HtmlMailViewTest覆盖 HTML 解析与渲染;AppEndToEndTest跑通收信→落库→分诊→推送→提醒→读正文→发信全链路。- 桌面冒烟:
:composeApp:run -Pmailweave.smoke=true真实启动窗口与数据库,打印MAILWEAVE_SMOKE_OK。
十二、第二次迭代:协议补齐 / 微软邮箱 / 提醒 / 数据迁移 / UI 重做
12.1 收信协议:IMAP + POP3 + SMTP
| 协议 | 能力 | 说明 |
|---|---|---|
| IMAP | 多文件夹、UID 增量、服务器已读回写(\Seen) |
默认协议,推荐 |
| POP3 | 单收件箱、UIDL 稳定标识、按需拉正文 | 老邮箱兜底;协议本身无标志位,已读只存在本地 |
| SMTP | SSL / STARTTLS、附件、XOAUTH2 | 发送副本由服务器自动归档 |
网易系(163/126/yeah.net/网易企业邮)实测坑:SMTP/IMAP 登录能过,但一开文件夹就被拒:
C4 NO EXAMINE Unsafe Login. Please contact kefu@188.com for help。
原因是没有先发 IMAP ID 命令。现在账号带 imapIdRequired 开关,网易系预设默认打开,连接后会先
IMAPStore.id(name/version/vendor/support-email) 再操作文件夹。
12.2 微软邮箱与 OAuth2(回答“OAuth2 是什么问题”)
微软已停用 IMAP/SMTP 的密码认证,必须用 OAuth2。之前的实现只能“手工粘贴一个 access token”, 问题在于:token 一小时就过期,没有刷新能力,也不能帮用户走授权流程——等于只有半个 OAuth2。 本次补齐为完整的 RFC 8628 设备码流程:
POST /devicecode拿user_code+verification_uri,界面显示“在浏览器输入这段验证码”;- 按
interval轮询/token,正确处理authorization_pending/slow_down/access_denied/expired_token; - 成功后把 refresh_token 存进本机凭据库,access token 到期前 5 分钟自动刷新;
- 连接 IMAP/SMTP 时用 XOAUTH2(
mail.<protocol>.auth.mechanisms=XOAUTH2,access token 当密码用)。
需要你自己注册一个公共客户端 ID(免费、两分钟):Azure 门户 → 应用注册 → 移动和桌面应用 →
允许公共客户端流 → 委托权限 IMAP.AccessAsUser.All、SMTP.Send,把客户端 ID 填进「设置 → 账号 → OAuth2」。
Google 同理(https://mail.google.com/)。刷新令牌与密码一样只存本机 vault.json。
12.3 内置邮箱预设(个人 + 企业)与自定义预设
内置预设已覆盖:网易(163 / 126 / yeah.net / 网易企业邮 qiye.163.com)、腾讯(QQ 邮箱 / Foxmail / 腾讯企业邮 exmail.qq.com)、华为云企业邮箱、阿里云邮箱 / 阿里企业邮、飞书邮箱、 Microsoft 365 / Outlook.com、Gmail,每项都带 IMAP + POP3 + SMTP 三套参数与加密方式。 「设置 → 账号」里选中预设即可一键填好;预设置信度由代码注释标明来源,核实不到的会显式标记为需要手填。
任何自建/公司邮箱也能用:手填服务器后点「保存预设」,会作为自定义预设持久化并出现在预设行里,
下次直接复用(AppSettings.customPresets)。输入邮箱地址时也会按域名自动套用能猜到的预设。
12.4 提醒:不写系统日历,改为到点提醒
AI 分诊抽到会议/截止时间后,会按「提前 N 分钟」落成本地提醒;邮件详情里也有「提醒我」快捷项 (1 小时后 / 3 小时后 / 明天 9:00 / 下周一 9:00)。到点由后台调度弹系统通知(Windows 托盘气泡), 可选同时推 IM。提醒页集中展示“已到期 / 即将到来 / 已结束”,可一键完成、忽略、删除、跳回原邮件。
12.5 后台同步(回答“能不能用 webdev 之类的同步”)
当前实现是应用内定时器(SyncScheduler,每 30 秒醒一次,按 syncIntervalMinutes 给每个账号的收件箱做增量同步),
关掉应用就停。可选路线与取舍:
| 方案 | 实时性 | 代价 |
|---|---|---|
| 应用内定时器(已实现) | 分钟级 | 需要开着应用 |
| IMAP IDLE 长连接(建议下一步) | 秒级 | 每账号一条常驻连接,需处理断线重连,服务端有并发限制 |
headless 守护进程(同 jar 加 --daemon) |
分钟级 | 不需要界面,但要做成服务/开机项 |
| 外部 web 服务 + 本机钩子 | 取决于对方 | 桌面端已提供 POST /hooks/{通道ID} 入站口,外部系统可主动触发 search/recent/summary/notify/status |
结论:不需要另做一个 web 端同步服务也能满足“后台同步”;真要秒级实时,正确做法是 IMAP IDLE 而不是轮询 web。
12.6 数据存放位置与迁移
- 数据目录默认
~/.mailweave(mailweave.db+vault.json+calendar/)。 - 「设置 → 数据存储」可改到任意位置,提供三种动作:迁移(搬走,清源)/ 复制(保留源,便于备份)/ 切换到已有目录。
- 迁移前会先关闭数据库连接(避免拷到半写状态),全部复制成功后才写指针与清理源目录;失败则保持原状。
- 引导目录里只放一个
location.json指针,所以换到别处后仍能在原处找到新位置,重启后生效(界面会给出重启提示与退出按钮)。
12.7 UI/UX 重做
视觉:统一设计令牌(配色 / 间距刻度 / 圆角 / 字阶),左侧导航栏 + 顶部操作区 + 底部状态条的三段式外壳; 邮件三栏(文件夹 / 列表 / 阅读),列表带头像色块、未读圆点、分类胶囊、标签与星标,悬停/选中都有反馈; 设置改成分页签(账号 / AI 模型 / IM 渠道 / 路由规则 / 同步与提醒 / 数据存储 / 关于)。
UX(这次特别补的部分):
- 键盘快捷键:
Ctrl+N写信、Ctrl+R/F5同步、Ctrl+F/Ctrl+K聚焦搜索、Ctrl+Enter发送、Esc关闭写信/弹窗、Ctrl+1..4切换页面;列表里J/K上下移动(自动滚动到可见)、Enter打开、U切换已读、S切换星标。 - 危险操作二次确认:删除账号/渠道/规则/提醒都会弹确认,并说明影响范围(“服务器上的邮件不受影响”)。
- 记住上次位置:账号与文件夹选择持久化,重开后回到原处;但不会因此触发自动同步,避免突然跑流量。
- 输入即引导:输入邮箱地址自动套用能猜到的预设;缺少凭据、模型未配置等情况都给可点击的补救入口。
- 就近反馈:连接测试结果显示在表单内,操作结果与错误集中在底部状态条且可一键关闭;发送失败保留草稿不丢内容。
- 空态可行动:无账号时直接给“去添加账号”,有账号无邮件时给“同步”按钮。
12.8 包路径
源码包根改为 top.softwarescience.mailweave(Gradle group 同为 top.softwarescience),
即 shared/src/commonMain/kotlin/top/softwarescience/mailweave/...。
12.9 真机验证(用真实 163 账号)
shared/src/jvmTest/kotlin/.../live/LiveMailTest.kt 会走真实的 IMAP/POP3/SMTP(含发一封自检邮件),
凭据只从环境变量读取,未设置时显式 skip:
MAILWEAVE_LIVE_EMAIL=xxx@163.com MAILWEAVE_LIVE_PASSWORD=授权码 \
gradle :shared:jvmTest --tests "top.softwarescience.mailweave.live.LiveMailTest"
十三、平台路线图(Roadmap)
13.1 现状与总览
| 平台 | 当前状态 | 可复用的部分 | 主要缺口 |
|---|---|---|---|
| Windows / macOS / Linux(Desktop) | ✅ 已可用:Windows 已验证运行与打包;macOS/Linux 同一套 JVM 代码与 AWT 实现,配置已就位 | 全部 | 需在对应系统上出包并做一轮人工验证 |
| Android | ⛔ 未启用 | commonMain 的模型/仓库/AI/IM/同步引擎、整套 Compose UI、SQLDelight schema |
邮件协议栈(Jakarta Mail 是 JVM 专属)、Android 端 actual 实现、通知与文件访问 |
| iOS | ⛔ 未启用 | 同上 | 需要 macOS 主机 + Xcode;邮件协议栈;Kotlin/Native target 与 actual 实现 |
| HarmonyOS | ⛔ 未启用 | 同上 | 需要 MYKMP / ovCompose 提供 ohos_arm64 target;邮件协议栈 |
13.2 关键决策:移动端的邮件协议栈怎么选(必须先定)
Jakarta Mail / Angus Mail、LangChain4j 都是 JVM 专属,这是移动端最大的阻塞点。三条路线:
| 方案 | 做法 | 优点 | 代价 |
|---|---|---|---|
| A. 自研 KMP 原生协议栈 | 用 Ktor Socket 在 commonMain 写 IMAP/SMTP(含 IDLE、SASL XOAUTH2),全平台共用一套 |
一次投入长期受益;GreenMail 可端到端验证;不再受 JVM 限制 | 工作量最大(IMAP 子集 + MIME 解析约 2000 行),首版功能需裁剪 |
| B. 各端独立实现 | Android 用 android-mail(javax.mail 分支)或自研;iOS 走 Swift 侧 IMAP 库;鸿蒙用 Java/Kotlin 侧实现 |
每端都能快速跑通 | 同一套逻辑维护 3 遍,行为不一致风险高 |
| C. 瘦客户端 + 同步网关 | 手机不直连 IMAP,走 HTTP/WebSocket 连自建网关(server/sync-gateway),网关持有 IMAP 连接、跑 AI 与提醒,手机只做展示与推送 |
移动端最快落地;省电、推送友好;顺带把“后台同步”升级成常驻服务 | 需要一台常驻机器/服务器;离线能力受限 |
建议顺序:先 A 的“最小可用子集”(GET/SEARCH/FETCH 头 + 正文 + SMTP 发送 + IDLE),它同时解决 Android/iOS/鸿蒙;C 作为 A 未完成期间的过渡(桌面已具备入站钩子,扩展成网关成本不高)。
13.3 分阶段计划
0.2 —— 桌面做扎实
- IMAP IDLE 长连接(秒级收信,替代轮询);后台同步可选“IDLE 优先、轮询兜底”。
- Google OAuth2 联调(设备码 + 刷新);微软邮箱用真实账号再验一轮。
- 华为云企业邮箱服务器参数核实(当前标为需手填)。
- 多账号并发同步 + 失败退避重试;同步日志面板。
- 备份/恢复(导出
mailweave.db+vault.json为单文件包);自动更新(jpackage + 更新源)。
0.3 —— Android
- 前置:
androidTarget()+ SQLDelightandroid-driver+ Compose MP Android 壳工程。 - 需要新写的 actual:应用数据目录、通知(
NotificationManager)、附件选择(SAF)、HTTP 钩子。 - 邮件栈按 13.2 选定的方案接入;先只做“收信 + 阅读 + 回复 + 提醒”。
- 验收:真机安装、真实邮箱收发、后台通知、断网重连。
0.4 —— macOS / Linux 正式出包
- 在对应系统上
packageDistributionForCurrentOS:macOS 出dmg(需要 Apple 开发者签名与公证才能免“未知开发者”提示)、Linux 出deb/rpm。 - 平台适配:macOS 使用钥匙串替代
vault.json、菜单栏与 Dock 行为;Linux 桌面通知走 libnotify(当前托盘通知在部分桌面环境不可用)。
0.5 —— iOS
- 前置:macOS + Xcode;Compose MP iOS target;Framework 集成方式(CocoaPods/SPM)。
- 复用
commonMain全部业务代码与 UI;写 iOS actual(文件、通知、后台刷新)。 - 验收:TestFlight 内测、真机收发、分享扩展(“用 MailWeave 转发这封邮件”)。
0.6 —— HarmonyOS
- 前置:MYKMP(或 ovCompose)提供的
ohos_arm64target 与 DevEco 工程接入;共享代码编译为动态库。 - Compose UI 经 Skia 绘到 XComponent(MYKMP 方案)。
1.0 —— 多设备与生态
- 自建同步网关(
server/sync-gateway):桌面/手机共享同一份账号与提醒,配合 C 方案还能给移动端省电。 - MCP 工具生态对外开放;RAG 知识库(历史邮件检索增强);A2A 多 Agent 协作。
13.4 每个阶段的验收口径(避免“看起来做完了”)
- 协议类改动:必须跑通
LiveMailTest(真实服务器)+ GreenMail 端到端,并留下输出。 - UI 类改动:必须离屏渲染或真实运行截图复核,不能只看编译通过。
- 平台类改动:必须在该平台上跑起来一次(打包产物启动 + 收发一封真实邮件),否则只能标“未验证”。
十四、打包与发布
14.1 版本与产物
- 版本号单一来源:
gradle.properties的mailweave.version(当前 0.4.0),Gradle 会据此生成MailWeaveInfo.kt, 应用内显示、打包版本、后续发布都读它,不再有两处硬编码。 - 目标格式(
composeApp/build.gradle.kts):Windowsmsi/exe、macOSdmg、Linuxdeb/rpm。 jpackage 不支持交叉构建:在 Windows 上只能出 Windows 产物,dmg必须在 macOS 上构建、deb/rpm必须在 Linux 上构建。 - 常用命令:
./gradlew :composeApp:createDistributable # 开发版免安装运行时
./gradlew :composeApp:createReleaseDistributable # 发布版(ProGuard 压缩 + 合并 jar)
./gradlew :composeApp:packageDistributionForCurrentOS # 当前系统的安装包(Windows 需安装 WiX)
14.2 包体优化做了什么
-
内置运行时按模块裁剪:
nativeDistributions.modules(...)只带必需的 JDK 模块(java.sql/java.net.http/java.naming/java.security.sasl/jdk.unsupported等)。新增依赖后要重跑:composeApp:suggestRuntimeModules对齐。 注意:如果本机装了 JDK 17,jlink 会把整个模块图带进来,包体会明显变大——这是官方已知行为,不是本项目的问题。 -
ProGuard 发布版实测效果(
createReleaseDistributable):产物 总大小 app 目录 jar 数 开发版 createDistributable167 MB 81 MB 97 个 发布版(压缩 + 合并 jar) 151 MB 64 MB 1 个 jar 合并不只是省体积,也少掉 96 次类路径扫描,冷启动更稳。规则见
composeApp/proguard-rules.pro。 Windows 安装包同样受益:MailWeave-0.2.0.msi开发版 95 MB → 发布版 79 MB(-17%)。 (Compose 插件会自动下载 WiX,所以 Windows 上不需要手工装 WiX 也能出 msi。) -
ProGuard 踩过的三个坑(都已验证并固化):
-dontwarn的参数是「产生告警的引用方类」,不是缺失的类;写错会一直Please correct the above warnings first。optimize=true会让 Compose 启动即抛configureSwingGlobalsForCompose相关异常 —— 所以发布版只做压缩与 jar 合并,不动字节码优化。kotlinx.coroutines必须整包 keep:否则JobSupport.cancel()被改写成invokespecial非法字节码,启动直接VerifyError: Bad invokespecial instruction ... indirect superinterface。
-
发布版自检:注入
-Dmailweave.smoke=true跑一次打包产物(本次实测 exit 0 并打印MAILWEAVE_SMOKE_OK),只编译通过不算数。 -
图标资产可复现:
packaging/generate-icons.java一条命令生成png/ico/icns,不手工维护二进制。 ICO 必须是小端的多尺寸 DIB(16/24/32/48/64/128/256),只塞一张 PNG 或写成大端都会被 Win32 判为“数据无效”, jpackage 报无法更新 ... 的图标;ICNS 则相反是大端。 -
打包后的自检:
createDistributable产物用java-options=-Dmailweave.smoke=true注入到app/MailWeave.cfg里跑一次,确认能独立启动(这是发现“漏带 JDK 模块”的唯一有效手段)。
关于
这个项目由 CodeLife Leon 独立开发与维护。更多项目、各项目发版信息、赞赏与联系方式都在个人站:
- 个人站:https://softwarescience.top/
- 本项目主页:https://softwarescience.top/projects/mailweave
- 下载与发版:https://softwarescience.top/releases/mailweave
- 赞赏支持:https://softwarescience.top/sponsor(自愿,不影响任何版本的发布)
问题反馈与需求建议:优先在仓库提 Issue,也可以邮件 virgil_lgp@163.com。
十五、自研 KMP 原生协议栈(路线 A 已落地第一版)
15.1 为什么自己做
Jakarta Mail / Angus Mail 与 LangChain4j 都是 JVM 专属,这是 Android/iOS/鸿蒙复用的最大阻塞点。
按路线图 13.2 选了方案 A:自研 KMP 原生协议栈(Ktor Socket + TLS),第一版已完成并在真实 163 账号上验证。
15.2 代码位置与能力
shared/src/commonMain/kotlin/top/softwarescience/mailweave/mail/
wire/MailWireTypes.kt MailConnectionConfig / RemoteFolder / MailboxStatus / RemoteHeader / MailWireException
wire/ImapClient.kt 自写 IMAP:CAPABILITY / LIST / EXAMINE|SELECT / UID FETCH / UID STORE / UID SEARCH /
LOGIN / AUTHENTICATE XOAUTH2 / ID(网易系必需)/ STARTTLS / IDLE / LOGOUT,
自己实现 CRLF 行读取与 `{n}` 字面量按长度读取
wire/SmtpClient.kt 自写 SMTP:EHLO/HELO 回退、能力解析、STARTTLS、AUTH(XOAUTH2 / PLAIN / LOGIN)、
MAIL FROM / RCPT TO / DATA(dot-stuffing)
wire/MimeCodec.kt Base64 / quoted-printable / RFC 2047 编码字 / RFC 2231 参数 + `expect decodeText`
wire/MimeHeaderParser.kt 头部折叠展开、重复头、大小写不敏感
wire/MimeMessageParser.kt multipart 递归(深度上限)、CTE 先解字节再按 charset 解码、附件与预览
wire/MimeComposer.kt 发信报文的 commonMain 组包(UTF-8 + base64、RFC 2047 主题与文件名)
wire/ModifiedUtf7.kt IMAP modified UTF-7(中文文件夹名)
wire/AddressParser.kt 地址头解析
wire/TlsSupport.kt TLS 收口:`mailTlsConfig`(expect) / `Socket.connectTls` / `Connection.upgradeTls`
NativeMailGateway.kt 用上面这些实现既有的 MailGateway 契约(UI/同步/AI 全都不用改)
SwitchableMailGateway.kt 按设置路由到 Jakarta 或原生引擎,切换不需要重启
设置入口:设置 → 同步与提醒 → 邮件协议引擎(MailEngine.JAKARTA 默认 / MailEngine.NATIVE)。
两个引擎共用同一套账户、缓存、AI 分诊与 IM 路由。
15.3 真机验证证据(163 真实账号)
| 环节 | 结果 |
|---|---|
| IMAP over TLS(imap.163.com:993) | ✅ 问候→CAPABILITY→LOGIN→ID 命令→EXAMINE |
| 列文件夹 | ✅ 6 个,且中文名解码正确:&V4NXPpCuTvY-→垃圾邮件(SPAM)、&XfJT0ZAB-→已发送(SENT)、&g0l6Pux-→草稿箱(DRAFTS) |
| 增量抓头 / 取正文 | ✅ 4 封,uid 升序,正文按 charset 正确解码 |
| SMTP over TLS(smtp.163.com:465) | ✅ 发送成功,163 侧确实收到(抓取结果里能看到自己发的自检邮件) |
| GreenMail 线协议用例 | ✅ IMAP/SMTP/MIME 共 20 个用例(含 CDC、字面量、dot-stuffing、附件、GBK 解码) |
15.4 真机验证挖出的两个坑(已修)
- 隐式 SSL 必须用
Socket.tls(...),不能用Connection.tls(...):后者在 465 上会卡死握手(30s 超时), 而前者在 993/465 上都正常。现在 TLS 收口在TlsSupport.kt,两个客户端共用同一实现。 - 中文文件夹名是 modified UTF-7:不解码不仅显示乱码,还会让文件夹类型识别失效(全部落到 CUSTOM)。
- 登录后必须重新取 CAPABILITY(RFC 3501):很多服务器只在认证后才公告 IDLE 等能力;163 实测前后都不公告, 但这条刷新是通用正确性修复。
- 默认引擎已切到自研协议栈:这样 IMAP 账号开箱即得实时收信;POP3 账号由
SwitchableMailGateway自动回落到 Jakarta(自研栈暂无 POP3),不会因为换默认引擎而失效。
15.5 当前边界(如实声明)
- POP3 未实现:原生引擎遇到 POP3 账号会返回明确提示,请切回 Jakarta 引擎(桌面端两者都可用)。
- IMAP IDLE 只到协议层:
switchToIdle/awaitIdleEvent已实现并有能力一致性用例,但还没接进同步引擎做常驻推送(当前仍是定时增量拉取)。 - STARTTLS 分支未在真实服务器上验证:163 的 993/465 都是隐式 SSL,STARTTLS 只有代码路径与 GreenMail 侧的静态核对。
- iOS / 鸿蒙 actual 待补:
decodeText、mailTlsConfig目前只有 JVM(Android 可直接复用 JVM 实现)。 补两个同签名 actual 即可让整套协议栈在 Kotlin/Native 上编译通过。 - XOAUTH2 未对真实 OAuth2 服务器验证(协议报文有单测)。
15.6 实时收信(IMAP IDLE)
- 实现:
sync/RealtimeSyncService给每个 IMAP 账号挂一条常驻 IDLE 监听;收到服务器推送后去抖 1.5 秒 再做增量同步,并弹系统通知(可在设置里关)。断线按 5s→10s→…最多 5 分钟指数退避重连。 状态栏显示「● 实时收信」与上次收信时间。 - 哪个引擎提供:目前只有自研协议栈(
NativeMailGateway.watch,自己发IDLE/DONE,每轮 ≤29 分钟)。 Jakarta 引擎刻意不实现:Angus 的IMAPFolder.idle()是阻塞调用,取消协程不会中断它,而从另一个线程close()打断又会与 idle 争同一把锁 —— 实测直接把测试任务挂死、greenMail.stop()也等不到连接关闭。 与其留一个退不掉的连接,不如让它只做定时同步兜底。 - 163/126 为什么没有秒级推送:实测 163(Coremail)登录前后都不在 CAPABILITY 里公告 IDLE
(只有
XAPPLEPUSHSERVICE/X-CM-EXT-1这类私有扩展)。所以网易邮箱会走到"明确拒绝 → 回落定时同步"这条 正常路径;《服务器支持 IDLE 时》才是秒级推送(GreenMail 用例已证明推送到回调的完整链路)。 - 验收:
IdleWatcherTest(GreenMail:建立 IDLE → 投递 → 回调触发 → 取消后 15 秒内退出);LiveMailTest.idlePushRoundTrip(真机:服务器支持就做"自发自收 + 等待推送",不支持就断言"干脆拒绝且不挂死")。
15.7 下一步(把路线 A 走完)
- 补 POP3 到原生栈(UIDL 去重模型已有),这样默认引擎不再需要 Jakarta 兜底。
- 补 iOS / 鸿蒙 actual,然后按 13.3 的 0.3 / 0.5 / 0.6 阶段推移动端 —— 移动端按你的意思先只留在路线图里, 近期精力放在桌面端可用与好用。
十六、第三次迭代(v0.4.0):正文排版 / 附件预览 / OAuth2 全流程
这一版只解决三个"明明有功能却用不了"的问题,改动都落在用户能看见的地方。
16.1 正文不再被压成纯文本(并顺手治乱码)
问题:阅读区取的是"纯文本形态的正文"(EmailMessage.displayBody),HTML 邮件在抓取阶段就被 HtmlTextExtractor
压平了 —— 表格散成一行、加粗/链接/列表全丢;而部分中文邮件还会整段乱码。
做法:
- 新增
ui/components/HtmlMailView.kt:一个绝不抛异常的 HTML 解析器 + Compose 渲染器(commonMain,无新依赖、 不用 jsoup —— jsoup 是 JVM 专属,这个组件将来要被移动端复用)。块级结构映射成HtmlBlock(标题/段落/有序无序列表/引用/pre/分割线/表格/图片占位),行内样式映射成HtmlSpan(粗体/斜体/下划线/删除线/等宽/链接)。 链接可点(LocalUriHandler),表格按Row + weight(1f)画成网格(邮件 HTML 大量用表格排版),cid:内联图降级为[图片] alt占位而不是破图。 - 阅读区按邮件真实内容展示:有 HTML 就渲染 HTML,只有纯文本才显示纯文本,两者都有时给一个"看纯文本 / 看 HTML 排版" 的切换按钮;正文没拉下来时退回列表预览并说明原因。
- 字符集纠偏(两个引擎共用一条解码链,
MimeCodec.jvm.decodeText):声明缺失/不认识时 UTF-8 → GB18030 → ISO-8859-1; 声明是单字节字符集或国标/Big5 这类双字节遗产字符集、而整段字节恰好是合法 UTF-8 时按 UTF-8 解;其它声明解出替换字符 再回退。实测覆盖"声明 us-ascii / latin1 / gb2312 却塞 GBK 或 UTF-8"这几种最常见的错标。 Jakarta 侧正文也改成"读字节 → 同一条解码链"(MimeParsing.readText),不再依赖容器按声明解好的字符串。
16.2 附件:右键预览(按需下载 + 系统默认程序)
问题:附件在列表里只是一行文字,点了没反应;附件字节根本没被保存,想预览也无从下手。
做法:
MailGateway新增fetchAttachment(account, credentials, folder, uid, attachmentIndex):按需取回单个附件的原始字节。 同步阶段依旧只存元数据(ParsedAttachment.data只在keepAttachmentData = true时填充),不会把整封邮件的附件读进内存。 两个引擎都实现:Jakarta 走 UID 定位 + 同一个 MIME 遍历器;自研栈复用 IMAPfetchMessage+ 解析器; POP3(Jakarta)线性扫描定位邮件后同样可取。- 平台层新增
expect fun openAttachmentWithSystem(fileName, bytes): Result<String>:落到<tmp>/mailweave-attachments/<安全文件名>(路径分隔符与 Windows 保留字符会被替换,每次都按当前字节覆写—— 同名附件长度可能一样而内容不同,靠"文件已存在就复用"会把旧内容当成新附件打开),再用系统默认程序打开 (Windows 走 Desktop/cmd start,macOSopen,Linuxxdg-open)。 - 界面:附件全部列出(
FlowRow换行),左键或右键弹出菜单 → "预览(用系统默认程序打开)";菜单同时显示大小。 Compose 这版的detectTapGestures不带右键回调,右键是自己读PointerEventType.Press + buttons.isSecondaryPressed实现的。
16.3 OAuth2:从"只有半个"到两条流程都能用
问题:设置页里 OAuth2 只有一个全局客户端 ID + "开始设备登录",而且: ① 列表页"测试"按钮拿空密码去连(OAuth2 账号必然失败); ② POP3 账号因为"必须填 IMAP 服务器"的校验根本存不下来; ③ Microsoft 365 / Outlook.com 预设不会自动切到 OAuth2,用户白试密码; ④ 最关键的是 Google 根本不可能成功 —— Google 的设备码端点不接受 Gmail 的 IMAP scope(只对白名单 scope 开放), 必须走授权码流程。
做法:
- 客户端配置(client ID / client secret / 租户)按服务商存进凭据库(
VaultData.oauthClients), 不再放在应用设置里当全局字段;一次授权的配置和之后每次刷新用的是同一份,避免"改了设置就去刷新旧 client_id 的令牌" 导致的invalid_grant。 - 两条流程:
- Microsoft:RFC 8628 设备码(浏览器输验证码),public client 不需要密钥;
- Google:授权码 + PKCE(S256) + 本机回环重定向(
127.0.0.1随机端口,只接/callback),state恒定时间比较防伪造回调,code_verifier只在进程内存里。
- 令牌按账号存(含 refresh token),取凭据时自动带上、过期前 5 分钟自动刷新(
OAuthRefreshingAccountRepository); 服务商优先用"该账号已授权过的那个"(令牌里记着),再回落到域名推断 —— 用户换了服务器地址也不会拿另一家的端点去刷新。 - 设置页重做:服务商可切换(按域名推断出默认值),客户端 ID / 密钥 / 租户按服务商分别填写并即时落盘; 设备码流程显示验证码并给"取消登录";已授权可"清除授权"。
- 账号设置的三个缺陷一并修掉:列表"测试"改用仓库里的凭据(含 OAuth2 令牌);POP3 账号按协议校验 POP3 主机;
Microsoft 预设直接切到 OAuth2(
MailProviderPreset.requiresOAuth2),密码框留空时不再抹掉已存的密码。
16.4 验证
gradle :shared:jvmTest :composeApp:jvmTest(本机 JDK 21):168 个用例全绿(shared 153 + composeApp 15;其中 3 个LiveMailTest需要真实账号环境变量,按设计跳过)。新增的关键用例:NativeAttachmentTest/JakartaMailGatewayTest/Pop3AndPresetTest:GreenMail 真协议端口上按下标取附件字节、越界返回 null;CharsetFallbackTest/MimeParsingCharsetTest:错标 charset 的中文正文(us-ascii+GBK、latin1+UTF-8、gb2312+UTF-8、UTF-8+GBK)都能正确解码;HtmlMailViewTest:解析器 13 个用例(标题/行内样式/链接/列表嵌套/表格/实体/脚本剔除/cid 占位/畸形 HTML)+ 离屏渲染冒烟;- OAuth 侧 48 个用例:设备码全状态、回环 PKCE(RFC 7636 向量)、state 校验、错误分支、超时、vault 往返与刷新。
- 界面证据(离屏渲染,非截屏):
composeApp/build/screenshots/01-mail.png(HTML 正文 + 附件菜单提示)、06-settings-account.png、08-settings-oauth.png(OAuth2 区块)。 - 桌面冒烟:
gradle :composeApp:run -Pmailweave.smoke=true→MAILWEAVE_SMOKE_OK。
16.5 仍未验证 / 边界
- 没有真机 OAuth 授权:本机没有可用的 Microsoft/Google 客户端凭据,且
oauth2.googleapis.com在境内不可达 (实测 curl 超时),所以两条流程都只做到"MockEngine + 真实回环端口"级别的验证;拿到凭据后按 §16.3 走一遍即可。 - 内联
cid:图片不显示(降级为占位);HTML 邮件不执行 CSS/JS(安全边界,刻意不做)。 - 正文里的图片链接不会自动下载(避免读一封邮件就向发件人服务器回连)。
版本历史
- 0.4.0 —— 三个"用不了"的修复:正文按邮件真实内容展示(HTML 邮件不再被压成纯文本,且声明 charset 与实际字节 不符的中文邮件不再乱码);附件左键/右键菜单用系统默认程序预览(按需下载到临时目录,两个引擎 + POP3 都实现); OAuth2 授权全流程打通(Microsoft 设备码 / Google 回环 + PKCE,令牌自动刷新),并修掉账号设置里"测试连接被 空密码挡住""POP3 账号存不下来""Microsoft 预设不会自动切 OAuth2"等问题。
- 0.3.0 —— 桌面端实时收信:IMAP IDLE 常驻监听(Jakarta 与自研栈双引擎)+ 新邮件系统通知 + 定时同步兜底;桌面易用性补齐(J/K 移动、Enter 打开、U 已读、S 星标,状态栏实时指示与上次收信时间)。
- 0.2.0 —— 自研 KMP 原生邮件协议栈第一版(route A):IMAP/SMTP/MIME/modified UTF-7 在 commonMain 自实现, 与 Jakarta 引擎可热切换;真机验证 163(IMAP+SMTP over TLS、ID 命令、中文文件夹名)。
- 0.1.0 —— 首个可运行版本:桌面端邮件收发(IMAP/POP3/SMTP)、SQLDelight 缓存、AI 分诊/回复/日程/对话、 IM 通知桥接与路由规则、本地提醒、数据目录迁移、桌面 UI。
项目介绍
“Weave”寓意将邮件、AI 智能体、即时通讯平台、自定义钩子编织成一张统一的信息网络。名称简洁、国际化,且与项目“连接一切通讯渠道”的核心理念高度契合。|作者个人站:https://softwarescience.top/
https://softwarescience.top/projects/mailweave定制我的领域