スクリプトでCSVまたはExcelの連絡先をBrevoにインポートする方法(Python、Node.js、cURL)

import-contacts APIを使って、CSVやExcelファイルから連絡先を一括でBrevoにインポートします。すぐに動かせるPythonとNode.jsスクリプト、cURLワンライナー、エラー処理、10MBを超えるファイルへのヒントを含みます。

Featured image for article: スクリプトでCSVまたはExcelの連絡先をBrevoにインポートする方法(Python、Node.js、cURL)

スプレッドシートから数千件の連絡先をBrevoに送り込む必要があったことがあるなら、手動UIの経路がすぐに苦痛になることをご存じでしょう。ファイルを選び、列をマッピングし、リストを選び、待ち、繰り返す。スクリプトなら同じ仕事が数秒で済みます。さらに重要なのは、スケジュール実行や、データベースエクスポート後、CRMが新しいCSVを吐き出すたびに再実行できることです。

このガイドでは、まさにこの目的のためにBrevoが用意しているAPIエンドポイントを、Python、Node.js、cURLワンライナーの完全な動作スクリプトとともに取り上げます。このガイドのすべては POST /v3/contacts/import にアクセスします。

エンドポイント概要

POST https://api.brevo.com/v3/contacts/import
Content-Type: application/json
api-key: YOUR_API_KEY
{
"fileBody": "EMAIL;FIRSTNAME;LASTNAME\n[email protected];Jane;Doe",
"listIds": [42],
"updateExistingContacts": true,
"emailBlacklist": false,
"smsBlacklist": false
}

レスポンスは素早く返ります。

{ "processId": 78 }

これは202 Accepted です。Brevo がインポートを受け付け、バックグラウンドで処理しています。processId は完了をポーリングしたり notifyUrl webhook を設定したりする際の追跡ハンドルです。

コードを書く前に押さえておきたい点がいくつかあります。

  • 本文は CSV(fileBody)、JSON(jsonBody)、リモート URL(fileUrl)のいずれかにできます。 1 つを選んでください。CSV 形式は列の区切りにカンマではなくセミコロン(;)を使います。よくある落とし穴です。
  • fileBodyjsonBody は 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 ヘッダーとして渡します。アカウントに対する完全な読み書き権限を持つので、パスワードのように扱ってください。

きれいなパターンとして、ソースツリーに残らないよう環境変数に入れます。

Terminal window
export BREVO_API_KEY="xkeysib-..."

Python: CSV を読んで Brevo にプッシュ

これは可能な限りシンプルなスクリプトです。標準ライブラリでローカル CSV を読み、本文をそのまま Brevo に送ります。requests 以外のサードパーティ SDK は不要です。

import_csv_to_brevo.py
import csv
import io
import os
import sys
import requests
API_KEY = os.environ["BREVO_API_KEY"]
LIST_ID = 42 # the Brevo list to add contacts to
INPUT_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}")

実行します。

Terminal window
python import_csv_to_brevo.py contacts.csv

CSV の最初の行はヘッダーです。列名は 大文字での完全一致 で Brevo 属性にマッピングされます。EMAILFIRSTNAMELASTNAME に加え、定義したカスタム属性が利用できます。EMAIL は必須です。

EMAIL,FIRSTNAME,LASTNAME,COMPANY,CITY
[email protected],Jane,Doe,Acme,Berlin
[email protected],John,Smith,Globex,Paris

これがフロー全体です。スクリプトは 1 秒以内に戻ります。実際のインポートはサーバーサイドで実行され、ボリュームに応じて数秒から数分かかります。

Excel ファイル: 実用的な 3 つのパス

