Sådan importerer du CSV- eller Excel-kontakter til Brevo med et script (Python, Node.js, cURL)
Masseimporter kontakter fra en CSV- eller Excel-fil til Brevo via import-contacts API'et. Indeholder klar-til-brug Python- og Node.js-scripts, en cURL-oneliner, fejlhåndtering og tips til filer større end 10 MB.
Hvis du nogensinde har haft brug for at skubbe et par tusind kontakter fra et regneark ind i Brevo, bliver den manuelle UI-vej hurtigt smertefuld: vælg fil, mapper kolonner, vælg liste, vent, gentag. Et script gør det samme på sekunder, og endnu vigtigere, du kan køre det igen på en tidsplan, efter en database-eksport, eller når dit CRM spytter en frisk CSV ud.
Denne guide gennemgår det API-endpoint, Brevo giver dig præcis til dette, med fuldt fungerende scripts i Python, Node.js og en cURL-oneliner. Alt i denne guide rammer POST /v3/contacts/import.
Endpointet i et overblik
POST https://api.brevo.com/v3/contacts/importContent-Type: application/jsonapi-key: YOUR_API_KEY
{ "listIds": [42], "updateExistingContacts": true, "emailBlacklist": false, "smsBlacklist": false}Svaret kommer hurtigt:
{ "processId": 78 }Det er en 202 Accepted. Brevo har accepteret importen og behandler den i baggrunden. processId er dit sporings-håndtag, hvis du vil polle for færdiggørelse eller opsætte en notifyUrl-webhook.
Et par ting værd at notere, før du skriver kode:
- Body kan være CSV (
fileBody), JSON (jsonBody) eller en ekstern URL (fileUrl). Vælg én. CSV-formen bruger semikolon (;) til at adskille kolonner, ikke kommaer. Det er en almindelig faldgrube. fileBodyogjsonBodyer begrænset til 10 MB. Brevo anbefaler at holde sig omkring 8 MB for at være på den sikre side. Til alt større skal du uploade filen til S3, GCS eller en hvilken som helst HTTPS-host og sendefileUrli stedet.- Brugerdefinerede attributter, der ikke findes i din konto, ignoreres stille. Opret dem i Brevos UI (eller via Attributes-API’et), før du importerer rækker, der bruger dem, ellers forsvinder dataene bare.
updateExistingContactser som standardtrue. Hvis du sætter den tilfalse, springer Brevo kontakter over, hvis e-mail allerede findes. Nyttigt til “kun tilføj nye”-jobs.emptyContactsAttributesstyrer, om tomme celler i din CSV sletter eksisterende værdier. Standard erfalse(tomme celler ignoreres). Sæt tiltrue, hvis CSV’en er sandhedens kilde, og du vil have tomme værdier til at fjerne forældede data.
Få en API-nøgle
Log ind på Brevo → Settings → SMTP & API → API Keys → opret en ny nøgle. Den ser ud som xkeysib-.... Du sender den som HTTP-headeren api-key på hver forespørgsel. Behandl den som et password, den har fuld læse/skrive-adgang til din konto.
Et rent mønster: læg den i en miljøvariabel, så den aldrig lander i dit kildetræ.
export BREVO_API_KEY="xkeysib-..."Python: læs en CSV, send den til Brevo
Dette er det enklest mulige script: læs en lokal CSV med standardbiblioteket, send body direkte til Brevo. Intet tredjepart-SDK nødvendigt udover 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}")Kør det:
python import_csv_to_brevo.py contacts.csvDin CSV’s første række er headeren. Kolonnenavne mappes til Brevo-attributter via store bogstaver, eksakt match: EMAIL, FIRSTNAME, LASTNAME, plus enhver brugerdefineret attribut, du har defineret. EMAIL er obligatorisk.
EMAIL,FIRSTNAME,LASTNAME,COMPANY,CITYDet er hele flowet. Scriptet returnerer inden for et sekund, selve importen kører server-side og tager fra et par sekunder til et par minutter afhængigt af volumen.
Excel-filer: tre brugbare veje
Brevos import-endpoint læser ikke .xlsx direkte, så du har tre reelle muligheder afhængigt af, hvor arbejdet skal leve:
- Kør en VBA-makro inde i projektmappen. Synkronisering med ét klik fra Excel selv, intet eksternt script. Det er det rigtige svar, når filen ligger på et skrivebord, og dit team vil have en knap på arket. Fuld kode i Excel til Brevo VBA-makro-guiden.
- Office Scripts + Power Automate. Hvis projektmappen ligger i OneDrive/SharePoint, og du vil have ubemandet tidsplanlagt synkronisering. Også dækket i Excel-guiden.
- Konverter
.xlsxtil CSV fra et script. Det dækker resten af dette afsnit. Bedst, hvis du allerede kører Python eller Node på en server og bare skal trække en projektmappe ind én gang om dagen.
Til mulighed 3 gør pandas det til en oneliner:
# 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")Hvis du ikke vil have pandas som afhængighed, virker openpyxl alene:
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: samme job, officielt SDK
Brevo udgiver @getbrevo/brevo til Node. Det håndterer auth, retries og den typede request-form:
// 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}`);Eller, hvis du ikke vil have SDK’et, virker plain fetch på samme måde:
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: en oneliner til ad hoc-importer
Når du bare vil teste endpointet eller skubbe en lille fil ind i hånden:
# 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 . slubrer hele filen og JSON-escaper den, det sparer dig for manuelt at escape linjeskift og citationstegn.
Filer større end 10 MB: brug fileUrl
fileBody er begrænset til 10 MB. Hvis dit kontaktudtræk er større, host filen på en URL, Brevo kan hente, og send den:
body = { "fileUrl": "https://files.example.com/contacts/2026-04-30.csv", "listIds": [42], "updateExistingContacts": True,}Alt, der er nåeligt via en offentlig HTTPS-URL, virker, præsignerede S3-URL’er, GCS, din egen statiske host, selv en GitHub raw URL til engangs-importer. Brevo henter filen fra din URL og kører så importen. Accepterede formater: .csv, .txt, .json.
Send JSON i stedet for CSV
Hvis du allerede trækker kontakter fra en database, behøver du ikke gå omvejen via CSV, send dem som JSON direkte:
body = { "jsonBody": [ { "attributes": { "FIRSTNAME": "Jane", "LASTNAME": "Doe", "COMPANY": "Acme", }, }, { "attributes": { "FIRSTNAME": "John", "LASTNAME": "Smith", "COMPANY": "Globex", }, }, ], "listIds": [42],}Samme 10 MB-loft. Samme asynkrone opførsel.
Polling for færdiggørelse
Importen er asynkron, processId er dit håndtag til at spore den. Der er et separat endpoint til at tjekke processtatus:
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}")For lange jobs er det renere mønster notifyUrl: send et HTTPS-endpoint, Brevo POSTer til, når importen er færdig, og spring polling over.
body["notifyUrl"] = "https://your-app.example.com/webhooks/brevo-import"Almindelige fejl og hvordan du retter dem
400 Bad Request uden tydelig årsag. Næsten altid semikolon vs. kommaer i CSV’en. Brevos import forventer ;, ikke ,. Dobbelttjek fileBody efter dit konverteringstrin.
Brugerdefinerede attributværdier forsvinder. Attributten findes ikke i din konto. Opret den under Contacts → Settings → Contact attributes før import, eller brug Attributes-API’et til at oprette den som del af dit script.
401 Unauthorized. Forkert headernavn. Det er api-key (små bogstaver, bindestreg), ikke Authorization eller X-API-Key.
Importen “lykkedes”, men kontakterne dukkede ikke op i listen. Tjek, at listIds indeholder den rigtige liste. Også: hvis updateExistingContacts er false, og kontakterne allerede findes, springer Brevo dem stille over i stedet for at gen-tilføje dem til listen.
Nogle rækker blev importeret, andre ikke. Brevo sender dig en e-mail med en pr-række-fejlrapport, efter importen er færdig (medmindre du sætter disableNotification: true). Rapporten fortæller, hvilke rækker der havde dårlige e-mails, manglende påkrævede felter eller formateringsproblemer.
Hvornår du skal scripte vs. bruge UI’en
UI’en er fin til engangs-importer under tusind rækker. Et script vinder, så snart:
- Du importerer mere end én gang (f.eks. ugentligt udtræk fra dit CRM)
- Kildedata skal renses, før de lander i Brevo (deduplikering, formatering af telefonnumre, opdeling af fulde navne i for/efter)
- Du vil have det til at køre på en tidsplan uden, at nogen klikker på knapper
- Filen er større end UI’ens komfortzone
Pak scriptet ovenfor ind i et cron-job eller en GitHub Action, og du har automatiseret kontaktsynkronisering. Næste indlæg i denne serie viser, hvordan du gør det samme direkte fra et Google Sheet med Apps Script, ingen server nødvendig.