Cách nhập danh bạ CSV hoặc Excel vào Brevo bằng script (Python, Node.js, cURL)

Nhập hàng loạt danh bạ từ tệp CSV hoặc Excel vào Brevo bằng API import-contacts. Bao gồm script Python và Node.js sẵn sàng chạy, lệnh cURL một dòng, xử lý lỗi và mẹo cho tệp lớn hơn 10 MB.

Featured image for article: Cách nhập danh bạ CSV hoặc Excel vào Brevo bằng script (Python, Node.js, cURL)

Nếu bạn từng cần đẩy vài nghìn danh bạ từ một bảng tính vào Brevo, lộ trình UI thủ công nhanh chóng trở nên mệt mỏi: chọn tệp, ánh xạ cột, chọn danh sách, chờ, lặp lại. Một script làm cùng công việc đó trong vài giây và quan trọng hơn, bạn có thể chạy lại nó theo lịch, sau khi xuất từ cơ sở dữ liệu hoặc bất cứ khi nào CRM của bạn xuất ra một CSV mới.

Hướng dẫn này bao gồm endpoint API mà Brevo cung cấp đúng cho mục đích đó, với các script đầy đủ chạy được bằng Python, Node.js và một lệnh cURL một dòng. Mọi thứ trong hướng dẫn này đều gửi đến POST /v3/contacts/import.

Tổng quan endpoint

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
}

Phản hồi trả về nhanh chóng:

{ "processId": 78 }

Đó là 202 Accepted, Brevo đã chấp nhận import và đang xử lý trong nền. processId là handle theo dõi của bạn nếu bạn muốn polling để kiểm tra hoàn thành hoặc cài đặt webhook notifyUrl.

Một vài điều cần lưu ý trước khi viết code:

  • Body có thể là CSV (fileBody), JSON (jsonBody) hoặc URL từ xa (fileUrl). Chọn một. Định dạng CSV sử dụng dấu chấm phẩy (;) để phân tách cột, không phải dấu phẩy. Đó là một cái bẫy phổ biến.
  • fileBodyjsonBody giới hạn 10 MB. Brevo khuyến nghị giữ ở mức khoảng 8 MB để an toàn. Với bất cứ tệp nào lớn hơn, hãy tải tệp lên S3, GCS hoặc bất kỳ host HTTPS nào và truyền fileUrl thay thế.
  • Custom attributes không tồn tại trong tài khoản của bạn sẽ bị bỏ qua mà không báo lỗi. Hãy tạo chúng trong UI của Brevo (hoặc qua Attributes API) trước khi import các hàng dùng chúng, nếu không dữ liệu sẽ biến mất.
  • updateExistingContacts mặc định là true. Nếu bạn đặt thành false, Brevo bỏ qua các danh bạ có email đã tồn tại. Hữu ích cho các tác vụ “chỉ thêm mới”.
  • emptyContactsAttributes kiểm soát việc các ô trống trong CSV có xóa giá trị hiện có hay không. Mặc định false (các ô trống bị bỏ qua). Đặt thành true nếu CSV của bạn là nguồn sự thật và bạn muốn các ô trống xóa dữ liệu cũ.

Lấy API key

Đăng nhập vào Brevo → Settings → SMTP & API → API Keys → tạo key mới. Nó trông như xkeysib-.... Bạn sẽ truyền nó dưới dạng header HTTP api-key trong mọi request. Hãy coi nó như mật khẩu, nó có toàn quyền đọc/ghi trên tài khoản của bạn.

Một mẫu sạch sẽ: đặt nó vào biến môi trường để nó không bao giờ rơi vào source tree của bạn.

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

Python: đọc CSV, đẩy lên Brevo

Đây là script đơn giản nhất có thể: đọc CSV cục bộ với thư viện chuẩn, gửi body thẳng đến Brevo. Không cần SDK bên thứ ba ngoài requests.

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}")

Chạy nó:

Terminal window
python import_csv_to_brevo.py contacts.csv

