#31 进箱:电子邮件自动归类

2025-08-17

谷歌(Gmail)和苹果(Apple iCloud)

Gmail: Primary(主要) Social(社交) Updates(动态) “Forums(论坛) Promotions(推广)
Apple: Primary(主要) Transactions(交易) Updates(更新) Promotions(推广)

Gmail 和 Apple 分类比较类似:

  • 主要:个人邮件
  • 更新/动态:资讯、订阅
  • 推广:营销邮件

不同的是,谷歌搞了一个社交与论坛,因为他们有这方面的业务。
苹果则是单独列了一个交易,将订单、物流等邮件单独管理。

微软(Outlook)

  • 重点
  • 其他

这个归类方式就比较简单,可能和微软的在线业务不够丰富、市场占有率不够高(和谷歌、苹果相比)有关。

总结

这个分类就收益于 AI 的出现,体现了 ISP 对电子邮件内容的控制程度的升高。

#30 Go 解析 SPF 记录

2025-07-08

背景知识:什么是 SPF?

SPF(Sender Policy Framework,发送方策略框架)是一种电子邮件验证协议,定义在 https://datatracker.ietf.org/doc/html/rfc7208 中。它的核心作用是让域名所有者声明:哪些 IP 地址或服务器被允许代表该域名发送邮件。

接收方邮件服务器收到邮件后,会查询发件人域名的 SPF 记录(一条 DNS TXT 记录),检查连接 IP 是否在授权列表中。这能有效防止邮件伪造和钓鱼攻击。

一条典型的 SPF 记录长这样:

v=spf1 ip4:192.0.2.0/24 include:_spf.google.com ~all

Golang 解析

注意:这个库只有 12 次提交,已经 2 年多没有更新,肯定不完备,但是放在小工具中简单使用也没有什么问题。
UPDATE: 这个库太简陋了,不如自己写一个。

mileusna/spf 是一个轻量级的 Go 语言 SPF 检查库,提供了两个核心接口:

功能 函数 说明
SPF 校验 spf.CheckHost() 给定 IP、域名和发件人,返回完整的 SPF 判定结果
记录查询 spf.LookupSPF() 仅从 DNS 拉取原始 SPF TXT 字符串
package main

import (
    "net"

    "github.com/mileusna/spf"
)

func main() {
    // optional, set DNS server which will be used by resolver.
    // Default is Google's 8.8.8.8:53
    spf.DNSServer = "1.1.1.1:53"

    ip := net.ParseIP("123.123.123.123")
    r := spf.CheckHost(ip, "qq.com", "service@qq.com", "");
    // returns spf check result
    // "PASS" / "FAIL" / "SOFTFAIL" / "NEUTRAL" / "NONE" / "TEMPERROR" / "PERMERROR"

    // if you only need to retrive SPF record as string from DNS
    spfRecord, _ := spf.LookupSPF("qq.com")
    fmt.Println(spfRecord)
    // v=spf1 include:spf.mail.qq.com -all
}

spf.CheckHost() 返回以下 7 种字符串结果之一:

返回值 含义 邮件处理建议
"PASS" IP 被明确授权 正常接收
"FAIL" IP 不在授权列表中(-all 或 -ip4 匹配失败) 建议拒收
"SOFTFAIL" 软失败(~all),IP 可能未授权但无法确定 接受但标记/放入垃圾箱
"NEUTRAL" 策略判定为中立(?all) 按常规流程处理
"NONE" 域名无 SPF 记录或记录无法解析 建议拒收
"TEMPERROR" DNS 临时错误(超时、SERVFAIL 等) 可能是 DNS 抖动,稍后重试,暂不拒收
"PERMERROR" SPF 记录存在永久错误(语法错误、多重记录等) 配置异常,建议拒收

#29 一封邮件的“500 英里魔咒”:一个 Bug 如何暴露网络底层规律

2024-03-13

