[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-81474":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":16,"stars7d":17,"stars30d":17,"stars90d":15,"forks30d":15,"starsTrendScore":18,"compositeScore":19,"rankGlobal":10,"rankLanguage":10,"license":10,"archived":20,"fork":20,"defaultBranch":21,"hasWiki":22,"hasPages":20,"topics":23,"createdAt":10,"pushedAt":10,"updatedAt":24,"readmeContent":25,"aiSummary":26,"trendingCount":15,"starSnapshotCount":15,"syncStatus":17,"lastSyncTime":27,"discoverSource":28},81474,"ClawEmail","WangXingFan\u002FClawEmail","WangXingFan","基于 claw.163.com 的 子邮箱批量管理 \u002F 实时收发 一体化前后端","",null,"TypeScript",32,24,30,0,1,2,3,44.39,false,"main",true,[],"2026-06-12 04:01:34","# Claw Email Web Manager\n\n基于 `claw.163.com` 的 **子邮箱批量管理 \u002F 实时收发** 一体化前后端。\n通过 Web UI 验证码登录 Claw，自动派生 Dashboard Cookie 与 API Key，为每个子邮箱维持长连接监听，新邮件实时入库并经 SSE 推送给前端，可在线发件、回复、删除（远端 + 本地双删）、下载附件。\n\n仓库结构：\n\n```text\nsrc\u002F\n  server\u002F                Fastify 5 后端（SQLite + Claw SDK + Dashboard 内部接口）\n    config.ts            环境变量解析（zod）\n    db.ts                better-sqlite3 schema 与 DAO\n    runtime-config.ts    运行时凭据（优先读 SQLite，再回退 .env）\n    claw-dashboard.ts    Claw Dashboard 内部 HTTP 接口封装\n    claw-mail.ts         @clawemail\u002Fnode-sdk 客户端池 + 发件\u002F回信\u002F删信\u002F列表\n    listener-manager.ts  每邮箱 WS 长连接 + 指数退避重连\n    sse.ts               SSE 广播总线\n    routes\u002F              auth \u002F mailboxes \u002F mails \u002F send \u002F events\n    index.ts             Fastify 启动 + 静态托管前端\n  web\u002F                   Vite 7 + React 19 单页应用\n    src\u002FApp.tsx          主壳：登录\u002F路由\u002F连接卡\u002F工具栏\n    src\u002Fapi.ts           前端调用层（统一 X-Admin-Password \u002F ?token=）\n    src\u002Fi18n.tsx         中英双语 + 暗亮主题\n    src\u002Fcomponents\u002F      InboxView \u002F MailboxesView \u002F ComposeDrawer \u002F ListenersDrawer \u002F ListenersView\n    src\u002Fhooks.ts         可拖拽栏宽（localStorage 持久化）\n```\n\n## 1. 功能矩阵\n\n| 模块 | 能力 | 实现位置 |\n|---|---|---|\n| Claw 绑定 | 邮箱 + 验证码两步登录；自动取 `auth\u002Fme` \u002F `workspaces` \u002F `mailboxes` \u002F `api-keys`；写入 SQLite | `routes\u002Fclaw-auth.ts`、`runtime-config.ts` |\n| 邮箱管理 | 创建（前缀 `^[a-z0-9]{1,32}$`）、列表、`?sync=true` 与远端做差量同步、删除（拒绝删主邮箱） | `routes\u002Fmailboxes.ts`、`claw-dashboard.ts` |\n| 通讯规则 | 同步并保存 `commLevel` \u002F `extReceiveType` \u002F `extSendType`；邮箱页可配置个人 \u002F 内部 \u002F 外部通信范围 | `routes\u002Fmailboxes.ts`、`CommunicationRulesDrawer.tsx` |\n| 实时收件 | 每个 `active` 邮箱一条 WS 监听；落库为 `mails` + `attachments`；SSE `event: mail` 推送 | `listener-manager.ts`、`sse.ts` |\n| 收件同步 | `GET \u002Fapi\u002Fmails?sync=true`：远端 INBOX `id` 列表 → 删本地多余、补本地缺失 | `routes\u002Fmails.ts` |\n| 邮件详情 | 返回行 + 解析后的原始 JSON + 附件元数据 | `routes\u002Fmails.ts` |\n| 删信 | SDK `moveMessages([id], \"Trash\")` 远端删除 + 本地行删除 | `claw-mail.ts`、`routes\u002Fmails.ts` |\n| 发件 | 仅允许 `from` 是本地已管理邮箱 | `routes\u002Fsend.ts` |\n| 回信 | 基于本地 `mailId` 反查 `provider_mail_id` 调 SDK | `routes\u002Fsend.ts` |\n| 附件下载 | 不缓存原始字节，按需经 SDK 流式拉取 | `routes\u002Fmails.ts` |\n| 监听器诊断 | `\u002Fapi\u002Flisteners` 输出 `email\u002Fconnected\u002Fretry`；前端有侧栏摘要 + 抽屉详情 | `routes\u002Fevents.ts`、`ListenersDrawer.tsx` |\n| 前端体验 | 中英双语、暗亮主题、拖拽栏宽（侧边栏 \u002F 邮件列表）、登录态 localStorage 记忆 | `i18n.tsx`、`hooks.ts` |\n\n## 2. Claw 验证码登录链\n\n不收集任何 Claw 密码。`POST \u002Fapi\u002Fauth\u002Fclaw\u002Fverify-code` 内部串联以下接口：\n\n```http\nPOST https:\u002F\u002Fclaw.163.com\u002Fmailserv-claw-dashboard\u002Fp\u002Fv1\u002Fauth\u002Femail\u002Fsend-code\nPOST https:\u002F\u002Fclaw.163.com\u002Fmailserv-claw-dashboard\u002Fp\u002Fv1\u002Fauth\u002Femail\u002Fverify-code   → Set-Cookie: CLAW_SESS\nGET  https:\u002F\u002Fclaw.163.com\u002Fmailserv-claw-dashboard\u002Fapi\u002Fv1\u002Fauth\u002Fme\nGET  https:\u002F\u002Fclaw.163.com\u002Fmailserv-claw-dashboard\u002Fapi\u002Fv1\u002Fworkspaces\nGET  https:\u002F\u002Fclaw.163.com\u002Fmailserv-claw-dashboard\u002Fapi\u002Fv1\u002Fmailboxes?workspaceId=\u003Cid>\nGET  https:\u002F\u002Fclaw.163.com\u002Fmailserv-claw-dashboard\u002Fapi\u002Fv1\u002Fapi-keys\n```\n\n落库（SQLite `app_settings` 表）：\n\n```text\nclaw.apiKey\nclaw.dashboardCookie\nclaw.userEmail\nclaw.workspaceId \u002F claw.workspaceName\nclaw.parentMailboxId\nclaw.rootPrefix\nclaw.domain\n```\n\n`workspace` 取 `status=active`，`apiKey` 取 `defaultFlag=1` 优先。\n绑定成功后会先 `stopAllMailboxListeners()` + `resetMailClients()` 再用新凭据 `startAllMailboxListeners()`，避免旧连接残留。\n\n## 3. Dashboard 内部接口（仅后端调用）\n\n| 用途 | 方法 \u002F 路径 |\n|---|---|\n| 列出工作区下的邮箱树 | `GET \u002Fapi\u002Fv1\u002Fmailboxes?workspaceId=\u003Cid>` |\n| 创建子邮箱 | `POST \u002Fapi\u002Fv1\u002Fmailboxes`（`{prefix, displayName, mailboxType:\"sub\", workspaceId, parentMailboxId}`） |\n| 配置通讯规则 | `POST \u002Fapi\u002Fv1\u002Fmailboxes\u002Fcomm-settings?id=\u003CmailboxId>`（`{commLevel, extReceiveType?, extSendType?}`） |\n| 删除邮箱 | `POST \u002Fapi\u002Fv1\u002Fmailboxes\u002Fdelete?id=\u003CmailboxId>` |\n\n返回壳为 `{code, message, success, result}`，由 `parseDashboardResponse` 统一解包。\n\n## 4. 本项目 HTTP API\n\n### 4.1 鉴权\n\n所有 `\u002Fapi\u002F*` 必须带：\n\n```http\nX-Admin-Password: \u003CADMIN_PASSWORD>\n```\n\n浏览器无法自定义头的场景（SSE、附件 `\u003Ca href>`）改用：\n\n```http\n?token=\u003CADMIN_PASSWORD>\n```\n\n`X-Admin-Password` 与 `query.token` 命中其一即放行（见 `src\u002Fserver\u002Findex.ts: extractAdminPassword`）。\n\n### 4.2 端点清单\n\n```http\nGET    \u002Fhealth\nGET    \u002Fapi\u002Fauth\u002Fclaw\u002Fstatus\nPOST   \u002Fapi\u002Fauth\u002Fclaw\u002Fsend-code\nPOST   \u002Fapi\u002Fauth\u002Fclaw\u002Fverify-code\nPOST   \u002Fapi\u002Fauth\u002Fclaw\u002Frefresh\nPOST   \u002Fapi\u002Fauth\u002Fclaw\u002Flogout\n\nGET    \u002Fapi\u002Fmailboxes                # 仅本地\nGET    \u002Fapi\u002Fmailboxes?sync=true      # 与 Claw 做差量同步后再返回\nPOST   \u002Fapi\u002Fmailboxes                # { suffix }\nPOST   \u002Fapi\u002Fmailboxes\u002F:id\u002Fcomm-settings      # { commLevel, extReceiveType?, extSendType? }\nDELETE \u002Fapi\u002Fmailboxes\u002F:id\n\nGET    \u002Fapi\u002Fmails?mailbox=&limit=50&offset=0\nGET    \u002Fapi\u002Fmails?sync=true&mailbox=...      # 远端 INBOX 全量比对\nGET    \u002Fapi\u002Fmails\u002F:id                        # 详情 + 解析后 JSON + 附件元数据\nDELETE \u002Fapi\u002Fmails\u002F:id                        # 远端移到 Trash + 本地删除\nGET    \u002Fapi\u002Fmails\u002F:id\u002Fattachments\u002F:partId    # 流式下载附件\n\nPOST   \u002Fapi\u002Fsend                              # { from, to[], cc?, bcc?, subject?, body?, html? }\nPOST   \u002Fapi\u002Freply                             # { mailId, body?, html?, toAll? }\n\nGET    \u002Fapi\u002Fevents                            # SSE: event: mail\nGET    \u002Fapi\u002Flisteners\n```\n\n请求样例：\n\n```jsonc\n\u002F\u002F POST \u002Fapi\u002Fmailboxes\n{ \"suffix\": \"4\" }\n\n\u002F\u002F POST \u002Fapi\u002Fsend\n{\n  \"from\": \"vercel.4@claw.163.com\",\n  \"to\": [\"target@example.com\"],\n  \"cc\": [\"copy@example.com\"],\n  \"subject\": \"hello\",\n  \"body\": \"message body\",\n  \"html\": false\n}\n\n\u002F\u002F POST \u002Fapi\u002Freply\n{ \"mailId\": 123, \"body\": \"reply body\", \"toAll\": false, \"html\": false }\n```\n\nSSE 事件：\n\n```text\nevent: mail\ndata: {\"mailboxEmail\":\"vercel.4@claw.163.com\",\"id\":42,\"providerMailId\":\"...\"}\n```\n\n校验：所有入参经 zod 解析；失败返回 `400 {error:\"invalid input\", details:[...]}`。\n\n## 5. 数据持久化\n\nSQLite 文件由 `DATABASE_PATH` 指定（默认 `.\u002Fdata\u002Fapp.db`），开启 `journal_mode=WAL` + `foreign_keys=ON`。\n\n```text\nmailboxes      子邮箱：id \u002F email(unique) \u002F prefix \u002F status \u002F install_command \u002F auth_url \u002F comm_level ...\nmails          邮件：mailbox_email + provider_mail_id 联合唯一，含 raw_json 全文\nattachments    附件元数据：mail_id 外键 → mails.id（ON DELETE CASCADE）\napp_settings   key\u002Fvalue，存 Claw 凭据\n```\n\n附件二进制**不入库**，下载时调 `client.mail.getAttachment` 流式回传给浏览器。\n\n## 6. 监听器与重连\n\n`src\u002Fserver\u002Flistener-manager.ts`：\n\n- 启动条件：邮箱 `status === \"active\"` 且 `hasClawMailConfig()` 为真\n- 退避序列：`[1, 2, 4, 8, 16, 30]` 秒\n- `client.ws.onMessage` 收到 mailId → `client.mail.read({markRead:true})` → `saveMail` → SSE `mail` 广播\n- `client.ws.onDisconnect` 触发 `scheduleReconnect`\n- 删邮箱、断开 Claw 时会显式 `stopMailboxListener` 关闭 WS\n\n`\u002Fapi\u002Flisteners` 当前返回字段：`{ email, connected, retry }`。前端 `ListenersDrawer` 同时兼容了未来可能扩展的 `status \u002F startedAt \u002F lastEventAt \u002F error` 字段。\n\n## 7. 环境变量\n\n```env\nNODE_ENV=production\nPORT=3000\nADMIN_PASSWORD=change-me\n\n# 以下变量是\"兜底值\"，验证码登录成功后会被 SQLite 中的值覆盖\nCLAW_API_KEY=\nCLAW_DASHBOARD_COOKIE=\nCLAW_WORKSPACE_ID=\nCLAW_PARENT_MAILBOX_ID=\nCLAW_ROOT_PREFIX=\nCLAW_DOMAIN=claw.163.com\n\nDATABASE_PATH=.\u002Fdata\u002Fapp.db\n```\n\n读取顺序（`runtime-config.ts`）：`SQLite app_settings` → `process.env`，缺一则 API 报 `... is required; connect Claw first`。\n\n## 8. 本地运行\n\n应用监听端口由 `PORT` 环境变量控制，默认 **3000**（host `0.0.0.0`）。\n\n```powershell\nnpm install\nnpm run build\nnpm start\n# 默认 http:\u002F\u002Flocalhost:3000\n# 改端口： $env:PORT=8080; npm start\n```\n\n开发：\n\n```powershell\nnpm run dev          # tsx 跑后端，监听 :3000（受 PORT 控制）\nnpm run dev:web      # Vite 跑前端，监听 :5173\nnpm run typecheck    # tsc --noEmit\n```\n\n`npm run build` = `vite build` 产出静态资源到 `dist\u002Fweb` + `esbuild` 打包后端到 `dist\u002Fserver\u002Findex.js`，`@clawemail\u002Fnode-sdk`、`fastify`、`better-sqlite3` 等保持 external。\n\n## 9. Docker 部署\n\n容器内进程恒定监听 `3000`，宿主端口由 `ports` 左侧决定（默认 `3000:3000`）。\n\n### docker compose\n\n```powershell\ngit clone https:\u002F\u002Fgithub.com\u002FWangXingFan\u002FClawEmail.git\ncd ClawEmail\ncp .env.example .env\ndocker compose up -d\ncurl http:\u002F\u002Flocalhost:3000\u002Fhealth\n```\n\n### docker run\n\n```bash\ndocker run -d --name clawemail \\\n  -p 3000:3000 \\\n  -e ADMIN_PASSWORD=change-me \\\n  -v $PWD\u002Fdata:\u002Fapp\u002Fdata \\\n  ghcr.io\u002Fwangxingfan\u002Fclawemail:latest\n```\n\n`.\u002Fdata` 挂到 `\u002Fapp\u002Fdata` 持久化 SQLite。\n\n## 10. Cloudflare 无服务器部署\n\n本仓库同时提供 Cloudflare Workers + Static Assets + D1 的部署入口：\n\n```text\nsrc\u002Fcloudflare\u002F          Cloudflare Worker API（无 Fastify \u002F better-sqlite3 \u002F Node SDK）\nmigrations\u002F0001_initial.sql\nwrangler.toml\n```\n\n限制边界：\n\n- Cloudflare 版不运行常驻邮箱 WebSocket 监听器；收件箱通过前端刷新或 `GET \u002Fapi\u002Fmails?sync=true` 请求触发同步。\n- D1 替代本地 SQLite 文件；不需要自建服务器或挂载磁盘。\n- 附件仍然不入库，下载时从 Claw 远端按需转发。\n- D1 表结构会在首次访问 `\u002Fapi\u002F*` 时自动初始化。\n\n推荐部署方式：\n\n1. 先 Fork 本仓库到自己的 GitHub 账号。\n2. 进入 Cloudflare 控制台。\n3. 进入 `Workers & Pages` → `Create application` → `Import a repository`。\n4. 选择自己的 fork 仓库。\n5. 将项目名称改为小写，例如 `clawemail`、`clawemail-cf`。\n6. 按页面步骤一路下一步，直到部署完成。\n\n本地 Wrangler 手动部署：\n\n```powershell\nnpx wrangler login\nnpx wrangler d1 create clawemail\n```\n\n如果 Wrangler 提示是否把 D1 配置写入 `wrangler.toml`，选择 `Yes`。手动填写时应补上 `database_id`：\n\n```toml\n[[d1_databases]]\nbinding = \"DB\"\ndatabase_name = \"clawemail\"\ndatabase_id = \"你的 D1 database_id\"\n```\n\n设置管理密码并初始化 D1：\n\n```powershell\nnpx wrangler secret put ADMIN_PASSWORD\nnpm run cf:migrate\n```\n\n部署：\n\n```powershell\nnpm run cf:deploy\n```\n\n手动部署时也可以跳过 `npm run cf:migrate`，应用会在首次 API 请求时自动建表；保留该命令是为了需要显式执行迁移的场景。\n\nCloudflare 版入口与原 API 保持一致，前端仍调用同源 `\u002Fapi\u002F*`。如果需要回到原服务器版，继续使用 `npm run build && npm start` 或 Docker 部署即可。\n\n\n\n## 致谢\n\n感谢 [Linux.do](https:\u002F\u002Flinux.do) 社区。\n","ClawEmail 是一个基于 claw.163.com 的子邮箱批量管理和实时收发的一体化前后端解决方案。项目使用 TypeScript 开发，后端采用 Fastify 5 搭配 SQLite 数据库和 Claw SDK，前端则使用 Vite 7 和 React 19 构建单页应用。核心功能包括自动派生 Dashboard Cookie 与 API Key、为每个子邮箱维持长连接监听新邮件并实时推送到前端，支持在线发件、回复、删除（远端 + 本地双删）及附件下载。适用于需要高效管理多个子邮箱，并且对邮件实时性有较高要求的场景，如企业客服系统或个人多账号管理等。","2026-06-11 04:05:11","CREATED_QUERY"]