Hàng đầu tiên của CSV là header. Tên cột ánh xạ tới các attribute của Brevo theo chữ hoa, khớp chính xác: EMAIL, FIRSTNAME, LASTNAME, cộng với bất kỳ custom attribute nào bạn đã định nghĩa. EMAIL là bắt buộc.

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

Đó là toàn bộ luồng. Script trả về trong vòng một giây; quá trình import thực tế chạy ở phía server và mất từ vài giây đến vài phút tùy vào khối lượng.

Tệp Excel: ba lộ trình khả thi

Endpoint import của Brevo không đọc trực tiếp .xlsx, nên bạn có ba lựa chọn thực sự tùy vào nơi công việc cần diễn ra:

  1. Chạy macro VBA bên trong workbook: đồng bộ một-cú-click từ chính Excel, không cần script bên ngoài. Đây là câu trả lời đúng khi tệp nằm trên desktop và nhóm bạn muốn một nút trên sheet. Code đầy đủ trong hướng dẫn macro VBA Excel sang Brevo.
  2. Office Scripts + Power Automate: nếu workbook nằm trên OneDrive/SharePoint và bạn muốn đồng bộ theo lịch không cần giám sát. Cũng được nói tới trong hướng dẫn Excel.
  3. Chuyển đổi .xlsx sang CSV bằng script: nội dung mà phần còn lại của mục này đề cập. Tốt nhất nếu bạn đã chạy Python hoặc Node trên server và chỉ cần kéo workbook về một lần mỗi ngày.

Với lựa chọn 3, pandas khiến nó thành một dòng duy nhất:

# 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")

Nếu bạn không muốn pandas làm dependency, riêng openpyxl cũng đủ:

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: cùng một việc, SDK chính thức

