2026.07.19
国内自动发 Gmail · 六种方案挂了五种
昨晚需要给 Creem 客服发一封邮件。写好稿子大概 15 分钟。发这封邮件花了一个多小时。SMTP 五种方案都挂,最后用 Cloudflare Worker + Resend 搭出一条永远稳的 HTTPS 通道。
昨晚需要给 Creem 客服发一封邮件。
不是复杂的邮件 · 一段说清楚 KYC 出了什么问题 · 附上账户信息和一份想请他们做的三个选项。写好稿子大概 15 分钟。
发这封邮件花了一个多小时。
一切都从 SMTP 开始
习惯性写法 · Python 加 smtplib · Gmail 应用专用密码。
import smtplib, ssl
s = smtplib.SMTP_SSL('smtp.gmail.com', 465, context=ssl.create_default_context())
s.login(user, app_password)
s.send_message(msg)
ssl.SSLEOFError: EOF occurred in violation of protocol
改 587 STARTTLS。SMTPServerDisconnected: Connection unexpectedly closed。
这不奇怪。国内电信/联通/移动的骨干网 · smtp.gmail.com 的三个端口 (25 · 465 · 587) 早年就已经在做深度检测。SMTPS 的 SNI 会暴露 smtp.gmail.com 域名 · GFW 直接 reset 掉。
我以为切代理就好。
Clash + curl 挂在 schannel
本机 Clash 我一直开着 · 平时看 Google 没问题。7897 端口是 HTTP 代理:
curl.exe -x http://127.0.0.1:7897 --url smtps://smtp.gmail.com:465 --ssl-reqd --mail-from ... --mail-rcpt ... --user user:pass --upload-file email.txt
Clash 那边 CONNECT tunnel 建立成功 · HTTP/1.1 200 Connection established。然后:
* schannel: failed to receive handshake, SSL/TLS connection failed
curl: (35) schannel: failed to receive handshake, SSL/TLS connection failed
Windows 自带的 curl.exe 用的是 schannel (Windows 原生 TLS)。schannel 对经过 HTTP CONNECT tunnel 之后再 wrap TLS 支持得很差。这是一个已知问题 · 网上讨论好几年。
想换成 OpenSSL 后端的 curl · 得装 git-for-windows 或者从源码编译。晚上不想折腾。
Python + PySocks 是骨折
想换个思路 · Python 加 PySocks · 设定全局默认代理:
import socks
socks.set_default_proxy(socks.HTTP, '127.0.0.1', 7897)
socket.socket = socks.socksocket
import smtplib
OSError: [Errno 9] Bad file descriptor
PySocks 在 wrapping SSL socket 时会重复 close · Python 3.9 在 Windows 下这个 bug 一直没修好。
手写 CONNECT tunnel · Python 3.9 wrap_socket 也翻
想更底层一点 · 自己开 socket · 手写 HTTP CONNECT · 拿到裸 TCP socket 之后 Python ssl.wrap_socket 一下:
sock = socket.create_connection(('127.0.0.1', 7897))
sock.send(b"CONNECT smtp.gmail.com:465 HTTP/1.1\r\nHost: smtp.gmail.com:465\r\n\r\n")
buf = sock.recv(4096)
# 拿到 "200 Connection established"
ctx = ssl.create_default_context()
ssock = ctx.wrap_socket(sock, server_hostname='smtp.gmail.com')
FileNotFoundError: [Errno 2] No such file or directory
在 do_handshake 那一步爆出来。查了一下 · Python 3.9 wrap_socket 传入一个已 connected 的 socket 时 · 内部会在 Windows 上试图访问一个不存在的证书路径。3.10 以后修了。我这台机器是 3.9。
Node nodemailer + Clash 也断
Node 用 OpenSSL 不用 schannel · 我以为能行。装 nodemailer + https-proxy-agent:
const transporter = nodemailer.createTransport({
host: 'smtp.gmail.com', port: 465, secure: true,
auth: {user, pass},
proxy: 'http://127.0.0.1:7897',
})
Client network socket disconnected before secure TLS connection was established
CONNECT tunnel 应该是建了 · 后面 TLS 就断。可能是 Clash 的规则里 smtp.gmail.com 这个域名没有被识别成需要走远端代理 · 走了 DIRECT · GFW 一看是 gmail 的 SMTP 就 reset。
手写 net + tls
Node 里再底层一层 · net.createConnection 到 Clash · 手写 CONNECT · 然后 tls.connect(socket):
import net from 'node:net'
import tls from 'node:tls'
const raw = net.createConnection(7897, '127.0.0.1', () => {
raw.write('CONNECT smtp.gmail.com:465 HTTP/1.1\r\n...')
})
// 等 200 · 然后 tls.connect({ socket: raw, servername: 'smtp.gmail.com' }, ...)
一样断。到 TLS 握手那一步就断。
最后终于用 Gmail Web 发出去
打开浏览器 · mail.google.com · 新邮件 · 粘 · 发送。三十秒。
一个多小时的技术折腾 · 终究是浏览器赢了。
Gmail Web 能发的原因和 SMTP 不能发的原因 · 是一个东西的两面 —— HTTPS to mail.google.com 用的是 TLS 1.3 · 服务端支持 Encrypted Client Hello (ECH) · 那个 SNI 加密的。GFW 看不到你连的是 mail.google.com。SMTP 那边握手明文暴露 SNI · 直接被识别。
后面搭出来的 Resend 通道
发完那封邮件我意识到 · 以后每次要发个正经邮件都这样打开浏览器复制粘贴 · 那我在做客户服务、发 receipt 邮件的时候 · 全部要人工。这不能忍。
后来搭出来的东西是这样:
- Cloudflare Worker 已经跑在 api.datenix.xyz 上
- 加一个新端点
/api/send-email· 头里带 X-Send-Token 校验 (32 位 hex 随机 · 放本机文件) - Worker 收到请求 · fetch 一下 api.resend.com/emails · 传发件人 / 收件人 / 主题 / body
- Resend 是家做 transactional email 的公司 · 免费额度 100 封 / 天
- 发件人默认用
Datenix Team <onboarding@resend.dev>· 是 Resend 自家共享 domain · 不用验证 · 回复地址还是 hi@datenix.xyz
我这边发起的是 HTTPS 到我自家域名 · 走 CF Anycast · Anycast 那边 SNI 是 api.datenix.xyz · GFW 不会拦自己域名。CF Worker 在东京边缘节点上跑 · 它 fetch 到 Resend 是完全境外流量 · 不经过 GFW。
一条从我 PowerShell 一句话发出去 → 到达邮箱的通道:
Send-DatenixEmail -To 'someone@example.com' -Subject 'hi' -Text 'body'
PowerShell 函数放在 E:\7\windows-tool\Send-Email.ps1 · . .\Send-Email.ps1 加载一下 · 别名 send-email。
关于 Resend
一个坑 · Resend 免费额度只给 1 个已验证的 sending domain。我朋友之前搭 game-web 的时候用 yeluoxiaoxiao.top 验证了。datenix.xyz 想再加 · 免费额度不够。三条路:
- 删掉 yeluoxiaoxiao.top · 加 datenix.xyz
- 升级到 20 美金 / 月 Pro 计划
- 用
onboarding@resend.dev共享域
我先选第三条。等收入正式起来再决定要不要给每个品牌单独验证 domain。品牌邮件从 xxx@resend.dev 发确实没那么专业 · 但比不发好。等这个 SaaS 一个月能挣 500 美金再花 20 美金升级 · 逻辑对得上。
从这一次学到的
一个 · 独立开发者不该假设「浏览器能通 SMTP 也一定能通」。GFW 对不同协议的态度不一样 · HTTPS + SNI 加密最宽松 · SMTP 三个端口都在监控。
二个 · 客户服务、transactional email 这一层基础设施 · 必须一开始就搭。挂了一晚上救急我才反应过来 · 以后每次都靠人工发根本 scaling 不起来。
三个 · Cloudflare Worker + Resend 这个组合是国内独立开发者的黄金搭档。全程 HTTPS · 一个端点 · 免费到 100 封 · 平时的 KYC 沟通 · welcome 邮件 · receipt · winback 全能覆盖。
四个 · Windows 自带 curl 用 schannel 这件事 · 让我这类跨环境的场景很难做。之后我在这台机器上应该装一个 OpenSSL-based curl。
那封邮件后来怎么样
Creem 客服周一早上回信了。事情比我想的简单。他们直接把老 store 里的 Sumsub applicant 记录 reset 了 · 让我在 datenix store 重新走一遍 KYC。
那一封邮件写得清楚可能起了作用 —— 里面账户信息、已完成的技术工作、想要的选项 (Reset applicant / Merge / Delete old stores) 三张小表 · 客服基本上直接选了 Reset 那一条 · 回信只有一句「Done · please retry」。
比我预期快很多。
一个人做产品能省人力最大的地方 · 不是自动化 · 是把每一次和外部方的沟通都做到「对方选 A/B/C」而不是「对方还要想」。这个道理跟前面搭 Resend 通道其实是同一个 —— 消除中间环节的不确定性 · 让每一步都是可选可执行的。