Как да импортирате контакти от CSV или Excel в Brevo чрез скрипт (Python, Node.js, cURL)

Импортирайте групово контакти от CSV или Excel файл в Brevo чрез import-contacts API. Включва готови за изпълнение скриптове на Python и Node.js, едноредов cURL, обработка на грешки и съвети за файлове, по-големи от 10 MB.

Featured image for article: Как да импортирате контакти от CSV или Excel в Brevo чрез скрипт (Python, Node.js, cURL)

Ако някога Ви се е налагало да качите няколко хиляди контакта от електронна таблица в Brevo, ръчният път през интерфейса бързо става уморителен: избирате файл, мапвате колони, избирате списък, чакате, повтаряте. Скрипт върши същата работа за секунди и, по-важно, можете да го изпълнявате повторно по график, след експорт от база данни или винаги когато Вашата CRM система генерира нов CSV.

Това ръководство покрива API endpoint-а, който Brevo Ви предоставя точно за тази цел, с пълни работещи скриптове на Python, Node.js и едноредов cURL. Всичко в това ръководство използва POST /v3/contacts/import.

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
}

Отговорът се връща бързо:

{ "processId": 78 }

Това е 202 Accepted. Brevo прие импорта и го обработва във фонов режим. processId е Вашият идентификатор за проследяване, ако искате да следите за завършване или да настроите webhook чрез notifyUrl.

Няколко неща, които си струва да отбележите, преди да започнете да пишете код:

  • Тялото може да бъде CSV (fileBody), JSON (jsonBody) или отдалечен URL (fileUrl). Изберете едно. CSV формата използва точка и запетая (;) за разделител на колоните, не запетая. Това е често допускана грешка.
  • fileBody и jsonBody са ограничени до 10 MB. Brevo препоръчва да останете около 8 MB за безопасност. За нещо по-голямо качете файла в S3, GCS или който и да е HTTPS хост и подайте fileUrl.
  • Персонализирани атрибути, които не съществуват във Вашия акаунт, се игнорират мълчаливо. Създайте ги в интерфейса на Brevo (или чрез Attributes API), преди да импортирате редове, които ги използват, иначе данните просто изчезват.
  • updateExistingContacts по подразбиране е true. Ако го зададете на false, Brevo пропуска контакти, чийто email вече съществува. Полезно за задачи „само добавяне на нови”.
  • emptyContactsAttributes контролира дали празните клетки в CSV изтриват съществуващи стойности. По подразбиране е false (празните клетки се игнорират). Задайте true, ако CSV е източникът на истината и искате празните стойности да изчистят остарели данни.

Вземете API ключ

Влезте в Brevo → Settings → SMTP & API → API Keys → създайте нов ключ. Изглежда като xkeysib-.... Ще го подавате като HTTP header api-key при всяка заявка. Третирайте го като парола, тъй като има пълен read/write достъп до Вашия акаунт.

Чист подход: сложете го в променлива на средата, за да не попадне в кода Ви.

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

Python: прочетете CSV и го изпратете към Brevo

Това е възможно най-простият скрипт: четете локален CSV със стандартната библиотека и изпращате тялото директно към Brevo. Не е нужен сторонен SDK, освен 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}")

Изпълнете го:

Terminal window
python import_csv_to_brevo.py contacts.csv

Първият ред на Вашия CSV е заглавният. Имената на колоните се мапват към атрибутите на Brevo чрез главни букви, точно съвпадение: EMAIL, FIRSTNAME, LASTNAME, плюс всеки персонализиран атрибут, който сте дефинирали. EMAIL е задължителен.

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

Това е целият процес. Скриптът се връща за по-малко от секунда, а самото импортиране тече от страна на сървъра и отнема от няколко секунди до няколко минути в зависимост от обема.

Excel файлове: три работещи подхода

Импорт endpoint-ът на Brevo не чете .xlsx директно, така че имате три реални опции в зависимост от това къде трябва да живее работата:

  1. Изпълнете VBA макрос вътре в работната книга. Синхронизация с едно щракване от самия Excel, без външен скрипт. Това е правилният отговор, когато файлът се намира на работен компютър и екипът Ви иска бутон в таблицата. Пълният код е в ръководството за VBA макрос от Excel към Brevo.
  2. Office Scripts + Power Automate. Ако работната книга е в OneDrive/SharePoint и искате автоматична синхронизация по график. Също е разгледано в ръководството за Excel.
  3. Конвертиране на .xlsx към CSV от скрипт. Това разглежда останалата част от тази секция. Най-добре е, ако вече изпълнявате Python или Node на сървър и просто трябва да изтегляте работна книга веднъж дневно.

