Brevo API:面向开发者的实用指南

面向开发者的 Brevo API 指南:鉴权、基础 URL、联系人、事务性邮件、营销活动、CRM 对象、Webhook、速率限制以及真实环境中的限制。

Brevo API
Brevo API:面向开发者的实用指南?

Brevo 用一套 REST API 同时覆盖事务性消息、营销活动、联系人数据和 CRM 记录。让第一个请求返回 201 大约只要两分钟。但要做出一个不会静默丢数据的生产级集成,时间会长得多,因为最关键的几条约束要么没写进文档,要么与 API 自己报告的情况相互矛盾。

本指南两部分都讲:第一天就需要的端点、SDK 和鉴权,以及上线之前必须提前设计规避的平台限制。

Brevo API 覆盖哪些能力

所有能力都在同一个域名和同一个版本路径下。开发者文档把整个接口面分成四个产品领域:

  • 消息:事务性邮件、SMS 和 WhatsApp,包括批量发送、定时发送和消息活动记录。
  • 营销平台:联系人、列表、细分和邮件营销活动。
  • 电商:商品、订单和客户事件跟踪。
  • 对话:聊天挂件以及通过程序管理对话。

这些领域共用一个账号、一个联系人数据库和一个 API 密钥。这既方便,偶尔也危险:一个按预演数据写出来的脚本,操作的是营销活动实际发送的那批联系人。

事务性与营销的区别

这两类的行为差别足够大,把它们弄混是最常见的设计错误。

事务性营销
主要端点POST /v3/smtp/emailPOST /v3/emailCampaigns
寻址方式请求里显式指定收件人listIdssegmentIds
触发方式你的应用实时触发定时发送或按需发送
典型流量形态持续不断,一次一条突发式,一次一大批
速率限制姿态非常高,标准套餐每秒 1,000 次请求低,营销活动端点归入通用上限

如果你还在判断 Brevo 是不是合适的平台,平台概览覆盖了这部分内容。

鉴权与密钥管理

Brevo 使用放在自定义请求头里的纯 API 密钥。请求头名为 api-key,不是 Authorization,也没有 Bearer 前缀。先用过其他消息 API 的人几乎都会在这里栽跟头。

Terminal window
curl https://api.brevo.com/v3/account \
-H "api-key: $BREVO_API_KEY"

密钥在 Brevo 应用的账号设置里生成,位置是 SMTP and API 板块下的 API keys 标签页。给每个密钥起一个与使用它的系统对应的描述性名称。密钥值在生成时只显示一次,一旦丢失就只能重新生成,无法找回。

几条实操规则:

  • 按部署环境和服务分别签发独立密钥。吊销一个泄露的密钥,不应该同时搞垮三个无关系统。
  • 标准 API 密钥的作用范围是整个账号。把任何一个密钥都视为对联系人、发送和 CRM 数据的完全访问权限。
  • 对于代表其他 Brevo 账号操作的应用,Brevo 还支持 OAuth 2.0,具体流程与密钥流程一并写在鉴权方案文档中。
  • AI 助手使用的 MCP 服务器需要另一个令牌,而且确实使用 bearer 请求头。该令牌在同一个 API keys 页面生成,但与 REST 密钥不能互换。

基础 URL、版本以及第一次写入

基础 URL 是 https://api.brevo.com/v3/。版本写在路径里而不是请求头里,v3 是当前一代。本指南里的每个路径都相对于这个基础 URL。

第一次写入比第一次读取更能说明问题,因为它会触及账号里通常配置有误的部分,尤其是已验证发件人:

Terminal window
curl -X POST https://api.brevo.com/v3/smtp/email \
-H "api-key: $BREVO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sender": { "name": "Ops", "email": "[email protected]" },
"to": [{ "email": "[email protected]", "name": "Dev" }],
"subject": "First transactional send",
"htmlContent": "<html><body><p>It works.</p></body></html>",
"tags": ["smoke-test"]
}'

发送成功返回 201 和一个 messageId。定时发送返回 202。

你真正会用到的端点

联系人

POST /v3/contacts 创建联系人。请求体接受 email、用于自定义字段的 attributes 映射、listIds、用于你自己外部键的 ext_id,以及实践中最关键的两个开关:updateEnabled,它把这个调用变成 upsert;getId,它让响应返回联系人 id。

