Как да импортирате контакти от CSV или Excel в Brevo чрез скрипт (Python, Node.js, cURL)
Импортирайте групово контакти от CSV или Excel файл в Brevo чрез import-contacts API. Включва готови за изпълнение скриптове на Python и Node.js, едноредов cURL, обработка на грешки и съвети за файлове, по-големи от 10 MB.
Ако някога Ви се е налагало да качите няколко хиляди контакта от електронна таблица в Brevo, ръчният път през интерфейса бързо става уморителен: избирате файл, мапвате колони, избирате списък, чакате, повтаряте. Скрипт върши същата работа за секунди и, по-важно, можете да го изпълнявате повторно по график, след експорт от база данни или винаги когато Вашата CRM система генерира нов CSV.
Това ръководство покрива API endpoint-а, който Brevo Ви предоставя точно за тази цел, с пълни работещи скриптове на Python, Node.js и едноредов cURL. Всичко в това ръководство използва POST /v3/contacts/import.
Endpoint-ът накратко
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 е Вашият идентификатор за проследяване, ако искате да следите за завършване или да настроите 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 достъп до Вашия акаунт.
Чист подход: сложете го в променлива на средата, за да не попадне в кода Ви.
export BREVO_API_KEY="xkeysib-..."Python: прочетете CSV и го изпратете към Brevo
Това е възможно най-простият скрипт: четете локален CSV със стандартната библиотека и изпращате тялото директно към Brevo. Не е нужен сторонен SDK, освен 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}")Изпълнете го:
python import_csv_to_brevo.py contacts.csvПървият ред на Вашия CSV е заглавният. Имената на колоните се мапват към атрибутите на Brevo чрез главни букви, точно съвпадение: EMAIL, FIRSTNAME, LASTNAME, плюс всеки персонализиран атрибут, който сте дефинирали. EMAIL е задължителен.
EMAIL,FIRSTNAME,LASTNAME,COMPANY,CITYТова е целият процес. Скриптът се връща за по-малко от секунда, а самото импортиране тече от страна на сървъра и отнема от няколко секунди до няколко минути в зависимост от обема.
Excel файлове: три работещи подхода
Импорт endpoint-ът на Brevo не чете .xlsx директно, така че имате три реални опции в зависимост от това къде трябва да живее работата:
- Изпълнете VBA макрос вътре в работната книга. Синхронизация с едно щракване от самия Excel, без външен скрипт. Това е правилният отговор, когато файлът се намира на работен компютър и екипът Ви иска бутон в таблицата. Пълният код е в ръководството за VBA макрос от Excel към Brevo.
- Office Scripts + Power Automate. Ако работната книга е в OneDrive/SharePoint и искате автоматична синхронизация по график. Също е разгледано в ръководството за Excel.
- Конвертиране на
.xlsxкъм CSV от скрипт. Това разглежда останалата част от тази секция. Най-добре е, ако вече изпълнявате Python или Node на сървър и просто трябва да изтегляте работна книга веднъж дневно.
За опция 3 pandas го прави с един ред:
# 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 публикува @getbrevo/brevo за Node. Той се грижи за автентикация, повторни опити и типизирана форма на заявката:
// 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: едноредов вариант за инцидентни импорти
Когато просто искате да тествате endpoint-а или да качите малък файл ръчно:
# 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-екранира, което Ви спестява ръчното екраниране на нови редове и кавички.
Файлове, по-големи от 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": [ { "attributes": { "FIRSTNAME": "Jane", "LASTNAME": "Doe", "COMPANY": "Acme", }, }, { "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, без сървър.