截至本周,Mediocre 博客有一个新的关注机制:电子邮件!登录“关注”页面,每次在博客上发布新帖子时,您都会收到一封电子邮件。它就像 RSS,只是你可能真正使用它的机会很小。这篇文章将详细介绍我相对简单的设置,链接到我博客服务器代码中的相关点。虽然我没有刻意将我的代码打包成一个很好的公共包,但如果你对 Go 有一些粗略的了解,你可能会翻录我的代码并让它为你工作。别担心,它有一个宽松的许可证。对于大多数开发人员来说,自托管电子邮件是整个过程中最难和最陌生的部分。总而言之,如果不在某处租用 VPS,您就不太可能做到这一点。幸运的是,有一些便宜的 VPS 允许 SMTP 流量,所以这真的只是一个咬成本子弹的问题,让你对“自托管”的定义有点灵活。至少你还在控制代码!我强烈推荐 maddy 作为电子邮件服务器,它具有开箱即用的所有功能,没有 docker 要求,以及灵活而简单的配置语言。我在之前的帖子中讨论了我用来在远程 VPS 上设置 maddy 的一般步骤,所以我不会在这里重复。只知道我的私人星云 VPN 上有一个 VPS,一个 maddy 服务器在端口 587 上侦听外发邮件,并在该端口上进行用户名/密码身份验证。系统的其余部分位于托管我的博客的 Go 服务器中。服务器只有一个实例,它在我的客厅里运行。有了这些基线环境要求,设计的其余部分很容易遵循: POST /api/mailinglist/subscribe:接受 POST 表单参数电子邮件,向该电子邮件地址发送验证电子邮件。 POST /api/mailinglist/finalize:接受一个POST表单参数subToken,这是用户订阅时发送给用户的随机令牌。只有完成他们的订阅,用户才能被视为实际订阅。
POST /api/mailinglist/unsubscribe:接受 POST 表单参数 unsubToken,它与每个博客帖子通知一起发送给用户。 /mailinglist/finalize.html:发送给用户的验证邮件链接到这个页面,subToken 作为 GET 参数。此页面然后将 subToken 提交到 POST /api/mailinglist/finalizeendpoint。 /mailinglist/unsubscribe.html:发送给用户的每封博客文章通知电子邮件都包含一个指向此页面的链接,其中一个 unsubToken 作为 GET 参数。此页面然后将 unsubToken 提交到 POST/api/mailinglist/unsubscribe 端点。这是一个非常小的 API,但它涵盖了所有重要的事情,即验证(因为我不希望人们违背自己的意愿注册,也不想向虚假的电子邮件地址发送电子邮件)和取消订阅。对我来说很重要的是,有人不能只是坐下来循环向 POST /api/mailinglist/subscribe 端点提交随机电子邮件,导致我的电子邮件服务器最终被列入黑名单。为了防止这种情况,我实现了一个简单的工作量证明 (PoW) 系统,其中客户端必须首先获得一个 PoWchallenge,为该挑战生成一个解决方案(涉及大量 CPU 时间),然后将该解决方案作为订阅端点调用。服务器端和客户端代码都可以在博客的 git repo 中找到。理论上你可以在 pkg.go.dev 上查看服务器代码的 Go 文档,但显然他们的机器人不喜欢我的 WTFPL。当向客户端提供质询时,服务器会发回两个值:seed 和目标。
当客户端提交一个有效的解决方案时,服务器会检查 HMAC 以确保种子是由服务器生成的,它会检查过期时间以确保客户端没有花太长时间来解决它,并且它会在内部存储中检查该种子是否有还没解决。因为过期是内置在种子中的,所以服务器不必永远存储每个已解决的种子,直到种子过期。如果该 uint32 小于目标,则在第一步中生成的随机字节是有效的解决方案。否则客户端循环回到第一步。最后,添加了一个新的端点:GET /api/pow/challenge,它返回一个 PoWseed 和目标供客户端解决。由于种子在解决之前不需要存储在数据库中,因此对于在循环中发送垃圾邮件的人来说基本上没有任何后果。有了所有这些,之前描述的 POST /api/mailinglist/subscribe 端点现在还需要一个 powSeed 和一个 powSolution 参数。 Follow页面,在提交订阅请求之前,首先检索一个PoW挑战,生成一个解决方案,然后才会提交订阅请求。电子邮件的存储相当简单:因为我没有在多台主机上运行这个服务器,所以我可以只使用 SQLite。我的 SQLite 存储代码都可以在这里找到。 CREATE TABLE emails ( id TEXT PRIMARY KEY, email TEXT NOT NULL, sub_token TEXT NOT NULL, created_at INTEGER NOT NULL, unsub_token TEXT,verified_at INTEGER) 它可能有一天需要一个关于 sub_token 和 unsub_token 的索引,但我还没到.
id 字段是通过首先将电子邮件小写(因为电子邮件不区分大小写)然后对其进行散列来生成的。通过这种方式,我可以确保轻松识别重复项。有人仍然可以通过 + 技巧多次获取他们的电子邮件,但只要他们验证每一个我并不在乎。发布非常简单:我的 MailingList 接口上有一个 Publish 方法,它循环遍历 SQLite 表中的所有记录,丢弃那些未验证的,并向其余部分发送一封电子邮件,其中包含:然后我将使用命令行调用此 Publish 方法的接口。我还没有真正制作那个界面,但还没有人订阅,所以没关系。整个过程中最难的部分可能是设置 maddy,第二个是尝试在 javascript 中将十六进制字符串解码为字节字符串(我尝试过 Crypto-JS,但如果不拖入 webpack 或一堆其他废话,香草JS没有任何方法可以做到!)。希望阅读本文能让您考虑自行托管您自己的博客邮件列表。如果我们让这些大公司继续接管所有互联网功能,那么最终他们会制定标准,以便没有人可以自行托管任何东西,我们将不得不重新开始。真的,您是否需要在为您的食谱博客发送的电子邮件中跟踪代码?只需让您的用户安静地忽略您即可。