读取走 GET /v3/contacts,用 limit(默认 50,最大 1000)和 offset 分页,并支持 UTC 格式的 modifiedSincecreatedSince。增量同步应该依赖 modifiedSince,而不是遍历整个列表。注意 filter 参数只支持等值运算符,表达力更强的条件应该放进细分里。

批量导入用 POST /v3/contacts/import,它接受 fileUrlfileBodyjsonBody,指向 listIds,异步执行并返回一个 processId。Brevo 文档写明请求体上限 10 MB,并建议控制在 8 MB 附近,因为解析会让载荷膨胀。记得传 notifyUrl,这样你就能得知结果,而不用轮询。

事务性邮件

POST /v3/smtp/email 是主力端点。除了 sendertosubjecthtmlContent,还有几个字段值得了解:

  • templateId 配合 params,用 Brevo 模板及其变量替换取代内联内容。单个版本的 params 上限 100 KB,累计上限 1000 KB。
  • messageVersions,一次调用发送个性化变体,每个版本最多 99 个收件人。
  • tags,这个你应该始终设置。标签会随 Webhook 事件返回,是把送达事件和产生它的代码路径对应起来的唯一廉价手段。
  • scheduledAt 加上 batchId,用于你可能想整批取消的未来发送。
  • headers,采用 Title-Case,用于自定义 SMTP 请求头。

单次请求最多接受 2,000 个收件人。关于这个端点与营销活动发送的区别,事务性邮件指南给出了消息策略层面的视角。

邮件营销活动

POST /v3/emailCampaigns 需要 namesender,外加恰好一个内容来源:htmlContent(至少 10 个字符,小于 1 MB)、htmlUrltemplateId。受众放在 recipients 里,以 listIdssegmentIds 形式给出,scheduledAt 使用 YYYY-MM-DDTHH:mm:ss.SSSZ 的 UTC 格式。配套路由分别负责立即发送、发送测试、更新状态和拉取活动报告。

公司、交易与对象

Brevo 的 CRM 有两条相互重叠的写入路径,选对很重要。

CRM 路由是 POST /v3/companiesPATCH /v3/companies/{id}DELETE /v3/companies/{id},以及交易对应的同一组路由。它们是同步的。变更应用后 PATCH 返回 204。

对象 API 是批量路径:POST /v3/objects/{object_type}/batch/upsert 每次请求最多接受 1000 条记录和 1 MB,每条记录最多 500 个属性,每条记录每个对象类型最多 10 条关联记录。它返回 202 和一个 processId,意思是已受理而不是已应用。

Terminal window
curl -X POST https://api.brevo.com/v3/objects/company/batch/upsert \
-H "api-key: $BREVO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"identifiers": { "id": 12345 },
"attributes": { "domain": "acme.example", "industry": "retail" }
}
]
}'

Brevo CRM 指南从运营者视角讲解了这套对象模型。

官方 SDK

Brevo 在 getbrevo GitHub 组织下维护这些客户端:

语言代码仓库
Node.jsgithub.com/getbrevo/brevo-node
Pythongithub.com/getbrevo/brevo-python
PHPgithub.com/getbrevo/brevo-php
Javagithub.com/getbrevo/brevo-java
C#github.com/getbrevo/brevo-csharp
Gogithub.com/getbrevo/brevo-go
Rubygithub.com/getbrevo/brevo-ruby

Node 客户端以 @getbrevo/brevo 安装:

Terminal window
npm install @getbrevo/brevo
import { BrevoClient } from "@getbrevo/brevo";
const brevo = new BrevoClient({ apiKey: process.env.BREVO_API_KEY });
const result = await brevo.transactionalEmails.sendTransacEmail({
subject: "Order confirmed",
htmlContent: "<html><body><p>Thanks for your order.</p></body></html>",
sender: { name: "Acme", email: "[email protected]" },
to: [{ email: "[email protected]", name: "Customer" }],
tags: ["order-confirmation"],
});
console.log("Message ID:", result.messageId);

Python 客户端用 pip install brevo-python 安装。如果你不想为两个端点背上一个 SDK 依赖,原始 HTTP 接口面小到可以直接调用,这样也能让你免受 SDK 版本变动的影响:

import os
import requests
BASE = "https://api.brevo.com/v3"
HEADERS = {
"api-key": os.environ["BREVO_API_KEY"],
"Content-Type": "application/json",
}
def upsert_contact(email, attributes, list_ids):
response = requests.post(
f"{BASE}/contacts",
headers=HEADERS,
json={
"email": email,
"attributes": attributes,
"listIds": list_ids,
"updateEnabled": True,
},
timeout=30,
)
response.raise_for_status()
return response