2002 年,美国北卡罗来纳州一所大学发生了一件看似荒诞的网络故障:统计系的邮件服务器,只能向 500 英里以内的服务器正常发邮件。

统计系主任最初发现,邮件发送似乎存在一道神秘的“距离边界”:里士满、亚特兰大、华盛顿等地可以正常发送,但孟菲斯、波士顿、底特律等更远地区却几乎全部失败。统计系甚至请地理学家绘制地图,发现故障范围竟然真的接近一个以办公室为圆心、半径约 500 英里的圆。

管理员 Trey Harris 随后发现,真正决定邮件能否发送的并不是收件人住在哪里,而是对方邮件服务器在哪里。一位朋友虽然住在北卡,但邮件服务器部署在西雅图,邮件同样无法送达。

最终,发现问题出在一次系统升级:
顾问升级 SunOS 时,意外将新版 Sendmail 降级成了旧版本,却继续使用新版配置文件。
旧版程序无法识别部分新配置项,便悄悄忽略了它们,导致 SMTP 连接超时时间异常变成了接近 3 毫秒。

3 毫秒究竟有多短?TCP 建立连接需要经过网络传输,而数据传输速度受光速和网络设备延迟限制。距离较近的服务器,TCP 握手还能勉强在超时前完成;距离更远时,程序已经主动放弃连接。于是,一个普通的配置兼容性问题,最终表现成了仿佛系统“认识地理距离”的 500 英里边界。

这个故事也成为网络排障的经典案例:系统表面的异常现象,背后往往不是玄学,而是软件配置、网络协议和物理规律共同作用的结果。
给开发者的另一个教训是:程序异常之后,系统保持静默,按照错误的规则运行,比报错糟糕得多。

#28 邮件域名认证机制

2024-03-03

说明:SMTP AUTH (PLAIN / LOGIN) 属于邮件客户端身份认证机制,与上面说的邮件域名认证机制(也叫做邮件认证机制,Email Authentication)是两码事。

邮件域名认证机制包括 SPF、DKIM、DMARC 三种:

  • SPF 校验(RFC 7208):当前 SMTP 会话的客户端 IP 是否被 MAIL FROM 域名授权发送邮件。
    PS:优先使用 MAIL FROM(Envelope-From)域名,若 MAIL FROM 为空(<>),则使用 HELO/EHLO 域名。

  • DKIM 校验:DKIM 通过 DKIM-Signature 头中的 d=(Signing Domain,署域)和 s=(Selector)从 DNS 获取公钥,对指定的 header 和 body 进行签名校验。

    dig txt selector._domainkey.signing-domain
    
  • DMARC 校验(RFC 7489 Domain-based Message Authentication, Reporting, and Conformance (DMARC)):

    DMARC 在 SPF、DKIM 基础上引入了域名 “对齐” (Alignment) 概念,要求邮件 FROM 头域名必须与 SPF 校验所使用的域名 或 DKIM 署域 中的至少一个保持对齐(一致),实践中通常是两者同时保持对齐。
    PS:严格对齐就是要求域名部分完全一致,宽松对齐就是仅要求主域名 (eTLD + 1) 一致,对应 DMARC 记录的 aspf/adkim TAG 的值 r(relaxed mode)与 s(strict mode),如果没有指定,默认就是宽松对齐,实际上主流 MP 都没有指定。

    此外,DMARC 还提供了如果校验失败的处理策略框架 (none 监控 / quarantine 隔离 / reject 拒绝) 与反馈机制 (rua 汇总报告 / ruf 失败报告)。

#26 邮件地址黑名单

2023-12-25
  1. 退订
  2. FBL 举报
  3. 运营商反馈
  4. 收件人反馈(投诉)
  5. 地址不存在
  6. 邮箱容量已满
  7. 平台或客户手动指定的域名或地址
  8. 第三方提供的黑名单地址

有些是永久禁止,有些是临时禁止(比如一个月、三个月、半年)。

#24 SpamHaus

