网易首页 > 网易号 > 正文 申请入驻

一封欢迎邮件,为什么被两个邮件服务商绑死了?

0
分享至

周三下午,一个开发者把 main.ts 跑了二十遍。每跑一次,收件箱里就多一封真实的欢迎邮件。同事用自己的地址测试,也被塞了一堆。Resend 免费额度的计数在往上跳,而如果有人忘了配 RESEND_API_KEY,应用直接崩。


这段代码最初只有几行:引入 Resend 客户端,调用 emails.send,把发件人、收件人、标题、正文塞进去。跑通,邮件一秒后到达。看起来没有任何问题。

问题出在它跑通之后。测试流程需要反复触发注册,真实发信就成了负担。理想状态是:生产环境发真邮件,其他地方只让邮件停在本地,看得见、发不出去。

Mailpit 正好干这件事。它是一个假的 SMTP 服务器,带一个网页界面,应用“发出”的每封信都能在里面读到。用 Docker 一行配置就能起:映射 1025 端口给应用说话,8025 端口给自己看信。启动后打开本地页面,收件箱是空的。

因为 Mailpit 说的是 SMTP,用 Nodemailer 就能连上。于是发信函数里多了一个分支:生产环境走 Resend,其余走本地 SMTP。刷新页面,邮件出现了,问题似乎解决了。

但这段代码读起来有点不对劲。sendWelcomeEmail() 本该只负责“欢迎”这件事,现在它同时知道 Resend、Nodemailer、SMTP 端口和 NODE_ENV。下一个函数 sendPasswordResetEmail() 需要抄一遍同样的 if/else,再下一个也一样。想换第三个服务商,就得在项目里把每个 if 找出来。更隐蔽的是,Resend 客户端在模块导入时就被创建,哪怕这次根本不会用到它——那个“忘了 API key 就崩”的毛病,正是从这里来的。

这就是紧耦合的样子:一封欢迎邮件被粘在两个服务商和一个环境变量上,动其中一个,另外两个都得跟着动。

修法的第一步,是先说清楚需要什么,而不是谁来干。应用真正需要的只有一件事:把邮件发出去。于是定义一个 Mailer 接口,只包含一个 send 方法,参数是收件人、标题、正文。注意这里没有发件人字段——允许从哪个地址发,是服务商知道的事,应用不需要操心。

第二步,给每个服务商一个自己的盒子。ResendMailer 和 MailpitMailer 各自实现 Mailer,各自在构造函数里创建自己的客户端。整个项目里,只有这两个文件知道对应服务商的存在。

第三步,欢迎邮件回到无聊状态。它只接收一个 Mailer,调用 send,然后结束。导入列表里没有 Resend,没有 Nodemailer,没有 process.env。

第四步,把“选谁”这件事收到唯一的入口。应用启动的地方判断一次环境,生产环境就 new 一个 ResendMailer,否则 new 一个 MailpitMailer,然后把它传给发信函数。本地跑,邮件进 Mailpit 的网页界面,不需要 API key,不需要联网;带上生产环境变量跑,同一份代码、同一个函数,收件箱里收到真信。

这样改完,账可以一笔一笔算:

  • sendWelcomeEmail() 只剩一个变更理由——欢迎邮件的内容本身,与服务商和环境无关。
  • NODE_ENV 的判断在整个项目里只出现一行。再加十种邮件,这行也不用动。
  • 想接第三个服务商,写一个新类,改 main.ts 里的一行,其他代码甚至不知道发生过这件事。
  • Resend 的 SDK 只被 ResendMailer 导入。不创建它,就永远不碰它,那个崩溃顺带消失了。
  • 测试变得极其简单:传一个把邮件塞进数组的 Mailer,直接对数组断言,不需要 mock 库,不需要网络。

这就是松耦合的实际含义。欢迎邮件和 Resend 原本是粘在一起的,现在它们只是两个碰巧都认可 Mailer 这份小契约的东西。欢迎邮件不知道 Resend 存在,Resend 也不知道欢迎邮件存在,main.ts 是唯一把它们介绍给彼此的角色。

为两行邮件加这些结构,看起来像多余的仪式。但等到某个周五下午必须换掉服务商、而只有一处文件需要改的时候,这套东西的价值就显出来了。

顺带一提,Resend 免费额度有个限制:只能从 onboarding@resend.dev 发给你注册时用的那个邮箱。测试时把收件人设成它,就不用先验证域名。

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
3-1!39岁梅西4分钟双响 生涯936球获评10分 豪取3连胜

3-1!39岁梅西4分钟双响 生涯936球获评10分 豪取3连胜

念洲
2026-10-18 09:48:44
怕被中方拒之门外,高市早苗参加APEC峰会前,先给自己留好退路

怕被中方拒之门外,高市早苗参加APEC峰会前,先给自己留好退路

梁伫爱玩车
2026-10-17 20:36:29
男子逼好友睡他妻子,2010年好友不想睡了,他将好友的妻子和孩子杀死

男子逼好友睡他妻子,2010年好友不想睡了,他将好友的妻子和孩子杀死

汉史趣闻
2026-10-17 19:38:38
从“政府来养老”到“第一责任人”:谁在自力更生,谁在稳享保障?

从“政府来养老”到“第一责任人”:谁在自力更生,谁在稳享保障?

马小白
2026-10-17 17:04:07
“这个我们真没有……”李斌、何小鹏及车圈大V集体发声,“特斯拉会飞”三大疑问待解

“这个我们真没有……”李斌、何小鹏及车圈大V集体发声,“特斯拉会飞”三大疑问待解

