スクリプトでCSVまたはExcelの連絡先をBrevoにインポートする方法(Python、Node.js、cURL)
import-contacts APIを使って、CSVやExcelファイルから連絡先を一括でBrevoにインポートします。すぐに動かせるPythonとNode.jsスクリプト、cURLワンライナー、エラー処理、10MBを超えるファイルへのヒントを含みます。
スプレッドシートから数千件の連絡先をBrevoに送り込む必要があったことがあるなら、手動UIの経路がすぐに苦痛になることをご存じでしょう。ファイルを選び、列をマッピングし、リストを選び、待ち、繰り返す。スクリプトなら同じ仕事が数秒で済みます。さらに重要なのは、スケジュール実行や、データベースエクスポート後、CRMが新しいCSVを吐き出すたびに再実行できることです。
このガイドでは、まさにこの目的のためにBrevoが用意しているAPIエンドポイントを、Python、Node.js、cURLワンライナーの完全な動作スクリプトとともに取り上げます。このガイドのすべては POST /v3/contacts/import にアクセスします。
エンドポイント概要
POST https://api.brevo.com/v3/contacts/importContent-Type: application/jsonapi-key: YOUR_API_KEY
{ "listIds": [42], "updateExistingContacts": true, "emailBlacklist": false, "smsBlacklist": false}レスポンスは素早く返ります。
{ "processId": 78 }これは202 Accepted です。Brevo がインポートを受け付け、バックグラウンドで処理しています。processId は完了をポーリングしたり notifyUrl webhook を設定したりする際の追跡ハンドルです。
コードを書く前に押さえておきたい点がいくつかあります。
- 本文は CSV(
fileBody)、JSON(jsonBody)、リモート URL(fileUrl)のいずれかにできます。 1 つを選んでください。CSV 形式は列の区切りにカンマではなくセミコロン(;)を使います。よくある落とし穴です。 fileBodyとjsonBodyは 10MB が上限です。 Brevo は安全のため 8MB 程度に抑えることを推奨しています。それより大きい場合は、ファイルを S3、GCS、または任意の HTTPS ホストにアップロードしてfileUrlを渡してください。- アカウントに存在しないカスタム属性は黙って無視されます。 Brevo の UI(または Attributes API)で先に作成してから、それらを使う行をインポートしてください。さもないとデータが消えます。
updateExistingContactsのデフォルトはtrueです。falseに設定すると、Brevo はメールアドレスがすでに存在する連絡先をスキップします。「新規追加のみ」のジョブに便利です。emptyContactsAttributesは CSV の空セルが既存の値を消去するかどうかを制御します。デフォルトはfalse(空セルは無視)です。CSV が信頼できる情報源で、空欄が古いデータを消去すべき場合はtrueに設定してください。
API キーを取得する
Brevo にログインし、Settings → SMTP & API → API Keys で新しいキーを作成します。xkeysib-... のような形式です。すべてのリクエストで api-key HTTP ヘッダーとして渡します。アカウントに対する完全な読み書き権限を持つので、パスワードのように扱ってください。
きれいなパターンとして、ソースツリーに残らないよう環境変数に入れます。
export BREVO_API_KEY="xkeysib-..."Python: CSV を読んで Brevo にプッシュ
これは可能な限りシンプルなスクリプトです。標準ライブラリでローカル CSV を読み、本文をそのまま Brevo に送ります。requests 以外のサードパーティ SDK は不要です。
import csvimport ioimport osimport sysimport requests
API_KEY = os.environ["BREVO_API_KEY"]LIST_ID = 42 # the Brevo list to add contacts toINPUT_FILE = sys.argv[1] if len(sys.argv) > 1 else "contacts.csv"
# Brevo wants semicolon-separated CSV. If your file uses commas (most do),# convert it on the fly so you don't have to edit the source spreadsheet.def to_brevo_csv(path: str) -> str: with open(path, newline="", encoding="utf-8") as f: reader = csv.reader(f) # default: comma-separated out = io.StringIO() writer = csv.writer(out, delimiter=";") for row in reader: writer.writerow(row) return out.getvalue()
body = { "fileBody": to_brevo_csv(INPUT_FILE), "listIds": [LIST_ID], "updateExistingContacts": True, "emptyContactsAttributes": False, "emailBlacklist": False, "smsBlacklist": False,}
resp = requests.post( "https://api.brevo.com/v3/contacts/import", json=body, headers={"api-key": API_KEY, "Content-Type": "application/json"}, timeout=60,)
if resp.status_code != 202: print(f"Import failed: {resp.status_code} {resp.text}") sys.exit(1)
process_id = resp.json()["processId"]print(f"Import accepted. Process ID: {process_id}")実行します。
python import_csv_to_brevo.py contacts.csvCSV の最初の行はヘッダーです。列名は 大文字での完全一致 で Brevo 属性にマッピングされます。EMAIL、FIRSTNAME、LASTNAME に加え、定義したカスタム属性が利用できます。EMAIL は必須です。
EMAIL,FIRSTNAME,LASTNAME,COMPANY,CITYこれがフロー全体です。スクリプトは 1 秒以内に戻ります。実際のインポートはサーバーサイドで実行され、ボリュームに応じて数秒から数分かかります。
Excel ファイル: 実用的な 3 つのパス
Brevo のインポートエンドポイントは .xlsx を直接読まないため、作業がどこで行われるかに応じて 3 つの実用的な選択肢があります。
- ワークブック内で VBA マクロを実行する。 外部スクリプトなしで、Excel 自体からワンクリック同期できます。ファイルがデスクトップにあり、チームがシート上にボタンを欲しがっている場合の正解です。完全なコードは Excel-to-Brevo VBA マクロガイドにあります。
- Office Scripts + Power Automate。 ワークブックが OneDrive/SharePoint にあり、無人スケジュール同期を望む場合に。Excel ガイドでも取り上げています。
- スクリプトから
.xlsxを CSV に変換する。 このセクションの残りでカバーする内容です。すでにサーバーで Python や Node を実行しており、1 日に 1 回ワークブックを取り込むだけで良い場合に最適です。
選択肢 3 の場合、pandas を使えば 1 行で済みます。
# pip install pandas openpyxlimport pandas as pd
def xlsx_to_brevo_csv(path: str, sheet_name: str | int = 0) -> str: df = pd.read_excel(path, sheet_name=sheet_name) return df.to_csv(sep=";", index=False)
body["fileBody"] = xlsx_to_brevo_csv("contacts.xlsx")依存関係に pandas を入れたくない場合は、openpyxl だけでも動きます。
from openpyxl import load_workbookimport csv, io
def xlsx_to_brevo_csv(path: str) -> str: wb = load_workbook(path, read_only=True) ws = wb.active out = io.StringIO() writer = csv.writer(out, delimiter=";") for row in ws.iter_rows(values_only=True): writer.writerow(["" if v is None else v for v in row]) return out.getvalue()Node.js: 同じ仕事、公式 SDK
Brevo は Node 用に @getbrevo/brevo を公開しています。認証、リトライ、型付きのリクエスト形状を扱います。
// npm install @getbrevo/brevoimport fs from 'node:fs/promises';import { BrevoClient } from '@getbrevo/brevo';
const API_KEY = process.env.BREVO_API_KEY;const LIST_ID = 42;const INPUT_FILE = process.argv[2] ?? 'contacts.csv';
const client = new BrevoClient({ apiKey: API_KEY });
// Read the CSV and convert commas to semicolons if neededconst raw = await fs.readFile(INPUT_FILE, 'utf-8');const csv = raw.includes(';') ? raw : raw.replace(/,/g, ';');
const result = await client.contacts.importContacts({ fileBody: csv, listIds: [LIST_ID], updateExistingContacts: true, emptyContactsAttributes: false,});
console.log(`Import accepted. Process ID: ${result.processId}`);または、SDK を使いたくなければ、素の fetch で同じことができます。
const resp = await fetch('https://api.brevo.com/v3/contacts/import', { method: 'POST', headers: { 'api-key': process.env.BREVO_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ fileBody: csv, listIds: [42], updateExistingContacts: true, }),});
if (resp.status !== 202) { throw new Error(`Import failed: ${resp.status} ${await resp.text()}`);}
const { processId } = await resp.json();console.log(`Process ID: ${processId}`);cURL: アドホックなインポート用ワンライナー
エンドポイントをテストしたり、小さなファイルを手動で送り込みたいだけのときに。
# Convert commas to semicolons, then send the file inlinecsv=$(sed 's/,/;/g' contacts.csv | jq -Rs .)
curl -X POST https://api.brevo.com/v3/contacts/import \ -H "api-key: $BREVO_API_KEY" \ -H "Content-Type: application/json" \ -d "{\"fileBody\": $csv, \"listIds\": [42], \"updateExistingContacts\": true}"jq -Rs . はファイル全体を取り込んで JSON エスケープしてくれます。改行やクォートを手動でエスケープする手間が省けます。
10MB を超えるファイル: fileUrl を使う
fileBody は 10MB が上限です。連絡先ダンプがこれより大きい場合は、Brevo が取得できる URL でファイルをホストし、それを渡します。
body = { "fileUrl": "https://files.example.com/contacts/2026-04-30.csv", "listIds": [42], "updateExistingContacts": True,}公開 HTTPS URL で到達可能なものなら何でも動きます。署名付き S3 URL、GCS、自前の静的ホスト、ワンショットインポートなら GitHub の raw URL でも構いません。Brevo がその URL からファイルを取得し、インポートを実行します。受け付ける形式は .csv、.txt、.json です。
CSV の代わりに JSON を送る
すでにデータベースから連絡先を引き出しているなら、CSV を経由する必要はありません。直接 JSON として送信します。
body = { "jsonBody": [ { "attributes": { "FIRSTNAME": "Jane", "LASTNAME": "Doe", "COMPANY": "Acme", }, }, { "attributes": { "FIRSTNAME": "John", "LASTNAME": "Smith", "COMPANY": "Globex", }, }, ], "listIds": [42],}同じ 10MB の上限。同じ非同期の挙動。
完了をポーリングする
インポートは非同期です。processId が追跡用のハンドルになります。プロセスステータスをチェックする別のエンドポイントがあります。
def wait_for_import(process_id: int, timeout_s: int = 600) -> dict: import time deadline = time.time() + timeout_s while time.time() < deadline: r = requests.get( f"https://api.brevo.com/v3/processes/{process_id}", headers={"api-key": API_KEY}, timeout=30, ) r.raise_for_status() status = r.json() if status["status"] in ("completed", "failed"): return status time.sleep(5) raise TimeoutError(f"Import {process_id} did not finish in {timeout_s}s")
result = wait_for_import(process_id)print(f"Import {result['status']}: {result}")長時間実行ジョブにはよりきれいなパターンとして notifyUrl があります。インポート完了時に Brevo が POST してくる HTTPS エンドポイントを渡せば、ポーリングをスキップできます。
body["notifyUrl"] = "https://your-app.example.com/webhooks/brevo-import"よくあるエラーと修正方法
400 Bad Request で原因が明確でない。 ほとんどの場合、CSV のセミコロン対カンマです。Brevo のインポートは , ではなく ; を期待します。変換ステップ後の fileBody を再確認してください。
カスタム属性の値が消える。 その属性がアカウントに存在しません。Contacts → Settings → Contact attributes で先に作成するか、Attributes API を使ってスクリプトの一部として作成してください。
401 Unauthorized。 ヘッダー名が間違っています。Authorization や X-API-Key ではなく api-key(小文字、ハイフン)です。
インポートが「成功」したのに、連絡先がリストに表示されない。 listIds に正しいリストが含まれているか確認してください。また、updateExistingContacts が false で連絡先がすでに存在する場合、Brevo はリストに再追加せずに静かにスキップします。
一部の行はインポートされ、一部はされない。 インポートが完了すると Brevo が行ごとのエラーレポートをメールで送ります(disableNotification: true を設定していない限り)。レポートには、悪いメールアドレス、必須フィールドの欠落、フォーマットの問題があった行が記載されます。
スクリプト化すべきとき、UI を使うべきとき
UI は 1,000 行未満のワンショットインポートには問題ありません。次のいずれかに該当する場合、スクリプトが勝ります。
- 複数回インポートする場合(CRM からの週次エクスポートなど)
- ソースデータを Brevo に入れる前にクリーニングが必要な場合(重複排除、電話番号のフォーマット、フルネームを姓名に分割)
- 誰もボタンをクリックせずにスケジュール実行したい場合
- ファイルが UI の快適ゾーンより大きい場合
上記のスクリプトを cron ジョブや GitHub Action でラップすれば、自動連絡先同期の完成です。このシリーズの次の投稿では、Apps Script を使って Google Sheet から直接同じことを行う方法を示します。サーバー不要です。