Πώς να εισάγετε επαφές CSV ή Excel στο Brevo με ένα script (Python, Node.js, cURL)

Μαζική εισαγωγή επαφών από αρχείο CSV ή Excel στο Brevo μέσω του API import-contacts. Περιλαμβάνει έτοιμα προς εκτέλεση scripts σε Python και Node.js, ένα one-liner cURL, διαχείριση σφαλμάτων και συμβουλές για αρχεία μεγαλύτερα από 10 MB.

Featured image for article: Πώς να εισάγετε επαφές CSV ή Excel στο Brevo με ένα script (Python, Node.js, cURL)

Αν χρειάστηκε ποτέ να στείλετε μερικές χιλιάδες επαφές από ένα φύλλο εργασίας στο Brevo, η χειροκίνητη διαδρομή μέσω του UI γίνεται γρήγορα κουραστική: επιλέξτε το αρχείο, αντιστοιχίστε στήλες, επιλέξτε λίστα, περιμένετε, επαναλάβετε. Ένα script κάνει την ίδια δουλειά σε δευτερόλεπτα και, ακόμη πιο σημαντικό, μπορείτε να το εκτελέσετε ξανά σε προγραμματισμένη βάση, μετά από εξαγωγή βάσης δεδομένων ή κάθε φορά που το CRM σας παράγει ένα νέο CSV.

Αυτός ο οδηγός καλύπτει το API endpoint που σας δίνει το Brevo ακριβώς για αυτό, με πλήρως λειτουργικά scripts σε Python, Node.js και ένα one-liner 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 είναι η λαβή παρακολούθησής σας αν θέλετε να κάνετε polling για ολοκλήρωση ή να στήσετε ένα notifyUrl webhook.

Μερικά πράγματα που αξίζει να σημειώσετε πριν γράψετε κώδικα:

  • Το body μπορεί να είναι CSV (fileBody), JSON (jsonBody) ή απομακρυσμένο URL (fileUrl). Επιλέξτε ένα. Η μορφή CSV χρησιμοποιεί άνω και κάτω τελείες (;) για τον διαχωρισμό στηλών, όχι κόμματα. Αυτή είναι μια συνηθισμένη παγίδα.
  • Το fileBody και το jsonBody περιορίζονται σε 10 MB. Το Brevo συνιστά να μένετε γύρω στα 8 MB για ασφάλεια. Για οτιδήποτε μεγαλύτερο, ανεβάστε το αρχείο σε S3, GCS ή οποιονδήποτε HTTPS host και περάστε το fileUrl αντί αυτού.
  • Προσαρμοσμένα attributes που δεν υπάρχουν στον λογαριασμό σας αγνοούνται σιωπηλά. Δημιουργήστε τα στο UI του Brevo (ή μέσω του Attributes API) πριν εισάγετε γραμμές που τα χρησιμοποιούν, αλλιώς τα δεδομένα απλά εξαφανίζονται.
  • Το updateExistingContacts είναι από προεπιλογή true. Αν το ορίσετε σε false, το Brevo παραλείπει επαφές των οποίων το email υπάρχει ήδη. Χρήσιμο για εργασίες «μόνο νέες προσθήκες».
  • Το emptyContactsAttributes ελέγχει αν τα κενά κελιά στο CSV σας διαγράφουν υπάρχουσες τιμές. Προεπιλογή false (τα κενά κελιά αγνοούνται). Ορίστε το σε true αν το CSV είναι η πηγή της αλήθειας και θέλετε τα κενά να καθαρίζουν παρωχημένα δεδομένα.

Αποκτήστε ένα API key

Συνδεθείτε στο Brevo → Settings → SMTP & API → API Keys → δημιουργήστε ένα νέο κλειδί. Φαίνεται σαν xkeysib-.... Θα το περνάτε ως HTTP header api-key σε κάθε αίτημα. Αντιμετωπίστε το σαν κωδικό πρόσβασης, έχει πλήρη πρόσβαση ανάγνωσης/εγγραφής στον λογαριασμό σας.

