一次导出十二条活动报名记录很简单。第二次导入时,可能出现重复邮箱,也可能有人已在 Brevo 退订。可重复运行的导入必须处理这两种情况。
脚本使用 Brevo 联系人的读取、创建和更新端点。它面向小规模定时导入,不是批量加载器或 Google Sheets 内置连接器,也不会发送邮件。同意证据和脚本运行由你负责。
准备表格和列表
创建 Contacts 工作表,列标题必须如下。consent 使用复选框,只有获得对应营销用途的许可时才设为 TRUE。
| firstName | lastName | consent | |
|---|---|---|---|
| [email protected] | Jane | Doe | TRUE |
| [email protected] | Alex | Lee | FALSE |
创建或选择 Brevo 列表,将其数字 ID 写入 LIST_ID。确认 Brevo 中已存在 FNAME 和 LNAME 属性;示例只映射这两个字段。如果表格无法证明何时及如何获得同意,请另行保存来源记录。单个 TRUE 单元格不是证据。
在 Extensions → Apps Script → Project Settings → Script Properties 中添加 BREVO_API_KEY。脚本属性对项目用户共享,应限制编辑权限。不要把密钥放在单元格或源码中。参见 Google 的属性访问规则。
脚本
用下例替换 Code.gs,首次运行前设置 LIST_ID。
const LIST_ID = 42; // Replace with your existing Brevo list ID.const TAB = 'Contacts';const BASE = 'https://api.brevo.com/v3';
function syncSheetToBrevo() { const key = PropertiesService.getScriptProperties().getProperty('BREVO_API_KEY'); if (!key) throw new Error('Set BREVO_API_KEY in Script Properties first'); const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(TAB); if (!sheet) throw new Error('Contacts tab missing'); const values = sheet.getDataRange().getValues(); const headers = values.shift().map(h => String(h).trim()); const columns = ['email', 'firstName', 'lastName', 'consent'] .map(name => headers.indexOf(name)); if (columns.includes(-1)) throw new Error('Required header missing'); const [emailCol, firstCol, lastCol, consentCol] = columns; const seen = new Set(); const result = { written: 0, skipped: 0 };
for (const row of values) { const email = String(row[emailCol] || '').trim().toLowerCase(); if (row[consentCol] !== true || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) || seen.has(email)) { result.skipped++; continue; } seen.add(email); const path = '/contacts/' + encodeURIComponent(email); const current = brevo_(key, path, 'get'); if (current.code !== 200 && current.code !== 404) { throw new Error(`Contact read failed (${current.code}) for row ${result.written + result.skipped + 2}`); } if (current.code === 200) { const contact = JSON.parse(current.text); if (contact.emailBlacklisted || (contact.listUnsubscribed || []).includes(LIST_ID)) { result.skipped++; continue; } } const attributes = {}; if (row[firstCol] !== '') attributes.FNAME = String(row[firstCol]); if (row[lastCol] !== '') attributes.LNAME = String(row[lastCol]); const body = { attributes, listIds: [LIST_ID] }; const saved = current.code === 404 ? brevo_(key, '/contacts', 'post', { email, ...body }) : brevo_(key, path, 'put', body); if (![201, 204].includes(saved.code)) { throw new Error(`Contact write failed (${saved.code}) for ${email}: ${saved.text.slice(0, 200)}`); } result.written++; } Logger.log(JSON.stringify(result)); return result;}
function brevo_(key, path, method, body) { for (let attempt = 0; attempt < 3; attempt++) { const response = UrlFetchApp.fetch(BASE + path, { method, contentType: 'application/json', headers: { 'api-key': key }, payload: body ? JSON.stringify(body) : undefined, muteHttpExceptions: true, }); const code = response.getResponseCode(); if (code !== 429) return { code, text: response.getContentText() }; if (attempt === 2) return { code, text: response.getContentText() }; const reset = Object.entries(response.getHeaders()).find( ([name]) => name.toLowerCase() === 'x-sib-ratelimit-reset' ); const raw = Number(reset && reset[1]); const seconds = Number.isFinite(raw) && raw > 0 ? raw : 2 ** attempt; const waitSeconds = seconds + 2 ** attempt; if (waitSeconds > 60) return { code, text: response.getContentText() }; Utilities.sleep(waitSeconds * 1000); }}保存,选择 syncSheetToBrevo,点击 Run。Google 会请求 Sheets 和外部请求权限。在 Executions 查看 {written, skipped} 日志。读取返回 404 会创建联系人,返回 200 会更新;其他错误会停止。联系人写入成功不代表营销邮件已发送。
脚本在一次运行中去除重复邮箱,下次运行还会重新查询 Brevo。它不会把 emailBlacklisted 设为 false,也不会修改邮箱。如果 Brevo 报告全局屏蔽或已退出目标列表,即使表格仍是 TRUE,该行也会跳过。不要用它撤销退订。Brevo 提醒,修改被屏蔽联系人的邮箱可能解除屏蔽。
定时运行前检查一次
使用你控制的测试列表和联系人。一行 TRUE、一行 FALSE 加一个重复邮箱,应得到一次写入和两次跳过。让测试联系人在 Brevo 退订后重跑,该联系人不应再写入。测试错误时暂时设置无效的 LIST_ID,在 Executions 确认 HTTP 错误,再恢复 ID 并重试。不要用真实客户数据测试。本文代码已用模拟 Brevo 响应在本地验证,没有真实写入。
在 Apps Script → Triggers → Add Trigger 中选择 syncSheetToBrevo 和时间间隔。触发器以创建者账户运行,该账户须持续有表格权限。小表格可从每天一次开始,持续查看失败记录。Google 限制运行时间和 URL Fetch 使用量;大型数据源应拆成有界任务。
遇到 429,脚本读取 x-sib-ratelimit-reset,最多再试两次。如果需要等待超过 60 秒,就返回 429,留待下次运行,而不会提前重试。其他错误也会停止,下次运行从头开始。联系人可能在读取与写入之间退订;只有能接受这段短暂竞态时才适用。更严格的保护需要事件核对、可靠的同意记录,以及发送前的最后检查。
大表格可使用 Brevo 异步导入端点,它返回 processId,表示任务已接收,而不是已完成。报告成功前应检查结果。本文逐联系人处理可以定位失败的行。
常见问题
需要原生 Google Sheets 连接器吗?
不需要。示例在绑定表格的 Apps Script 项目中运行,并调用 Brevo 联系人 API。脚本、API 密钥和定时触发器都由你维护。
哪些行会发送给 Brevo?
只有 consent 为 TRUE 且邮箱有效的行。每次运行都会去重,写入前还会查询 Brevo。
重复运行会重新订阅已退订者吗?
不会。脚本跳过 Brevo 标记为 emailBlacklisted 或已退出目标列表的联系人,不会发送 emailBlacklisted: false,也不会改邮箱。上线前还应检查其他同意与屏蔽来源。
运行失败后如何重试?
查看 Apps Script 执行错误,修复原因后重跑同一函数。写入使用相同邮箱和列表 ID,已成功的行不会创建第二个联系人。