Commit Graph
5 Commits
Author SHA1 Message Date
tamakyi 5823b5fbb3 SMTP 断线自动重连:修掉大 delay 下整批 please run connect() first
现象:--delay 调到 60s/封、发 305 人时,第一封报 Server not connected,
之后每一封都是 please run connect() first,全军覆没。

根因:SMTP 服务器会掐掉空闲连接(QQ/163/阿里云常见 5~15 分钟,有的更短)。
脚本只在开头 smtp_login 一次,之后从不重连——连接一旦被服务器关掉,
同一个死掉的连接对象会被反复使用,除第一封外全是 connect() 报错。
delay 越大必然越早触发;以前默认 1s/封时几乎等不到超时,所以没暴露过。

改动:
- 新增 SmtpSession 包装连接:
  * 空闲超过 smtp_idle_reconnect 秒(默认 30,可 0 关闭)就主动断开重连,
    不必每封都先吃一发失败
  * 仍遇到断线类异常(ServerDisconnected/ConnectError/OSError)立即重连
    重试,最多 3 次(间隔 2s/4s);耗尽后抛原始异常、该收件人如实计失败
  * 只重试连接类异常;SMTP 拒收(5xx/4xx 回话)不重试,避免重复投递
  * 「Server not connected」不会被误判为限流,不会误触发发送中止
- 新增 --smtp-idle-reconnect / params.ini [send] smtp_idle_reconnect
- 发送循环改用 SmtpSession,结束统一 close
2026-09-07 20:14:34 +08:00
tamakyi f83ea3e29a 退信/巡检开关移入 params.ini [bounce],命令行仅作临时覆盖
原来这些开关只能靠命令行传,每次群发都得敲一长串,也容易漏。现在统一在
params.ini 的 [bounce] 小节长期配置,优先级为 命令行 > params.ini > 内置默认。

改动:
- load_params 新增 [bounce] 解析:watch_bounces / watch_every / prune_unknown /
  no_invalid_list / invalid_file / bounce_report / imap_host / imap_port /
  since / since_days;新增 _parse_bool 支持 1/0、yes/no、true/false、on/off,
  留空或非法值忽略并回退默认(带警告)
- 相关 argparse 默认值改为 None,以便区分「用户显式传了」还是「没传」——
  没传才用 params.ini 的值,保证命令行优先级
- save_params 回写 [bounce](用当前生效值),并用 _preserve_extra_sections
  原样保留文件里其它手写小节,避免保存时把配置抹掉
- 交互模式参数摘要里显示巡检开关的当前值与来源
- 把 argparse 构造抽成 build_arg_parser(),便于测试
- params.ini 补上 [bounce] 小节的默认配置与注释;README 同步
2026-09-07 19:25:20 +08:00
tamakyi 1cae63b671 发送中巡检限流退信:发现即中止发送
背景:限流退信是异步投到发件箱的——SMTP 当场返回 250,几十秒后收件箱
才收到「您的账号外发频率超过邮件系统限制」。只等发完再核查,等发现时
被限流的那一批早就全废了,白耗额度。

改动:
- 把 IMAP 扫描抽成 _scan_imap_bounces(),供退信核查与发送中巡检共用
  (新增 not_before 参数:只看指定时刻之后到达的退信)
- 发送过程中每 --watch-every 封(默认 10)巡检一次收件箱,命中限流类
  退信立即中止;只看本次运行开始后的新退信,历史退信不误触发
- SMTP 当场返回限流(如 450 MI:CEL)也立即中止;硬退信则照旧记入
  无效地址清单并继续发下一封
- 中止后把限流地址从断点清单剔除,等限制恢复重跑即自动补发
- IMAP 连续两轮不可用则自动关闭本次巡检并提示,不影响发送
- 新增 --no-watch-bounces / --watch-every,报告新增 aborted 等字段
- 顺带:分类依据里的状态码改为只显示匹配到的码(原来是整串原文)
2026-09-07 19:11:14 +08:00
tamakyi bc971deb37 退信核查按原因分类:只有限流/临时性退信才剔除补发
问题:退信核查此前凡是退信一律从断点清单剔除、下次重跑补发。
但退信分两类——「您的账号外发频率超过邮件系统限制」这类限流退信重发
可成功;「地址不存在」这类硬退信重发也发不出去,只会浪费每日发信额度、
拖垮发信信誉。

改动:
- 新增退信分类 rate/hard/unknown:优先用 DSN 状态码 5.x.x/4.x.x 判定,
  同一封退信里的多个收件人可分别归类;无 DSN 时回退正文关键词匹配
  (硬退信优先于限流,宁可不补发可疑地址)
- --prune-state 只剔除「可重试」退信;永久失败不剔除,即不再补发
- 新增无效地址清单 broadcast_invalid.json:硬退信地址记入后每次运行
  直接跳过(含 --reset-state),附 --reset-invalid / --no-invalid-list
- 新增 --prune-unknown / --invalid-file / --bounce-report 参数
- 新增退信明细报告 broadcast_bounce_report.json(含每个收件人判定依据)
- 修正 _part_text 取正文的两个解码坑:utf-8 正文默认 base64 传输编码未
  解码、str 形态 payload 走 raw-unicode-escape 致中文变 \uXXXX,两者都会
  让中文关键词匹配静默失效
- 补充退信主题关键词(delivery failed、未能送达、无法送达 等)
- README 补充退信分类表与无效地址清单说明
- 新增 .gitignore,移除误入库的 __pycache__/broadcast.cpython-313.pyc
2026-09-07 18:26:09 +08:00
tamakyi 912facaf2c first init 2026-09-07 18:04:51 +08:00