Ένα καθαρό μοτίβο: βάλτε το σε μια μεταβλητή περιβάλλοντος ώστε να μην μπει ποτέ στο δέντρο πηγαίου κώδικά σας.

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

Python: διαβάστε ένα CSV, στείλτε το στο Brevo

Αυτό είναι το απλούστερο δυνατό script: διαβάστε ένα τοπικό CSV με την τυπική βιβλιοθήκη, στείλτε το body κατευθείαν στο 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 σας είναι η κεφαλίδα. Τα ονόματα στηλών αντιστοιχίζονται σε attributes του Brevo με κεφαλαία γράμματα, ακριβή αντιστοιχία: EMAIL, FIRSTNAME, LASTNAME, συν οποιοδήποτε προσαρμοσμένο attribute έχετε ορίσει. Το EMAIL είναι υποχρεωτικό.

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

Αυτή είναι όλη η ροή. Το script επιστρέφει μέσα σε ένα δευτερόλεπτο, η πραγματική εισαγωγή τρέχει στην πλευρά του server και διαρκεί από λίγα δευτερόλεπτα έως λίγα λεπτά ανάλογα με τον όγκο.

Αρχεία Excel: τρεις βιώσιμες διαδρομές

Το endpoint εισαγωγής του Brevo δεν διαβάζει .xlsx απευθείας, οπότε έχετε τρεις πραγματικές επιλογές ανάλογα με το πού πρέπει να ζει η εργασία:

  1. Εκτελέστε μια μακροεντολή VBA μέσα στο workbook. Συγχρονισμός με ένα κλικ από το ίδιο το Excel, χωρίς εξωτερικό script. Αυτή είναι η σωστή απάντηση όταν το αρχείο ζει σε επιφάνεια εργασίας και η ομάδα σας θέλει ένα κουμπί στο φύλλο. Πλήρης κώδικας στον οδηγό μακροεντολής VBA Excel σε Brevo.
  2. Office Scripts + Power Automate. Αν το workbook ζει σε OneDrive/SharePoint και θέλετε προγραμματισμένο συγχρονισμό χωρίς επίβλεψη. Καλύπτεται επίσης στον οδηγό Excel.
  3. Μετατρέψτε .xlsx σε CSV από script. Αυτό καλύπτει η υπόλοιπη ενότητα. Καλύτερο αν ήδη τρέχετε Python ή Node σε server και απλά θέλετε να τραβάτε ένα workbook μία φορά την ημέρα.

Για την επιλογή 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: ένα one-liner για εισαγωγές ad-hoc

Όταν απλά θέλετε να δοκιμάσετε το 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-escape, σας γλιτώνει από το χειροκίνητο escape νέων γραμμών και εισαγωγικών.

Αρχεία μεγαλύτερα από 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 λειτουργεί, προ-υπογεγραμμένα URL του S3, GCS, ο δικός σας στατικός host, ακόμη και ένα 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. Ίδια ασύγχρονη συμπεριφορά.

Polling για ολοκλήρωση

Η εισαγωγή είναι ασύγχρονη, το 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 όταν τελειώσει η εισαγωγή και παραλείψτε το polling.

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

Συνηθισμένα σφάλματα και πώς να τα διορθώσετε

400 Bad Request χωρίς προφανή αιτία. Σχεδόν πάντα άνω και κάτω τελείες έναντι κομμάτων στο CSV. Η εισαγωγή του Brevo αναμένει ;, όχι ,. Ξανατσεκάρετε το fileBody μετά το βήμα μετατροπής.

Τιμές προσαρμοσμένων attributes εξαφανίζονται. Το attribute δεν υπάρχει στον λογαριασμό σας. Δημιουργήστε το στο Contacts → Settings → Contact attributes πριν την εισαγωγή ή χρησιμοποιήστε το Attributes API για να το δημιουργήσετε ως μέρος του script σας.

401 Unauthorized. Λάθος όνομα header. Είναι api-key (πεζά γράμματα, παύλα), όχι Authorization ή X-API-Key.