Brevo phát hành @getbrevo/brevo cho Node. Nó xử lý xác thực, retry và shape request có kiểu:

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}`);

Hoặc, nếu bạn không muốn SDK, fetch thuần cũng hoạt động theo cùng cách:

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: lệnh một dòng cho import tức thời

Khi bạn chỉ muốn test endpoint hoặc đẩy thủ công một tệp nhỏ vào:

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 . nuốt toàn bộ tệp và escape JSON. Giúp bạn không phải escape thủ công các xuống dòng và dấu nháy.

Tệp lớn hơn 10 MB: dùng fileUrl

fileBody giới hạn 10 MB. Nếu dump danh bạ của bạn lớn hơn, hãy lưu trữ tệp tại một URL mà Brevo có thể fetch và truyền nó:

body = {
"fileUrl": "https://files.example.com/contacts/2026-04-30.csv",
"listIds": [42],
"updateExistingContacts": True,
}

Bất cứ thứ gì có thể truy cập qua URL HTTPS công khai đều hoạt động: URL S3 đã ký trước, GCS, host tĩnh của riêng bạn, thậm chí URL raw của GitHub cho import một lần. Brevo fetch tệp từ URL của bạn, sau đó chạy import. Định dạng được chấp nhận: .csv, .txt, .json.

Gửi JSON thay vì CSV

Nếu bạn đã kéo danh bạ từ cơ sở dữ liệu, không cần đi vòng qua CSV, hãy gửi chúng dưới dạng JSON trực tiếp:

body = {
"jsonBody": [
{
"email": "[email protected]",
"attributes": {
"FIRSTNAME": "Jane",
"LASTNAME": "Doe",
"COMPANY": "Acme",
},
},
{
"email": "[email protected]",
"attributes": {
"FIRSTNAME": "John",
"LASTNAME": "Smith",
"COMPANY": "Globex",
},
},
],
"listIds": [42],
}

Cùng giới hạn 10 MB. Cùng hành vi bất đồng bộ.

Polling để kiểm tra hoàn thành

Quá trình import bất đồng bộ, processId là handle để theo dõi nó. Có một endpoint riêng để kiểm tra trạng thái process:

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}")

Với các tác vụ chạy lâu, mẫu sạch hơn là notifyUrl: truyền một endpoint HTTPS mà Brevo sẽ POST đến khi import kết thúc và bỏ qua polling.

body["notifyUrl"] = "https://your-app.example.com/webhooks/brevo-import"

Lỗi thường gặp và cách khắc phục

400 Bad Request không có nguyên nhân rõ ràng: hầu như luôn là vấn đề dấu chấm phẩy so với dấu phẩy trong CSV. Quá trình import của Brevo mong đợi ;, không phải ,. Kiểm tra lại fileBody sau bước chuyển đổi của bạn.

Giá trị custom attribute biến mất: Attribute không tồn tại trong tài khoản của bạn. Tạo nó trong Contacts → Settings → Contact attributes trước khi import, hoặc dùng Attributes API để tạo nó như một phần của script.

401 Unauthorized: Sai tên header. Đó là api-key (chữ thường, có dấu gạch nối), không phải Authorization hoặc X-API-Key.

Import “thành công” nhưng danh bạ không xuất hiện trong list: Kiểm tra xem listIds có chứa list đúng không. Ngoài ra: nếu updateExistingContactsfalse và danh bạ đã tồn tại, Brevo âm thầm bỏ qua chúng thay vì thêm lại vào list.

Một số hàng được import, một số không: Brevo gửi email báo cáo lỗi từng hàng cho bạn sau khi import hoàn tất (trừ khi bạn đặt disableNotification: true). Báo cáo cho bạn biết hàng nào có email sai, thiếu trường bắt buộc hoặc các vấn đề định dạng.

Khi nào nên dùng script và khi nào dùng UI

UI ổn cho các import một lần dưới một nghìn hàng. Một script thắng ngay khi:

  • Bạn import nhiều hơn một lần (ví dụ xuất hàng tuần từ CRM của bạn)
  • Dữ liệu nguồn cần làm sạch trước khi đưa vào Brevo (loại trùng lặp, định dạng số điện thoại, tách họ tên thành tên/họ)
  • Bạn muốn nó chạy theo lịch mà không có ai bấm nút
  • Tệp lớn hơn vùng thoải mái của UI

Bọc script ở trên trong một cron job hoặc một GitHub Action và bạn có đồng bộ danh bạ tự động. Bài tiếp theo trong loạt này hướng dẫn cách làm điều tương tự trực tiếp từ Google Sheet bằng Apps Script, không cần server.

Đọc thêm

Frequently Asked Questions

Endpoint API của Brevo để nhập danh bạ hàng loạt là gì?
POST https://api.brevo.com/v3/contacts/import. Endpoint chấp nhận nội dung CSV (fileBody), URL tệp từ xa (fileUrl) hoặc mảng JSON (jsonBody). Endpoint này bất đồng bộ, trả về processId ngay lập tức và hoàn tất việc nhập trong nền.
Kích thước tệp tối đa là bao nhiêu?
10 MB cho CSV inline (fileBody) hoặc JSON (jsonBody). Với tệp lớn hơn, hãy lưu trữ tệp ở nơi có thể truy cập được và truyền URL qua fileUrl. Cách này không có giới hạn kích thước nào được công bố.
Làm thế nào để nhập tệp .xlsx?
API import của Brevo chỉ chấp nhận .csv, .txt hoặc .json. Hãy chuyển .xlsx sang .csv trước. pandas, openpyxl hoặc LibreOffice headless có thể làm việc đó trong một dòng. Script trong hướng dẫn này bao gồm bước chuyển đổi xlsx sang csv.
Có ghi đè danh bạ hiện có không?
Mặc định là có. updateExistingContacts mặc định là true và đối chiếu theo email. Đặt thành false để bỏ qua các danh bạ đã tồn tại. Đặt emptyContactsAttributes thành true nếu bạn muốn các ô CSV trống xóa giá trị hiện có (nếu không, các ô trống sẽ bị bỏ qua).
Bắt đầu miễn phí với Brevo