面向 AI 助手,还有一个位于 https://mcp.brevo.com/v1/brevo/mcpMCP 服务器,用同一个设置页面生成的 bearer token 鉴权。它适合探索和账号问答,不适合生产数据链路。

Webhook

Webhook 是你得知发送之后发生了什么的途径。POST /v3/webhooks 创建一个,参数有 urleventstype,还可以选填 channelemailsms)、batched、自定义 headers 以及一个 auth 对象。

Webhook 有三种类型,事件词汇各不相同:

  • 事务性sentrequestdeliveredhardBouncesoftBounceblockedspaminvaliddeferredclickopeneduniqueOpenedunsubscribed
  • 营销spamopenedclickhardBouncesoftBounceunsubscribedlistAdditiondeliveredcontactUpdatedcontactDeleted
  • 入站inboundEmailProcessedreply,这两者还额外需要一个 domain
Terminal window
curl -X POST https://api.brevo.com/v3/webhooks \
-H "api-key: $BREVO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.example/hooks/brevo",
"type": "transactional",
"events": ["delivered", "hardBounce", "spam", "unsubscribed"],
"description": "Deliverability signals"
}'

有三件事要做对。第一,一个账号所有类型加起来最多只能有 40 个 Webhook,所以要在处理程序内部按事件分流,而不是给每个事件注册一个端点。第二,预计有量的时候用 batched 开关,因为一个请求携带多个事件,处理成本远低于多个请求。第三,保护接收端:Brevo 公布了自己的发送 IP 段,把你的端点限制到这些 IP 段是官方推荐的做法。再通过 headers 字段加上你自己的共享密钥作为第二层防护。

处理程序必须幂等。把消息 id 加事件类型加时间戳当作去重键。

速率限制与错误处理

Brevo 的速率限制按端点和套餐档位区分,各端点之间的差距极大。

端点标准Professional 与 Enterprise
POST /v3/smtp/email1,000 RPS2,000 RPS
POST /v3/transactionalSMS/send150 RPS200 RPS
/v3/contacts/...10 RPS,36,000 RPH20 RPS,72,000 RPH
POST /v3/events10 RPS,36,000 RPHEnterprise 更高
GET /v3/smtp/emails2 RPS,7,200 RPH3 RPS,10,800 RPH
其余所有端点100 RPH200 RPH

最后一行才是真正会咬人的地方。发送几乎不受限,而营销活动管理、CRM 读取和大多数管理类调用在标准套餐上共用每小时 100 次请求的额度。一个在每次写入前都要先读一遍公司记录的天真回填任务,不到两分钟就能耗光一小时的配额。

每个响应都带有 x-sib-ratelimit-limitx-sib-ratelimit-remainingx-sib-ratelimit-reset。成功时也要读,别只在失败时才看。超出限制会返回 429,正确的应对是等待 reset 请求头给出的时间间隔,然后再做带抖动的指数退避。

async function callBrevo(path, init, attempt = 0) {
const response = await fetch(`https://api.brevo.com/v3${path}`, {
...init,
headers: { "api-key": process.env.BREVO_API_KEY, "Content-Type": "application/json", ...init.headers },
});
if (response.status === 429 && attempt < 5) {
const reset = Number(response.headers.get("x-sib-ratelimit-reset") || 1);
const backoff = Math.pow(2, attempt) * 250 + Math.random() * 250;
await new Promise((r) => setTimeout(r, reset * 1000 + backoff));
return callBrevo(path, init, attempt + 1);
}
return response;
}

429 和 5xx 要重试。400 或 409 绝不要盲目重试,因为这两者通常意味着请求本身有问题,而不是发得太早;尤其是 409,需要的是换一种处理动作,而不是重复一次。

不发真实邮件的测试方法

在事务性发送请求里加上值为 dropX-Sib-Sandbox 请求头。Brevo 会校验请求,返回 201 和一个 messageId,不投递任何东西,也不写入邮件日志。

Terminal window
curl -X POST https://api.brevo.com/v3/smtp/email \
-H "api-key: $BREVO_API_KEY" \
-H "X-Sib-Sandbox: drop" \
-H "Content-Type: application/json" \
-d '{ "sender": { "email": "[email protected]" }, "to": [{ "email": "[email protected]" }], "subject": "Sandbox", "htmlContent": "<p>hi</p>" }'