Η εισαγωγή «πέτυχε» αλλά οι επαφές δεν εμφανίστηκαν στη λίστα. Ελέγξτε ότι το listIds περιέχει τη σωστή λίστα. Επίσης: αν το updateExistingContacts είναι false και οι επαφές υπάρχουν ήδη, το Brevo τις παραλείπει σιωπηλά αντί να τις προσθέσει ξανά στη λίστα.

Κάποιες γραμμές εισήχθησαν, άλλες όχι. Το Brevo σας στέλνει email με αναφορά σφαλμάτων ανά γραμμή αφού ολοκληρωθεί η εισαγωγή (εκτός αν ορίσετε disableNotification: true). Η αναφορά σας λέει ποιες γραμμές είχαν κακά emails, ελλιπή υποχρεωτικά πεδία ή προβλήματα μορφοποίησης.

Πότε να γράψετε script έναντι πότε να χρησιμοποιήσετε το UI

Το UI είναι μια χαρά για εφάπαξ εισαγωγές κάτω από χίλιες γραμμές. Ένα script κερδίζει μόλις:

  • Εισάγετε περισσότερες από μία φορές (π.χ. εβδομαδιαία εξαγωγή από το CRM σας)
  • Τα δεδομένα πηγής χρειάζονται καθαρισμό πριν φτάσουν στο Brevo (απαλοιφή διπλοτύπων, μορφοποίηση τηλεφώνων, διαχωρισμός πλήρων ονομάτων σε όνομα/επώνυμο)
  • Θέλετε να εκτελείται σε προγραμματισμένη βάση χωρίς κάποιον να πατάει κουμπιά
  • Το αρχείο είναι μεγαλύτερο από τη ζώνη άνεσης του UI

Τυλίξτε το παραπάνω script σε εργασία cron ή GitHub Action και έχετε αυτοματοποιημένο συγχρονισμό επαφών. Η επόμενη ανάρτηση σε αυτή τη σειρά δείχνει πώς να κάνετε το ίδιο πράγμα κατευθείαν από Google Sheet χρησιμοποιώντας Apps Script, χωρίς server.

Περαιτέρω ανάγνωση

Frequently Asked Questions

Ποιο είναι το API endpoint του Brevo για μαζική εισαγωγή επαφών;
POST https://api.brevo.com/v3/contacts/import. Δέχεται περιεχόμενο CSV (fileBody), URL απομακρυσμένου αρχείου (fileUrl) ή πίνακα JSON (jsonBody). Το endpoint είναι ασύγχρονο, επιστρέφει ένα processId αμέσως και ολοκληρώνει την εισαγωγή στο παρασκήνιο.
Ποιο είναι το μέγιστο μέγεθος αρχείου;
10 MB για inline CSV (fileBody) ή JSON (jsonBody). Για μεγαλύτερα αρχεία, φιλοξενήστε το αρχείο κάπου προσβάσιμο και περάστε το URL του μέσω fileUrl. Αυτή η διαδρομή δεν έχει τεκμηριωμένο όριο μεγέθους.
Πώς εισάγω ένα αρχείο .xlsx;
Το API εισαγωγής του Brevo δέχεται μόνο .csv, .txt ή .json. Μετατρέψτε πρώτα το .xlsx σε .csv. Τα pandas, openpyxl ή LibreOffice headless μπορούν να το κάνουν σε μία γραμμή. Το script σε αυτόν τον οδηγό περιλαμβάνει μετατροπή xlsx προς csv.
Θα αντικαταστήσει υπάρχουσες επαφές;
Από προεπιλογή ναι. Το updateExistingContacts είναι από προεπιλογή true και αντιστοιχίζει βάσει email. Ορίστε το σε false για να παραλείψετε επαφές που υπάρχουν ήδη. Ορίστε το emptyContactsAttributes σε true αν θέλετε τα κενά κελιά CSV να διαγράφουν υπάρχουσες τιμές (αλλιώς τα κενά κελιά αγνοούνται).
Ξεκινήστε δωρεάν με το Brevo