#41 Email Verification API
Email WebDev 2026-09-18传统邮箱验证通常采用 Double Opt-In:用户提交邮箱后,服务端发送验证邮件,用户切换到邮箱客户端打开邮件,再点击链接返回网站完成验证。这个流程虽然可靠,但存在明显的行为断点。新的 Email Verification API 试图让浏览器在用户不离开当前页面的情况下完成邮箱所有权验证。
coding in a complicated world
传统邮箱验证通常采用 Double Opt-In:用户提交邮箱后,服务端发送验证邮件,用户切换到邮箱客户端打开邮件,再点击链接返回网站完成验证。这个流程虽然可靠,但存在明显的行为断点。新的 Email Verification API 试图让浏览器在用户不离开当前页面的情况下完成邮箱所有权验证。
对于 ESP(Email Service Provider)来说,邮件打开率长期以来都是最重要的用户行为指标之一。传统实现非常简单:在邮件 HTML 中插入一个 1×1 像素的远程图片,用户打开邮件时,邮件客户端加载这张图片,ESP 的服务器收到 HTTP 请求,就可以认为邮件发生了一次“打开”。
Resend 不是传统的企业邮箱服务,而是一个面向开发者的 Email Infrastructure 平台,定位类似 SendGrid、Mailgun、Amazon SES。
Stalwart Mail Server
https://github.com/stalwartlabs/stalwart
All-in-one Mail & Collaboration server. Secure, scalable and fluent in every protocol (IMAP, JMAP, SMTP, CalDAV, CardDAV, WebDAV).
Stalwart 是采用 Rust 开发的开源邮件与协作服务器,集成 SMTP、JMAP、IMAP、POP3、Sieve,以及 CalDAV、CardDAV、WebDAV 等协议,同时提供反垃圾邮件、反钓鱼、DKIM/SPF/DMARC/ARC、OIDC、2FA、Prometheus 等能力。它既可以单机运行,也支持进一步扩展到集群。
Bulwark Webmail
https://github.com/bulwarkmail/webmail
Self-hosted JMAP webmail for Stalwart Mail Server. Mail, calendar, contacts, and files in one client.
Bulwark 是面向 Stalwart 的现代化自托管 Webmail,基于 Next.js 开发,以 JMAP 作为主要通信协议。除了邮件,还提供日历、通讯录和文件管理,并支持 SSO/OIDC、2FA、多账号、全文搜索、Sieve、PWA 等功能。
可以采用 Stalwart Mail Server 作为邮件系统核心服务,Bulwark Webmail 作为统一 Web 邮箱前端,构建一套轻量、现代化、可自主管理的企业邮件平台。
整体方案的主要价值是减少邮件系统组件数量,同时保持标准协议兼容性。对于个人邮件、企业内部邮箱、小型邮件服务平台以及需要自建 Webmail 的场景,这套组合值得优先进行 PoC。
部署层建议采用 Docker Compose + Nginx。
DNS 配置 MX、SPF、DKIM、DMARC、PTR/rDNS,并根据实际业务配置 SMTP 出站策略。
mail.example.com
├── :25 SMTP
├── :587 Submission
├── :143 IMAP
├── :4190 Sieve 邮件过滤规则管理
└── :443 HTTPS(Nginx)
├── Stalwart JMAP
├── Stalwart CalDAV
├── Stalwart CardDAV
├── Stalwart WebDAV
└── Bulwark Webmail
存储方面,初期可采用单机部署,邮件数据、索引和附件使用本地磁盘(嵌入式 KV 数据库 RocksDB)。
| Type | Local | Cluster |
|---|---|---|
| Data Store | RocksDB | FoundationDB / PostgreSQL / MySQL |
| Blob Store | RocksDB | S3 |
| Search Store | RocksDB | Elasticsearch / Meilisearch / Data Store |
| In-Memory Store | RocksDB | Redis / Valkey / Memcache / Data Store |
可以通过 Docker 部署 https://hub.docker.com/r/stalwartlabs/stalwart,也可以直接在服务器上安装:
curl https://get.stalw.art/install.sh -o install.sh
sudo sh install.sh
对于邮件发送企业来说,最头疼的问题之一,是不知道用户为什么不再收到自己的邮件。邮件成功投递到收件箱,并不意味着发送任务结束:用户可能直接删除,也可能将邮件标记为垃圾邮件。后者会直接影响发送 IP、域名的信誉,严重时甚至导致邮件被拒收或进入黑名单。
MJML(Mailjet Markup Language)是一种专门用于编写响应式 HTML 邮件的标记语言。相当于是 HTML 在电子邮件场景下的更高层的封装,由编译器生成兼容各种邮箱客户端的 HTML。主要解决邮件 HTML 的痛点:
注意:MJML 只负责 UI 不含模板功能。如果需要使用变量或者其他模板语法,可以另外选择任何一种语言进行渲染,比如 Node Handlebars,Golang 模板,Python Jinja2 等。
MJML 最初由法国邮件服务公司 Mailjet 在 2015 年开源。目前项目已经独立维护,属于开源生态:
GitHub Repository: mjmlio/mjmlio
扩展名:.mjml
<mjml>
<mj-head>
<mj-title>Welcome</mj-title>
</mj-head>
<mj-body>
<mj-section>
<mj-column>
<mj-text font-size="20px">
Hello Woody
</mj-text>
<mj-button href="https://example.com">
Click
</mj-button>
</mj-column>
</mj-section>
</mj-body>
</mjml>
通过一下方法可以直接预览效果:
安装:
npm install -g mjml
转换:
mjml template.mjml -o template.html
生成的 HTML 会包含:
用 perl 开发的命令行工具 swaks 用来发送邮件挺方便的,但是有些环境不便安装,我问 AI,结果推荐了 curl 命令。
-> % curl --help smtp
smtp: SMTP protocol
--crlf Convert LF to CRLF in upload
-F, --form <name=content> Specify multipart MIME data
--form-string <name=string> Specify multipart MIME data
-H, --header <header/@file> Pass custom header(s) to server
--login-options <options> Server login options
--mail-auth <address> Originator address of the original email
--mail-from <address> Mail from this address
--mail-rcpt <address> Mail to this address
--mail-rcpt-allowfails Allow RCPT TO command to fail
--oauth2-bearer <token> OAuth 2 Bearer Token
-X, --request <method> Specify request method to use
--ssl Try enabling TLS
--ssl-reqd Require SSL/TLS
我用 curl 发送了几封邮件,还真的挺好的,满足我的要求。
生成邮件内容:
export FROM=noreply@example.com
export RCPT=ninedoors@126.com
export USER=shanda
export PASS=6404290f99f75bf182673bcbd012c121
# AI 生成验证码邮件模板
cat > /tmp/mail.html <<EOF
From: $FROM
To: $RCPT
Subject: Your Verification Code: CODE
Content-Type: text/html; charset=UTF-8
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport"content="width=device-width, initial-scale=1.0"><title>Verification Code</title><style>@media only screen and(max-width:600px){.container{width:100%!important}.mobile-padding{padding:10px!important}}</style></head><body style="margin: 0; padding: 10px; background-color: #f4f4f4; font-family: Arial, sans-serif;"><table role="presentation"align="center"border="0"cellpadding="0"cellspacing="0"width="100%"style="max-width: 600px; width: 100%; margin: 0 auto; background-color: #ffffff;"><tr><td class="mobile-padding"style="padding: 40px 20px;"><table role="presentation"width="100%"border="0"cellpadding="0"cellspacing="0"><tr><td style="padding-bottom: 30px;"><h1 style="color: #333333; margin: 0;">Verification Code</h1></td></tr></table><table role="presentation"width="100%"border="0"cellpadding="0"cellspacing="0"><tr><td style="padding: 20px 0;"><p style="color: #666666; line-height: 1.6; font-size: 16px;">Your verification code is:<h2 style="letter-spacing: 2px;">CODE</h2></p></td></tr></table></td></tr></table></body></html>
EOF
# 替换验证码内容,我不想每次发送相同内容
# sed "s/CODE/$(printf "%06d" $((RANDOM % 1000000)))/g" /tmp/mail.html > /tmp/mail2.html
sed s/CODE/$(python -c "import secrets; print('%06d' % secrets.randbelow(10**6))")/g /tmp/mail.html > /tmp/mail2.html
邮件发送:
curl smtp://smtp.engagelab.cc:2525 --mail-from nobody@whatever.com --mail-rcpt $RCPT --user "$USER:$PASS" --upload-file /tmp/mail2.html -vv -s 2>&1 | grep -Fv '] * '
遇到一个奇怪的现象:zsh 中 RANDOM 这样使用结果不会变更,没有时间研究,暂时忽略。
-> % echo $(printf "%06d" $((RANDOM % 1000000)))
018840
-> % echo $(printf "%06d" $((RANDOM % 1000000)))
018840
-> % echo $(printf "%06d" $((RANDOM % 1000000)))
018840
如果直接使用却会,不知道为什么:
for i in {1..5}; do echo $RANDOM done
JMAP(JSON Meta Application Protocol)是一套基于 JSON 和 HTTP 的现代数据访问与同步协议,由 Fastmail 团队推动并提交 IETF 标准化。JMAP Core 于 2019 年发布为 RFC 8620,邮件数据模型则定义于 RFC 8621,并进一步扩展到日历、联系人等场景。