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.
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/importContent-Type: application/jsonapi-key: YOUR_API_KEY
{ "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. fileBodyvàjsonBodygiớ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ềnfileUrlthay 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.
updateExistingContactsmặc định làtrue. Nếu bạn đặt thànhfalse, 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”.emptyContactsAttributeskiểm soát việc các ô trống trong CSV có xóa giá trị hiện có hay không. Mặc địnhfalse(các ô trống bị bỏ qua). Đặt thànhtruenế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.
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 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}")Chạy nó:
python import_csv_to_brevo.py contacts.csvHà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Đó 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:
- 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.
- 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.
- Chuyển đổi
.xlsxsang 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 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")Nếu bạn không muốn pandas làm dependency, riêng openpyxl cũng đủ:
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: 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:
// 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}`);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:
# 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 . 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": [ { "attributes": { "FIRSTNAME": "Jane", "LASTNAME": "Doe", "COMPANY": "Acme", }, }, { "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 updateExistingContacts là false 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.