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.

Featured image for article: Sådan importerer du CSV- eller Excel-kontakter til Brevo med et script (Python, Node.js, cURL)

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/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
}

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.
  • fileBody og jsonBody er 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 sende fileUrl i 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.
  • updateExistingContacts er som standard true. Hvis du sætter den til false, springer Brevo kontakter over, hvis e-mail allerede findes. Nyttigt til “kun tilføj nye”-jobs.
  • emptyContactsAttributes styrer, om tomme celler i din CSV sletter eksisterende værdier. Standard er false (tomme celler ignoreres). Sæt til true, 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æ.

Terminal window
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_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}")

Kør det:

Terminal window
python import_csv_to_brevo.py contacts.csv

Din 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,CITY
[email protected],Jane,Doe,Acme,Berlin
[email protected],John,Smith,Globex,Paris

Det 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:

  1. 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.
  2. Office Scripts + Power Automate. Hvis projektmappen ligger i OneDrive/SharePoint, og du vil have ubemandet tidsplanlagt synkronisering. Også dækket i Excel-guiden.
  3. Konverter .xlsx til 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 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")

Hvis du ikke vil have pandas som afhængighed, virker openpyxl alene:

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: samme job, officielt SDK

Brevo udgiver @getbrevo/brevo til Node. Det håndterer auth, retries og den typede request-form:

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}`);

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:

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 . 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": [
{
"email": "[email protected]",
"attributes": {
"FIRSTNAME": "Jane",
"LASTNAME": "Doe",
"COMPANY": "Acme",
},
},
{
"email": "[email protected]",
"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.

Yderligere læsning

Frequently Asked Questions

Hvad er Brevos API-endpoint til masseimport af kontakter?
POST https://api.brevo.com/v3/contacts/import. Det accepterer CSV-indhold (fileBody), en URL til en ekstern fil (fileUrl) eller et JSON-array (jsonBody). Endpointet er asynkront, det returnerer et processId med det samme og afslutter importen i baggrunden.
Hvad er den maksimale filstørrelse?
10 MB for inline CSV (fileBody) eller JSON (jsonBody). Til større filer kan du hoste filen et tilgængeligt sted og sende dens URL via fileUrl. Den vej har ingen dokumenteret størrelsesgrænse.
Hvordan importerer jeg en .xlsx-fil?
Brevos import-API tager kun .csv, .txt eller .json. Konvertér først .xlsx til .csv. pandas, openpyxl eller LibreOffice headless kan klare det på én linje. Scriptet i denne guide indeholder en xlsx til csv-konvertering.
Vil det overskrive eksisterende kontakter?
Som standard ja. updateExistingContacts er som standard true og matcher på e-mail. Sæt den til false for at springe kontakter over, der allerede findes. Sæt emptyContactsAttributes til true, hvis du vil have tomme CSV-celler til at slette eksisterende værdier (ellers ignoreres tomme celler).
Start gratis med Brevo