За опция 3 pandas го прави с един ред:

# 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 публикува @getbrevo/brevo за Node. Той се грижи за автентикация, повторни опити и типизирана форма на заявката:

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: едноредов вариант за инцидентни импорти

Когато просто искате да тествате endpoint-а или да качите малък файл ръчно:

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-екранира, което Ви спестява ръчното екраниране на нови редове и кавички.

Файлове, по-големи от 10 MB: използвайте fileUrl

fileBody е ограничен до 10 MB. Ако Вашият експорт на контакти е по-голям, хоствайте файла на URL, който Brevo може да достъпи, и го подайте:

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.

Изпратете JSON вместо CSV

Ако вече извличате контакти от база данни, не е нужно да правите междинна стъпка през 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],
}

Същият лимит от 10 MB. Същото асинхронно поведение.

Опитване за завършване

Импортът е асинхронен. processId е Вашият идентификатор за проследяване. Има отделен endpoint за проверка на статуса на процеса:

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: подайте HTTPS endpoint, на който Brevo ще изпрати POST, когато импортът приключи, и пропуснете опитването.

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

Често срещани грешки и как да ги поправите

400 Bad Request без очевидна причина. Почти винаги е заради точки и запетаи срещу запетаи в CSV. Импортът на Brevo очаква ;, не ,. Проверете отново fileBody след стъпката за конвертиране.

Стойностите на персонализирани атрибути изчезват. Атрибутът не съществува във Вашия акаунт. Създайте го под Contacts → Settings → Contact attributes преди импортирането или използвайте Attributes API, за да го създадете като част от Вашия скрипт.

401 Unauthorized. Грешно име на header-а. Името е api-key (с малки букви, тире), не Authorization или X-API-Key.

Импортът „успя”, но контактите не се появиха в списъка. Проверете дали listIds съдържа правилния списък. Също така: ако updateExistingContacts е false и контактите вече съществуват, Brevo ги пропуска тихо, вместо да ги добавя отново към списъка.

Някои редове са импортирани, други не. Brevo Ви изпраща имейл с отчет за грешки по редове, след като импортът приключи (освен ако не сте задали disableNotification: true). Отчетът Ви казва кои редове са имали лоши имейли, липсващи задължителни полета или проблеми с форматирането.

Кога да използвате скрипт и кога интерфейса

Интерфейсът е добър за еднократни импорти под хиляда реда. Скрипт побеждава веднага щом:

  • Импортирате повече от веднъж (например седмичен експорт от Вашия CRM)
  • Изходните данни се нуждаят от почистване, преди да попаднат в Brevo (премахване на дубликати, форматиране на телефонни номера, разделяне на пълни имена на първо/фамилно)
  • Искате да се изпълнява по график без някой да щрака бутони
  • Файлът е по-голям от това, което интерфейсът удобно обработва

Опаковайте скрипта по-горе в cron задача или GitHub Action и получавате автоматизирана синхронизация на контакти. Следващата публикация в тази серия показва как да правите същото директно от Google Sheet чрез Apps Script, без сървър.

Допълнително четиво

Frequently Asked Questions

Кой е API endpoint-ът на Brevo за групово импортиране на контакти?
POST https://api.brevo.com/v3/contacts/import. Той приема CSV съдържание (fileBody), URL към отдалечен файл (fileUrl) или JSON масив (jsonBody). Endpoint-ът е асинхронен, връща processId веднага и завършва импортирането във фонов режим.
Каква е максималната големина на файла?
10 MB за вграден CSV (fileBody) или JSON (jsonBody). За по-големи файлове хоствайте файла на достъпно място и подайте URL-а му чрез fileUrl. Този път няма документиран лимит за размер.
Как да импортирам .xlsx файл?
Импорт API-то на Brevo приема само .csv, .txt или .json. Преди това конвертирайте .xlsx към .csv. pandas, openpyxl или LibreOffice headless могат да го направят с един ред. Скриптът в това ръководство включва конвертиране от xlsx към csv.
Ще презапише ли съществуващи контакти?
По подразбиране, да. updateExistingContacts е true по подразбиране и съпоставя по email. Задайте го на false, за да пропуснете контакти, които вече съществуват. Задайте emptyContactsAttributes на true, ако искате празните клетки в CSV да изтриват съществуващи стойности (иначе празните клетки се игнорират).
Започнете безплатно с Brevo