营销邮件

背景

公司要发营销邮件,总共有107万封邮件,然后需要在早上9点之前发完。然后呢,这又不是一个永久性的功能,是一次性的,所以只是简单的用jar运行,然后触发接口进行发送。整体而言是没有什么难度的。

邮件服务厂商

邮件服务厂商是Brevo。

api文档:Schedule transactional emails.

整个项目做下来对brevo提供的接口的吐槽点:

  1. brevo的接口文档没有返回值的说明,是自己调用他们接口根据返回值进行猜测的。
  2. 返回值中没有表示这个邮件是否发送成功的字段,需要自己在他们给定的页面设置一个回调接口。EMAIL | Send a transactional message
  3. 如果账号里没钱了,调用这个发送接口依然会给你一个messageId,但是后续不会对这个messageId有任何回调。简而言之就是发送接口的返回值骗你说:"我发送成功了,等着被回调吧。" 其实brevo实际上的处理是不给目标对象发送的,因为你账户没钱了。直接把这个邮件给丢了,这属实太狗了。

ER图设计

order_email 是邮件发送主表:从线上订单的备份库里导出的近几年购买人员的邮箱信息。 send_history 是发送记录表,每发送一个邮件,将调用发送邮件的返回值记录到这个表里。 hook_history 是回调记录表,根据mail和messageId确认一个唯一发送人,将order_email的delivery_status字段改成1.

技术方案设计

再次强调一次业务需求点:需要在2-3小时内发送结束,尽量越短越好,不超过早上9点。

由于brevo的发送api支持定时发送功能(提供了一个scheduled_at字段)。

所以我们只需要将待发送的107万封邮件在目标发送时间之前发送给brevo服务商就好了。

但是 ------ 前面提到brevo发送成功与否的标识是通过回调接口来确认的。而我们的项目只是一个简单的jar包形式启动的。就假设 3小时107w封邮件都发送成功,那qps是:1070000/(3600*3) = 99 不到 100.

一个普通jar包顶着qps为100的流量,并且持续3小时,这肯定是行不通的。

so......

需要部署多个jar包来分摊这些qps:用nginx做负载均衡,在brevo设置回调接口的地方将nginx对外的公网地址填进去,然后在nginx里设置四个jar包进程的回调地址。

brevo webhook地址设置

贴一个nginx配置文件:

nginx.conf 复制代码
http {
    # 定义 upstream 块,用于配置后端服务器组
    upstream email_hook_backend {
        # 轮询策略,依次将请求分发到各个后端服务器
        least_conn; 
        server 10.100.4.35:9050;
        server 10.100.4.35:9051;
        server 10.100.4.35:9052;
        server 10.100.4.35:9053;
    }

    server {
        listen 80;
        server_name your_domain_or_ip;

        location /order-mail-info/email/hook {
            # 将请求代理到 upstream 定义的后端服务器组
            proxy_pass http://email_hook_backend;
            # 设置请求头,将客户端的真实 IP 传递给后端服务器
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }
}

技术方案设计图:

相关推荐
IT_陈寒40 分钟前
Vue这个特性差点让我加班到凌晨,谁懂啊
前端·人工智能·后端
段一凡-华北理工大学44 分钟前
向量数据库实战:选型、调优与落地~系列文章12:文本分块策略实战:chunk_size 怎么选?重叠多少?
开发语言·数据库·后端·oracle·rust·工业智能体·高炉智能化
码事漫谈1 小时前
1999年的电商前夜和2026年的AI前夜 历史押韵但从不重复
后端
Conan在掘金2 小时前
鸿蒙报错速查:arkts-strict-typing Property does not exist on type 'object',object 装函数就炸,根因 + 真解法
后端
颜酱2 小时前
01 | 骨架搭建:FastAPI + Vue 跑通第一个 SSE 流式问答
前端·人工智能·后端
程序员cxuan3 小时前
Grok Build 被众人唾骂,结果老马把它开源了
人工智能·后端·程序员
从零开始的代码生活_3 小时前
C++ 继承详解:访问控制、对象模型、菱形继承与设计取舍
开发语言·c++·后端·学习·算法
Conan在掘金3 小时前
�鸿蒙报错速查:Cannot find name 'require',禁用 require(),根因 + 真解法
后端
隔窗听雨眠3 小时前
Spring Boot在云原生时代的编程范式革新研究
spring boot·后端·云原生
一朵小花3 小时前
记一次Docker 容器 NFS 文件写入延迟排查过程
后端