灵枢邮件获客系统

LingShu Outreach · 内部用的邮件获客与收发管理系统。「审核」只是其中一页;系统不发邮件、不连 SMTP、不保存任何 SMTP 凭据 —— 发送由引擎执行。

初始化第一个管理员

这台审核台还没有任何用户。用服务器上的一次性令牌建第一个管理员账号 —— 之后加同事、改角色、重置口令都在「管理 → 用户」页里做,不用再 SSH。

灵枢邮件获客系统

总览

发件人(发信邮箱)

契约 A(H1):邮箱身份与 SMTP 端点只在 send/config.json 里 —— 那份「可信注册表」属主是引擎用户,审核台既不写也改不了,这一页只只读展示它。 新增 / 更换邮箱或端点,需要部署方修改 send/config.json。 这一页能改的只有每邮箱的 dailyCap / ramp / minIntervalSec / enabled,而且只能收紧或停用; 点「生成并校验」把它们写进 send/control/config.json(只含这几项的覆盖层), 写盘前先用「注册表 ⊕ 覆盖层」跑一次引擎 dry-run,退出码 0 才落盘。 凭据只写不读 —— 填进去立刻投递到 send/incoming_secrets/<id>.env,之后这里只显示「已设置 / 未设置」。

清单

引擎校验会对每个邮箱各跑一次 dry-run,总共最多 150 秒(先于反代的 180 秒超时结束)。 邮箱多的时候按个数摊时间。

限额覆盖

这四项是审核台唯一能写进 send/control/config.json 的东西,而且 只能收紧:dailyCap 取更小者(硬顶 40)、ramp 逐日取更小者、最小间隔取更大者(全局下限 90 秒); 取消「启用」写的是 enabled:false(覆盖层里 enabled 只允许出现 false)。 身份、队列、认证方式、provider、端点、备注一律改不了 —— 它们在 send/config.json 里,由部署方维护。

凭据

一次提交 = 整份覆盖。审核台不回读旧值,所以这次没填的字段会从 <id>.env 里消失 —— 上面列了当前有哪些 KEY,照着补齐。 写完这里就再也读不到值了:任何接口都不返回,审计只记字段名。
G2:这里只有 user / pass / client_id 三项 —— SMTP 端点(host / port / secure)由 provider 在引擎侧固定,自建端点由部署方写在引擎的可信配置里。 能改端点,就等于能把一份自己读不到的口令送去别的主机认证一次,所以这三项审核台一律不接受。

批量导入

批量导入只导入限额字段:id / dailyCap / ramp / minIntervalSec / enabled (中文列名也认)。其它列一律整行拒绝并提示 —— from / 发信地址 / queue / auth / provider / host / port / secure / stage / note 都是注册表的东西,请部署方改 send/config.json; user / pass / client_id 也不再走这条路,请用清单里每个发件人的「凭据」表单单独投递。 id 必须已经在注册表里;限额只能收紧;「停用」不在这条路上做(它会连带不可逆的吊销,必须逐个确认)。 逐行校验:坏行只影响自己,报告里会给出行号与原因。

任务

任务(campaign)是系统里的一等对象:名单挂在它下面、批次通过 batchmeta.campaignId 认领它、 发送计划引用它的框架 hash。框架通过 = 第一层审核;逐封审是第二层,在「审核」页。 框架实质修改(hash 变)会让批准自动作废并回到 draft。

新建任务

名单

导入 CSV / TSV(一期不做 XLSX)。缺「邮箱」列整份拒绝; 缺来源 URL 或采集日期的行可以导入,但不可导出、不可进计划。 导入时每行生成一个不可变的 leadId —— 序列按它认人,不按行号。 「清洗」(Bouncer 验证)是第二轮的事,这一页现在只做导入、分层与政策改判。

导入

发送

用自然语言或表单定发送计划 → 代码校验 → 确定性回读 + 日历预览 → 确认(= 一段 ≤ 7 天的发送授权)。 人看见的那段中文由代码从校验后的最终字段生成,模型的复述只摆在旁边对照。 确认只落 plans.json 与审计;写 LIVE_CONFIRMED / ACTIVE.json 只发生在 计划日早上 08:40–08:55 的武装窗口,而且**preflight 全过之后**才写(规格 §5.7)。 错过窗口不补;序列第 2 步永远不自动武装。

新建计划

发送运行(引擎侧)

只读引擎写的 send/runs/*.json 与 send/send_log.json。 定时发送由引擎侧的 systemd timer 触发,审核台只写 ACTIVE.json 告诉它「今天发哪一批」。

人工登记一条投诉 / 退订

键盘:J/K 上下 · A 通过 · H 打回 · E 编辑 · Esc 收起编辑

退订登记

退订表是只增不减的(CASL 要求永久保留)。登记后该邮箱永久不可导出,并会进入后续每一份队列文件的 state.manual.unsubscribes。 H6:登记的顺序是「先写权威契约文件、成功了才写本地记录」 —— 契约写失败就整条失败,本地一个字节都不记;每次登记之后还会立刻对一次账 (本地退订集合必须 ⊆ 契约集合),对不上就阻断导出与「设为生效」。

管理员面板

急停与当日确认

STOP 急停
写 send/control/STOP。引擎启动时与每封发送前各查一次,见到就安全退出(退出码 3)。
当日 LIVE_CONFIRMED
写 send/control/LIVE_CONFIRMED,内容 = 温哥华当天日期。这是 live 四道闸门的第 (c) 道,只管当天。

导入批次

批次 id = 文件名。重复导入同一批次会保留已有审核决定(按行号 + 邮箱匹配); 底稿正文变过的行,原有「通过」作废并标记需重审。

导出队列

只导出状态为「通过」的行。导出后自动跑一次引擎 dry-run(每次一个独立的隔离状态目录,跑完删掉), 退出码非 0 或出现 body_changed / sha / body_drift 类拒绝即判失败,文件会被改名成 .REJECTED.json。 引擎校验最多跑 150 秒(要先于反代的 180 秒超时结束,否则页面报 504 而服务端还在跑)。 文件名带导出时刻(…_日期_时分秒_queue.json),同一天再导一次不会覆盖上一份 —— 正在生效(ACTIVE.json 指向)的文件更是禁止被覆盖或删除。

批次管理与「设为生效」

生效 = 写 send/batches/reviewed/ACTIVE.json,告诉引擎侧的定时器「这个发件人今天发哪一份队列」。 只有引擎校验通过的导出才能设为生效;同一队列同时只能有一个生效批次。 生效不等于会发 —— 真发还要当天点过 LIVE_CONFIRMED,且四道闸门全过。

双人复核

开关是全局的。开启后,只有一个人通过的行显示为「待复核」,不会被导出。

第一层审核与频控(v2.0)

「框架通过」= 任务页上的第一层审核(逐封审是第二层,在审核页)。 域级日上限填 0 表示只在计划校验里告警、不拦。

收信开关(inboxCollector)

auto:收信成功跑过一轮之后才判新鲜度;从来没跑过 → 武装被 inbox_never_ran 拦。 on:总是判新鲜度(超过上限没成功轮询过 → inbox_stale)。 off:部署方明确不收信 —— 武装放行,但退信 / 回信 / 投诉这段时间一条都进不来。 要收信:服务器上 sudo systemctl enable --now lingshu-inbox.timer(单元默认是关的)。 这个开关写在 review/data/settings.json,改一次进一次审计。

用户

审计日志