要清楚它能证明什么、不能证明什么。沙盒模式只校验请求格式,对发件人鉴权、模板渲染或送达能力什么都说明不了。凡是涉及联系人或 CRM 数据的集成测试,都要单独准备一个 Brevo 账号,因为沙盒模式只覆盖发送,不覆盖 API 的其余部分。

决定集成设计的那些限制

下面这些约束,只有当集成在真实账号上跑起量之后才会显现。其中好几条与 API 自己的说法相互矛盾。它们都没有商量余地,所以唯一明智的做法是围绕它们来设计。

公司必须有域名,且一个域名只能有一家公司

GET /v3/crm/attributes/companies 把每个属性都报告为非必填,创建公司的参考文档也只把 name 列为必填。实际上,POST /v3/companies 如果没有非空的 domain 属性,会返回 400 并提示缺少强制默认属性。传空字符串和不传的失败方式完全一样。

更麻烦的是,域名唯一性是强制的。在已被占用的域名上创建第二家公司会返回 409。对 B2B 商务来说这是结构性的:共用同一个买家邮箱域名的多家子公司,无法在 Brevo 里各自作为独立公司存在。而且仅仅同步一个联系人,就足以让一家公司凭该联系人的邮箱域名冒出来,于是一次创建可能与谁也没有显式创建过的公司撞车。正确的处理程序应该在收到 409 时接管已存在的公司,而不是失败或重试。

未声明的属性会被静默丢弃

这是平台里最危险的行为,而 Brevo 把它写得清清楚楚:如果某个属性出现在请求里但此前没有在对象架构中定义,那就什么都不会发生。不报错,不创建属性,也不给警告。

因此 2xx 响应并不能证明你的数据落库了。写入前先读取架构,在你自己的客户端里丢弃所有未声明的字段,并且宁可拒绝运行属性不存在的同步任务,也不要写了一个月半截记录之后才有人发现。

属性过滤器会被接受然后忽略

GET /v3/companies?filters[attributes.domain]=... 返回 200 并忽略该过滤条件。两个完全不同的过滤条件返回相同的记录。通过这条路由,没有任何可用的方式按属性查找公司。

再加上大账号上无过滤的列表调用无论页大小多少都会 504 超时,一家已存在的公司通过文档中的路径可能真的找不到。变通办法是扫描 GET /v3/objects/company/records 并带上 sort=desc,它速度快、可分页、会返回属性,把扫描页数限制在一个合理范围内即可。刚刚触发 409 的那家公司几乎总是片刻之前才创建的,所以按最新优先扫描能很快找到它。

每个对象类型一百万条记录,且没有批量删除

当某个对象类型的记录数达到一百万条时,POST /v3/objects/{type}/batch/upsert 会返回 400。它连更新一起挡住:用记录自己的数字 id 定位一条已存在的记录,同样失败。整条对象写入路径一次性关闭。

想退回上限之下很慢,因为 POST /v3/objects/{type}/batch/deletecompany 这类 Brevo 标准对象类型返回 403。唯一的路子是 DELETE /v3/companies/{id},一次一条,每条约 156 毫秒。用这种方式清理 124,000 条记录,开 20 个并行工作进程也要花上好几个小时。请按计划定期监控记录数,而不是通过一次失败的同步才发现上限,并把高频更新走 PATCH /v3/companies/{id},这条路径没有这类限制。

ext_id 是 Brevo 的 id,不是你的

在对象记录上,identifiers.ext_id 存放的是 Brevo 自己的 CRM 公司 id,一个 Mongo 风格的字符串。它不是可以自由使用的外部键。把 ext_id 设成你自己平台的标识符再做 upsert,结果是产生重复记录而不是匹配上。你的外部 id 应该放在一个自己声明的属性里。

对象 upsert 是异步的,CRM 写入不是

batch/upsert 返回 202 和一个 processId,之后才实际应用。一个不存在的 id 会异步失败,但仍然向调用方返回 202。PATCH /v3/companies/{id} 返回 204,且是同步应用的。如果你的同步任务报告成功,只有同步路径才配得上这个词,其他路径都需要再读一次确认。