Brevo のインポートエンドポイントは .xlsx を直接読まないため、作業がどこで行われるかに応じて 3 つの実用的な選択肢があります。

  1. ワークブック内で VBA マクロを実行する。 外部スクリプトなしで、Excel 自体からワンクリック同期できます。ファイルがデスクトップにあり、チームがシート上にボタンを欲しがっている場合の正解です。完全なコードは Excel-to-Brevo VBA マクロガイドにあります。
  2. Office Scripts + Power Automate。 ワークブックが OneDrive/SharePoint にあり、無人スケジュール同期を望む場合に。Excel ガイドでも取り上げています。
  3. スクリプトから .xlsx を CSV に変換する。 このセクションの残りでカバーする内容です。すでにサーバーで Python や Node を実行しており、1 日に 1 回ワークブックを取り込むだけで良い場合に最適です。

選択肢 3 の場合、pandas を使えば 1 行で済みます。

# pip install pandas openpyxl
import 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_workbook
import 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 を公開しています。認証、リトライ、型付きのリクエスト形状を扱います。

import-csv-to-brevo.mjs
// npm install @getbrevo/brevo
import 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 needed
const 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: アドホックなインポート用ワンライナー

エンドポイントをテストしたり、小さなファイルを手動で送り込みたいだけのときに。

Terminal window
# Convert commas to semicolons, then send the file inline
csv=$(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": [
{
"email": "[email protected]",
"attributes": {
"FIRSTNAME": "Jane",
"LASTNAME": "Doe",
"COMPANY": "Acme",
},
},
{
"email": "[email protected]",
"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 ヘッダー名が間違っています。AuthorizationX-API-Key ではなく api-key(小文字、ハイフン)です。

インポートが「成功」したのに、連絡先がリストに表示されない。 listIds に正しいリストが含まれているか確認してください。また、updateExistingContactsfalse で連絡先がすでに存在する場合、Brevo はリストに再追加せずに静かにスキップします。

一部の行はインポートされ、一部はされない。 インポートが完了すると Brevo が行ごとのエラーレポートをメールで送ります(disableNotification: true を設定していない限り)。レポートには、悪いメールアドレス、必須フィールドの欠落、フォーマットの問題があった行が記載されます。

スクリプト化すべきとき、UI を使うべきとき

UI は 1,000 行未満のワンショットインポートには問題ありません。次のいずれかに該当する場合、スクリプトが勝ります。

  • 複数回インポートする場合(CRM からの週次エクスポートなど)
  • ソースデータを Brevo に入れる前にクリーニングが必要な場合(重複排除、電話番号のフォーマット、フルネームを姓名に分割)
  • 誰もボタンをクリックせずにスケジュール実行したい場合
  • ファイルが UI の快適ゾーンより大きい場合

上記のスクリプトを cron ジョブや GitHub Action でラップすれば、自動連絡先同期の完成です。このシリーズの次の投稿では、Apps Script を使って Google Sheet から直接同じことを行う方法を示します。サーバー不要です。

さらに読む

Frequently Asked Questions

連絡先を一括インポートするためのBrevo APIエンドポイントは何ですか?
POST https://api.brevo.com/v3/contacts/import です。CSVコンテンツ(fileBody)、リモートファイルURL(fileUrl)、またはJSON配列(jsonBody)を受け付けます。エンドポイントは非同期で、processIdをすぐに返し、インポートはバックグラウンドで完了します。
ファイルサイズの上限はどれくらいですか?
インラインCSV(fileBody)またはJSON(jsonBody)で10MBです。それより大きなファイルの場合は、到達可能な場所にファイルをホストし、fileUrlでURLを渡してください。このパスにはドキュメント化されたサイズ上限はありません。
.xlsxファイルはどうやってインポートしますか?
BrevoのインポートAPIは.csv、.txt、.jsonのみを受け付けます。まず.xlsxを.csvに変換してください。pandas、openpyxl、またはLibreOffice headlessで1行で変換できます。このガイドのスクリプトにはxlsxからcsvへの変換が含まれています。
既存の連絡先は上書きされますか?
デフォルトでは上書きされます。updateExistingContactsはデフォルトでtrueで、メールアドレスでマッチングします。falseに設定すると既存の連絡先をスキップします。空のCSVセルで既存の値を消去したい場合はemptyContactsAttributesをtrueに設定してください(そうでない場合は空のセルは無視されます)。
Brevoで無料で始める