我们如何在 Cloudflare 上构建安全的 Beta 等候名单
只有当邮箱地址真实有效时,Beta 等候名单才是一项业务资产。
大多数团队会从一个邮箱输入框和提交按钮开始。对于落地页实验来说,这样就够了。但当名单开始影响发布计划、邀请批次、投资者更新或产品决策时,这远远不够。如果任何人都能提交他人的地址,或者机器人可以在一夜之间填满数据库,团队得到的就不是可用信号,而是嘈杂的需求数据。
这是我在 Cloudflare 上构建面向生产环境的 Beta 等候名单时采用的方法:双重选择加入、服务端机器人检查、短时有效的验证链接、哈希令牌、通过现有邮件托管服务发送 SMTP 邮件、将已验证记录与历史记录分开的管理员默认设置,以及不会假装已配置基础设施已经上线的发布门禁。
最近的一次 Beta 等候名单实施在真实交付中验证了这一方法,而且不需要完整的账户系统。真正有价值的是工作的结构:一个小型注册界面,以及围绕同意、滥用、邮件投递、数据质量和部署建立的明确边界。
安全等候名单解决的业务问题
等候名单有两个作用。
它应该收集需求,也应该帮助团队根据这些需求采取行动。第二部分正是薄弱表单容易失败的地方。
如果你无法信任名单,每一次后续跟进都会变慢。你会怀疑有多少记录来自机器人。发送邀请邮件前会犹豫。你导出数据、手动清理,但仍然不知道这个人是否控制该邮箱地址。名单最终会变成粗略的虚荣指标,而不是发布工具。
安全的 Beta 等候名单可以提供更干净的输入:
目标不是让一个简单表单变得复杂,而是让表单表达的含义与业务的理解一致。
Cloudflare 架构
核心技术栈有意保持精简。
Cloudflare Workers 处理请求。Cloudflare Turnstile 检查注册行为是否像真人。D1 存储等候名单记录和待验证记录。现有 SMTP/邮件托管服务通过 TLS 发送确认邮件。定时 Worker 清理任务会删除已过期的待验证记录。
对于许多早期团队来说,这套技术栈已经足够。你不需要仅仅为了收集一份负责任的 Beta 名单,就增加完整的用户账户系统。
流程如下:
这样,产品团队就能清楚区分:待确认的兴趣不等于已验证的需求。
双重选择加入是一项产品决策
双重选择加入通常被视为邮件卫生措施。我认为它也是产品卫生措施。
如果 Beta 名单将决定谁最先获得访问权限,团队就应该知道每个地址都属于一个已经确认过的人。24 小时的确认窗口足以应对正常注册,同时也足够短,可以避免过期的待验证记录长期存在。
确认链接本身不应直接改变状态。链接扫描器、邮件预览和意外的 GET 请求都真实存在。更安全的模式是让链接先渲染确认页面,然后要求用户点击按钮,通过同源 POST 提交确认。
多出的这一次点击很小,但边界很有用。
这也能让支持工作更清晰。如果有人说自己从未注册过,你可以说明该流程要求访问邮箱,并在地址进入已验证名单前执行明确的确认操作。
在不让表单令人反感的情况下防护机器人
好的等候名单不应该让人感觉像在参加安全考试。
防护措施应该主要隐藏在表单之后。在这次构建中,Worker 在服务端验证由 Cloudflare 管理的 Turnstile 小组件。验证会检查令牌、预期操作和预期主机名。没有服务端验证的浏览器小组件只是装饰;必须由 Worker 完成验证。
Turnstile 只是其中一层。表单还使用蜜罐字段进行低成本机器人检测,限制请求体大小以避免超大负载浪费 Worker 资源,使用基于密钥的 IP 速率限制,以及基于密钥的单地址尝试次数限制。
单地址限制很重要,因为邮箱验证可能被用来骚扰他人。你不希望有人反复触发发送到同一个收件箱的确认邮件。
公开响应保持通用。它不应透露某个地址是否已经存在、正在等待验证、已被抑制,或触发了某项限制。这样可以避免注册端点变成邮箱枚举工具。
令牌存储:对你发送的内容进行哈希
验证链接很敏感,因为它们可以证明用户能够访问邮箱。
实现会在邮件链接中发送随机令牌,但 D1 只存储该令牌带密钥的哈希值。确认时,Worker 会对提交的令牌进行哈希,并与存储的哈希值比较。原始令牌不会存放在数据库中。
这种设计在保持系统简单的同时降低了影响范围。如果待验证表泄露,攻击者也无法直接获得可使用的确认链接。
令牌只能使用一次。确认成功后,Worker 会删除待验证记录。过期的待验证记录会在正常的等候名单操作中顺便清理,并通过每日运行的 Cloudflare Cron 进行定期清理。
这条清理路径可以让表保持较小规模,无需手动处理数据库。
通过现有邮件托管服务发送邮件
许多团队已经拥有邮件托管服务。对于 Beta 等候名单来说,他们不一定需要新的事务性邮件供应商。
在我验证过的实现中,团队在现有邮件托管服务上创建了专用发件地址,Worker 通过 TLS 使用 SMTP 发送邮件。邮件内容很简单:确认 Beta 注册,链接在 24 小时内有效;如果不是你发起的请求,请忽略此邮件。
这已经足够完成任务。
价值不在于精美的邮件设计,而在于明确的发件人、清晰的用途,以及可以在发布前进行冒烟测试的投递路径。对于初创公司来说,在产品甚至还没有用户之前,这通常比增加另一个供应商更合适。
管理员视图应该帮助团队避免错误假设
安全性不只体现在公开端点上。
管理员视图需要反映数据契约。默认应显示已验证地址。较早的未验证记录或历史记录可以继续保留,但必须通过明确的筛选条件才能查看。CSV 导出也应遵循同样的规则。
这样可以避免一个常见的发布错误:导出所有历史记录,并把它们当作已确认的需求。
在最近的一次实现中,生产名单在新的验证模型上线前就已经存在历史地址。迁移计划会将这些地址保留为历史记录,而不会仅仅因为新系统现在有了已验证状态,就悄悄把旧记录标记为已验证。
这就是迁移与改写历史之间的区别。
已配置不等于已上线
这是我向其他团队交付服务时会坚持的边界。
邮箱可以已经存在。Turnstile 小组件可以已经存在。Worker secret 名称可以已经配置。测试也可以全部通过。但这些都不意味着公开网站已经运行新的等候名单。
对于当前的参考实现,双重选择加入流程已经完成,并在本地验证通过。待执行的 D1 迁移、新的 Worker 版本以及定时清理 Cron 仍然需要发布批准和部署。在这些工作完成之前,公开网站仍然运行旧的等候名单表单。
这种区分可以保护业务。D1 迁移会改变生产数据结构。Worker 部署会改变注册行为。Cron 会增加后台数据变更。每一步都需要明确的发布窗口、验证流程和回滚考虑。
安全等候名单不应该因为名称中带有“安全”二字,就被随意发布。
验证工作是什么样的
对于面向生产环境的等候名单交付,我希望在发布前看到证据。
参考实现通过了 150 项测试。Astro check 报告 0 个错误。生产构建成功。由于这项工作仅涉及 Web,因此现有的原生应用改动保持不变。
即使完成这些工作,发布清单仍然很重要:
这就是“代码可以编译”和“注册漏斗已经准备好承接流量”之间的区别。
这对初创公司有什么帮助
当团队即将进入 Beta 阶段,但还没有准备好构建完整账户系统时,这种模式很有用。
你可能正在发布移动应用、SaaS 工具、私有 Alpha、受限的 AI 功能,或硬件预订名单。你需要收集需求,但在开始发送邀请之前,也需要干净的数据和明确的同意。
安全的 Cloudflare 等候名单无需增加大型后端,就能提供这些能力:
它足够小,可以快速发布;也足够严格,可以值得信任。
你的产品需要这个吗?
我可以帮助产品团队设计、保护或实现这类注册流程。
真正有价值的工作不是把 CAPTCHA 放到表单上,而是确定注册意味着什么、如何证明同意、令牌存放在哪里、邮件如何投递、管理员默认看到什么,以及如何在公开流量到来前验证发布结果。
如果你的 Beta 名单即将成为发布计划的一部分,那么在使用它做出决策之前,先让这份名单值得信任是很有必要的。