一份简短的集成检查清单

  • 按服务和环境分别使用独立 API 密钥,人员变动时轮换。
  • 所有写入都经由同一个客户端,它读取速率限制请求头并在 429 时退避。
  • 启动时校验属性架构,属性缺失就拒绝运行同步。
  • 创建公司返回 409 意味着接管,而不是重试。
  • 批量路径用对象 API 换吞吐,凡是必须确认结果的走 CRM 路由。
  • Webhook 做到幂等、批量、限制 IP,并携带共享密钥请求头。
  • 联系人增量同步用 modifiedSince,不要全量遍历。

搭建并维护这一层是实打实的工程工作:架构校验、退避、接管逻辑、对账。Tajo 的存在就是为了把这些吸收掉,让 Shopify 和商务数据与 Brevo 的联系人、公司和事件保持同步,而不用任何人手写重试和去重逻辑。如果你打算自己接线,Brevo 集成指南会带你走一遍写代码之前要做的数据模型选择。

要点回顾

  • 这套 API 是位于 https://api.brevo.com/v3/ 的单一 REST 接口,用 api-key 请求头鉴权,而不是 bearer token。
  • 速率限制极不均衡:发送几乎不受限,而其他大多数端点在标准套餐上共用每小时 100 次请求。
  • 官方 SDK 覆盖七种语言,但当你只需要少数几个端点时,HTTP 接口面简单到可以直接调用。
  • 沙盒模式只校验请求格式,所以发送之外的任何测试都要另开一个账号。
  • 2xx 响应不能证明写入已生效。未声明的属性会被静默丢弃,对象 upsert 是异步的。
  • 围绕这些固定限制来设计:一个域名一家公司、每个对象类型一百万条记录、标准对象没有批量删除,以及默默不生效的属性过滤器。

常见问题

Brevo API 的基础 URL 是什么?
所有 Brevo REST 调用都指向 https://api.brevo.com/v3/。版本号写在路径里,v3 是当前一代,因此像事务性发送这样的端点,完整路径就是 https://api.brevo.com/v3/smtp/email。
如何完成 Brevo API 的鉴权?
把密钥放在名为 api-key 的 HTTP 请求头里发送。标准 API 密钥不使用 Authorization 或 Bearer 请求头。密钥在账号设置的 SMTP and API 里的 API keys 页面生成,值只显示一次。
Brevo API 的速率限制是多少?
限制按端点和套餐档位区分。标准账号上,事务性发送端点允许每秒 1,000 次请求,联系人端点允许每秒 10 次,其余所有端点每小时上限 100 次请求。Professional 和 Enterprise 套餐的档位更高。超出限制会返回 429。
Brevo 有官方 SDK 吗?
有。Brevo 在 getbrevo 这个 GitHub 组织下发布了 Node.js、Python、PHP、Java、C#、Go 和 Ruby 的客户端。Node 包在 npm 上是 @getbrevo/brevo,Python 包在 PyPI 上是 brevo-python。
怎样在不发出真实邮件的情况下测试 Brevo API?
在事务性发送请求里加上值为 drop 的 X-Sib-Sandbox 请求头。Brevo 会校验请求,返回 201 和一个 messageId,但不发送任何邮件,也不写入邮件日志。它只检查请求格式,不检查送达能力。
Brevo 支持哪些 Webhook 事件?
事务性 Webhook 覆盖 sent、request、delivered、hardBounce、softBounce、blocked、spam、invalid、deferred、click、opened、uniqueOpened 和 unsubscribed。营销 Webhook 另外增加 listAddition、contactUpdated 和 contactDeleted。入站 Webhook 覆盖 inboundEmailProcessed 和 reply。一个账号最多可以保存 40 个 Webhook。
能在 Brevo 里创建两个域名相同的公司吗?
不能。Brevo 对公司记录强制域名唯一,重复创建会返回 409 和域名唯一性错误。正确的集成行为是接管已存在的那个公司,而不是重试创建。
为什么 Brevo API 写入返回成功却什么都没变?
对象和 CRM 写入会静默丢弃没有在对象架构中声明的属性。Brevo 明确记录了这一行为:不报错,也不创建属性。先读取架构,再验证写入结果,不要仅凭 2xx 状态码就相信数据已落库。
Brevo API 有批量删除吗?
对 company 这类 Brevo 标准对象类型没有。批量删除路由对它们返回 403,因此清理只能通过 DELETE /v3/companies/{id} 一条一条执行。批量清理请按小时而不是分钟来排期。

申请抢先体验

请填写名字,以及邮箱或手机号。我们会与您联系,提供 Tajo 访问详情。

自动识别
获取Brevo