Apps ScriptでGoogle Sheetsの連絡先をBrevoに取り込む

同意確認、フィールドの対応付け、配信停止の保護、APIエラーの報告を行う、繰り返し実行可能なApps Scriptの例です。

google apps script brevo

要点は何ですか?

Sheetにemail、firstName、lastName、consent列を作ります。BREVO_API_KEYをScript Propertiesに保存し、コードにリストIDを設定してsyncSheetToBrevoを実行します。同意した行だけを対象に、Brevoの配信停止を確認し、重複を除き、APIエラーで停止します。自分で管理するApps Scriptであり、標準搭載のコネクターではありません。

詳しく見る
このページの内容

イベント参加者12人を一度だけ書き出すのは簡単です。2回目には同じアドレスが重複し、別の人がBrevoで配信停止しているかもしれません。繰り返し実行する取り込みでは両方を確認する必要があります。

このスクリプトはBrevoの連絡先の取得・作成・更新エンドポイントを使います。小規模な定期取り込み向けで、一括ローダーやGoogle Sheets標準コネクターではありません。メールは送りません。同意の証拠と運用は自分で管理します。

Sheetとリストを準備する

Contactsタブに次の列名を正確に作ります。consentにはチェックボックスを使い、対象のマーケティング用途に同意した場合だけTRUEにします。

emailfirstNamelastNameconsent
[email protected]JaneDoeTRUE
[email protected]AlexLeeFALSE

Brevoのリストを作成または選択し、数値IDをLIST_IDに設定します。FNAMEとLNAME属性がBrevoに存在することを確認してください。対応付けるのはこの2つだけです。同意した日時と方法をSheetで証明できない場合は、元の記録を別に保管します。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なら更新します。それ以外のエラーは停止します。連絡先の書き込み成功は、キャンペーンの送信を意味しません。

スクリプトは1回の実行内で重複アドレスを除き、次の実行で再びBrevoを照会します。emailBlacklistedをfalseにせず、メールアドレスも変えません。Brevoが全体ブロックまたは対象リストからの配信停止を示す場合、SheetがTRUEでもその行を除きます。配信停止の解除には使わないでください。Brevoは、ブロックされた連絡先のアドレス変更がブロック解除につながる可能性を示しています。

定期実行前に一度確認する

自分が管理するテストリストと連絡先を使います。TRUEの行、FALSEの行、重複メールを含めると、書き込み1件とスキップ2件が期待されます。テスト連絡先をBrevoで配信停止して再実行し、その人に書き込みがないことを確認します。エラー確認ではLIST_IDを一時的に無効にし、ExecutionsのHTTPエラーを見てから戻して再実行します。実際の顧客データで試さないでください。このコードはモックBrevo応答でローカル検証しており、ライブの書き込みはしていません。

Apps Script → Triggers → Add TriggerでsyncSheetToBrevoと時間間隔を選びます。トリガーは作成者のアカウントで実行されます。そのアカウントにSheetへのアクセスが必要です。小さなSheetなら毎日から始め、失敗を確認してください。Googleは実行時間とURL Fetchを制限します。大きなデータは上限付きの作業に分けます。

429ではx-sib-ratelimit-resetを読み、最大2回だけ再試行します。必要な待機が60秒を超えれば早く試さず、429を返して次回に任せます。ほかのエラーも停止し、次回は先頭から始まります。取得から書き込みまでの間に配信停止される可能性があります。この短い競合期間を許容できる場合だけ使ってください。厳密な保護にはイベント照合、信頼できる同意記録、送信直前の確認が必要です。

大きなSheetにはBrevoの非同期インポートがprocessIdを返します。これは処理の受付であって完了ではありません。結果を確認してください。ここでの連絡先ごとの処理なら、失敗した行が分かります。

よくある質問

Google Sheetsの標準コネクターは必要ですか?

いいえ。Sheetに紐づくApps ScriptからBrevoの連絡先APIを呼びます。スクリプト、APIキー、定期トリガーは自分で管理します。

どの行がBrevoに送られますか?

consentがTRUEでメールアドレスが有効な行だけです。実行ごとに重複を除き、書き込み前にBrevoを確認します。

再実行すると配信停止した人が再登録されますか?

BrevoがemailBlacklistedまたは対象リストからの配信停止と示す連絡先は除きます。emailBlacklisted: falseを送らず、メールアドレスも変えません。本番前にはほかの同意・配信抑止情報も確認してください。

失敗した実行をやり直すには?

Apps Scriptの実行エラーを読み、原因を直して同じ関数を再実行します。同じメールとリストIDを使うため、成功済みの行で二人目の連絡先は作成されません。

先行利用を申し込む

お名前と会社のメールアドレスを入力してください。Tajoへのアクセス申請を確認するためのリンクをメールでお送りします。

Brevoを取得