Πώς να εισάγετε επαφές CSV ή Excel στο Brevo με ένα script (Python, Node.js, cURL)
Μαζική εισαγωγή επαφών από αρχείο CSV ή Excel στο Brevo μέσω του API import-contacts. Περιλαμβάνει έτοιμα προς εκτέλεση scripts σε Python και Node.js, ένα one-liner cURL, διαχείριση σφαλμάτων και συμβουλές για αρχεία μεγαλύτερα από 10 MB.
Αν χρειάστηκε ποτέ να στείλετε μερικές χιλιάδες επαφές από ένα φύλλο εργασίας στο 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/importContent-Type: application/jsonapi-key: YOUR_API_KEY
{ "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 σε κάθε αίτημα. Αντιμετωπίστε το σαν κωδικό πρόσβασης, έχει πλήρη πρόσβαση ανάγνωσης/εγγραφής στον λογαριασμό σας.
Ένα καθαρό μοτίβο: βάλτε το σε μια μεταβλητή περιβάλλοντος ώστε να μην μπει ποτέ στο δέντρο πηγαίου κώδικά σας.
export BREVO_API_KEY="xkeysib-..."Python: διαβάστε ένα CSV, στείλτε το στο Brevo
Αυτό είναι το απλούστερο δυνατό script: διαβάστε ένα τοπικό CSV με την τυπική βιβλιοθήκη, στείλτε το body κατευθείαν στο 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 σας είναι η κεφαλίδα. Τα ονόματα στηλών αντιστοιχίζονται σε attributes του Brevo με κεφαλαία γράμματα, ακριβή αντιστοιχία: EMAIL, FIRSTNAME, LASTNAME, συν οποιοδήποτε προσαρμοσμένο attribute έχετε ορίσει. Το EMAIL είναι υποχρεωτικό.
EMAIL,FIRSTNAME,LASTNAME,COMPANY,CITYΑυτή είναι όλη η ροή. Το script επιστρέφει μέσα σε ένα δευτερόλεπτο, η πραγματική εισαγωγή τρέχει στην πλευρά του server και διαρκεί από λίγα δευτερόλεπτα έως λίγα λεπτά ανάλογα με τον όγκο.
Αρχεία Excel: τρεις βιώσιμες διαδρομές
Το endpoint εισαγωγής του Brevo δεν διαβάζει .xlsx απευθείας, οπότε έχετε τρεις πραγματικές επιλογές ανάλογα με το πού πρέπει να ζει η εργασία:
- Εκτελέστε μια μακροεντολή VBA μέσα στο workbook. Συγχρονισμός με ένα κλικ από το ίδιο το Excel, χωρίς εξωτερικό script. Αυτή είναι η σωστή απάντηση όταν το αρχείο ζει σε επιφάνεια εργασίας και η ομάδα σας θέλει ένα κουμπί στο φύλλο. Πλήρης κώδικας στον οδηγό μακροεντολής VBA Excel σε Brevo.
- Office Scripts + Power Automate. Αν το workbook ζει σε OneDrive/SharePoint και θέλετε προγραμματισμένο συγχρονισμό χωρίς επίβλεψη. Καλύπτεται επίσης στον οδηγό Excel.
- Μετατρέψτε
.xlsxσε CSV από script. Αυτό καλύπτει η υπόλοιπη ενότητα. Καλύτερο αν ήδη τρέχετε Python ή Node σε server και απλά θέλετε να τραβάτε ένα workbook μία φορά την ημέρα.
Για την επιλογή 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: ένα one-liner για εισαγωγές ad-hoc
Όταν απλά θέλετε να δοκιμάσετε το 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-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": [ { "attributes": { "FIRSTNAME": "Jane", "LASTNAME": "Doe", "COMPANY": "Acme", }, }, { "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.