极目新闻
2026-10-16 22:20:21
中国将迎来史上最大规模的退休潮。

中国将迎来史上最大规模的退休潮。

荆楚寰宇文枢
2026-10-10 22:52:15
命里有时终须有,命里无时莫强求。广州业主23年950万卖掉137方三房,26年买家断供房子法拍只值500万,银行希望他能半价买回去

命里有时终须有,命里无时莫强求。广州业主23年950万卖掉137方三房,26年买家断供房子法拍只值500万,银行希望他能半价买回去

捣蛋窝
2026-10-18 06:55:22
未来可期!14岁李昊炎巴萨生涯首次戴帽:半场3球 连轰世界波

未来可期!14岁李昊炎巴萨生涯首次戴帽:半场3球 连轰世界波

念洲
2026-10-18 06:54:03
详解特斯拉会飞的汽车:自己给高压空气罐推进器充气,倒挂爬升用的是风扇吸力

详解特斯拉会飞的汽车:自己给高压空气罐推进器充气,倒挂爬升用的是风扇吸力

红星新闻
2026-10-16 19:09:29
徐浦大桥违规变道的小车司机,
这事尘埃落定之后要面临 4 个窘境

徐浦大桥违规变道的小车司机, 这事尘埃落定之后要面临 4 个窘境

皮蛋儿电影
2026-10-18 09:01:05
首款陪伴机器人上市,配备160余种交互姿势,或成下一个万亿风口

首款陪伴机器人上市,配备160余种交互姿势,或成下一个万亿风口

疯狂小菠萝
2026-10-17 15:39:06
英国小两口在拼多多买了个花园拱门,收到包裹懵逼了!网友:这就是为什么有些人不该获得选举权的原因...

英国小两口在拼多多买了个花园拱门,收到包裹懵逼了!网友:这就是为什么有些人不该获得选举权的原因...

悦居英国
2026-10-16 18:52:20
不可思议!疯狂小杨哥换新号复出,一日涨粉10万,网友:涨粉再快,也回不到之前上亿的粉丝量了

不可思议!疯狂小杨哥换新号复出,一日涨粉10万,网友:涨粉再快,也回不到之前上亿的粉丝量了

火山詩话
2026-10-18 07:10:10
日本网友让志田千阳“滚出日本、入籍中国”,结果她真的来了

日本网友让志田千阳“滚出日本、入籍中国”,结果她真的来了

叒女紫121
2026-10-15 17:59:37
搅屎棍开始发力了!

搅屎棍开始发力了!

胖胖说他不胖
2026-10-18 10:00:31
今天10月18日,全体退休人员注意:10月养老金发放,必查三项!

今天10月18日,全体退休人员注意:10月养老金发放,必查三项!

老孙说
2026-10-18 09:15:14
悲催!一对孟加拉夫妇被砍头,在沙特阿拉伯清真寺内实施“不道德行为”,相关视频在网络传播

悲催!一对孟加拉夫妇被砍头,在沙特阿拉伯清真寺内实施“不道德行为”,相关视频在网络传播

火山詩话
2026-10-18 06:42:43
普京已明确表示:俄罗斯已接管法国、德国、瑞典、比利时等欧洲四国企业在俄罗斯的资产,作为欧盟支持乌克兰直接参与战场敌对行动的回应

普京已明确表示:俄罗斯已接管法国、德国、瑞典、比利时等欧洲四国企业在俄罗斯的资产,作为欧盟支持乌克兰直接参与战场敌对行动的回应

吉刻新闻
2026-10-17 16:15:36
太离谱了!婚期将近,男方亲戚两次致电让未过门的女生前去帮忙,男友就在身边没阻拦,女生选择推迟订婚

太离谱了!婚期将近,男方亲戚两次致电让未过门的女生前去帮忙,男友就在身边没阻拦,女生选择推迟订婚

火山詩话
2026-10-18 08:15:10
普京:包围并消灭2000人的乌军集群,且在460平方公里区域内实施包围,这非比寻常;乌克兰打击“俄罗斯的谷歌”,互联网成新战场前线

普京:包围并消灭2000人的乌军集群,且在460平方公里区域内实施包围,这非比寻常;乌克兰打击“俄罗斯的谷歌”,互联网成新战场前线

扬子晚报
2026-10-17 19:49:23
2026-10-18 11:15:00
像素与芯片
像素与芯片
有态度网友ytd
216文章数 80关注度
往期回顾 全部

科技要闻

王江舟院士:6G到底是不是骗局 是不是收割

头条要闻

牛弹琴:法国又"燃烧"了 整个国家地图上到处都在冒烟

头条要闻

牛弹琴:法国又"燃烧"了 整个国家地图上到处都在冒烟

体育要闻

升级版杨瀚森再闯难关:当上开拓者第三中锋还得拼

娱乐要闻

曝郭麒麟与25岁知名女星已经结婚,女方回应关系

财经要闻

马斯克:美债问题只能靠AI和机器人解决

汽车要闻

星瑞L PLUS限时售价10.97万元起 开启智混3L时代

态度原创

房产
数码
教育
时尚
健康

房产要闻

利好密集落地+现房好房断档,广州楼市买入窗口,正在倒计时!

数码要闻

极夜带来7.3L迷你工作站DeskOne W2,支持第12~14代英特尔桌面处理器

教育要闻

样本|区域弘扬教育家精神的广州探索

伊姐周六热推:电视剧《喜剧之王》;电视剧《把黑夜点燃》......

看着很健康的人,怎么会突然猝死?

无障碍浏览 进入关怀版