2023-12-05

关于 SpamHaus

Wikipedia:The SpamHaus Project

Spamhaus Project 是一个位于安道尔公国(法国和西班牙中间,498 平方公里)的国际组织,由 Steve Linford 于 1998 年创立,旨在跟踪垃圾邮件发送者和垃圾邮件相关活动。
SpamHaus 这个名字是一个伪德语表达,由林福德创造,指的是向垃圾邮件发送者发送垃圾邮件或故意向垃圾邮件发送者提供服务的互联网服务提供商或其他公司。

该组织维护了几个反垃圾邮件列表,被很多邮件服务采用。
这个列表通过基于 DNS 的黑名单(DNSBL)和白名单(DNSWL)提供,对个人和中小企业免费,大量查询使用应该是有付费服务(采用 rsync 订阅)。
SpamHaus 提供了文档《Understanding DNSBL Filtering》讲解其 DNSBL 的工作原理。

主要列表

  • SBL:Spamhaus Block List,发送垃圾邮件 IP
    • BCL:Spamhaus Botnet Controller List,僵尸网络
    • CSS:Combined Spam Sources,属于 SBL
    • DROP:Don't Route Or Peer,直接丢弃所有流量 IP,提供给防火墙使用,属于 SBL
  • XBL:Exploits Block List,被劫持机器 IP
  • PBL:Policy Block List,未通过身份验证滥发邮件 IP,可以自助移除
  • ZEN:SBL + XBL + PBL
  • DBL:Domain Block List,域名级别拉黑

#23 关于自托管邮件服务

2023-06-10

看到科技爱好者周刊推荐的一篇文章,介绍了自托管邮件服务的一些现状,主要是 Gmail 这样的主流邮箱服务提供商(MSP)拒收来自自托管邮件服务的邮件(或标记成垃圾邮件),导致自托管邮件服务的运营遇到很大的困难。

电子邮件在因特网没有出现之前就已经诞生,简单、开放,易于开发和使用,人人都能成为 Email 网络中的一个节点。实际上,大部分人都是使用的一些大 MSP 的服务,但也有部分人(或者组织)使用的是自己部署的邮件服务。他们会发现哪怕所有应该做的都做了,比如 SPF,DKIM,DMARC,他们的邮件还是经常无法正常投递出去(被拒、限流等),或者在收件人的垃圾文件夹中。

在一定程度上,MSP 的做法也是可以理解的,垃圾邮件泛滥成灾,确实防不胜防。因为邮件服务本身是毫无门槛。除非上一个手机实名制这样的严格管控,或许能解决这个问题。

文章提出的主要价值点是,什么情况下我们有必要自建邮箱服务?

  1. 准备好投入很多时间和精力来维护这套系统
  2. 搭建系统
  3. 留意 SPF 和 DMARC 报告
  4. 有服务器管理能力(Linux,Docker)
  5. ISP 支持开放 25、143、465、587、993 端口
  6. 静态 IP + rDNS 配置权限
  7. 一个合适的域名

#22 SMTP 校验主机域名

2023-02-15

Python

import smtplib
import ssl

host = 'smtp.126.com'
s = smtplib.SMTP(host)
context = ssl.create_default_context()
context.check_hostname = True
s.starttls(context=context)
s.quit()

Golang

如果服务器支持 STARTTLS,标准库 net/smtp 的 SendMail 方法就会校验主机名。

package main

import (
    "crypto/tls"
    "fmt"
    "net/smtp"
)

func main() {
    host := "smtp.126.com"
    port := 25
    c, err := smtp.Dial(fmt.Sprintf("%s:%d", host, port))
    if err != nil {
        panic(err)
    }
    tlsConfig := &tls.Config{ServerName: host}
    if err := c.StartTLS(tlsConfig); err != nil {
        panic(err) // panic: x509: certificate is valid for xxx, not yyy
    }
    if err = c.Quit(); err != nil {
        panic(err)
    }
}