Excel-Kontakte mit einem VBA-Makro zu Brevo schicken (und die Office-Scripts-Alternative)
Ein funktionierendes VBA-Makro, das Kontakte aus einer Excel-Tabelle per Klick an die Brevo-API postet. Dazu, wann du stattdessen Office Scripts + Power Automate nimmst, und die Trade-offs gegenüber einem Google-Apps-Script-Setup.
Du hast Kontakte in Excel und willst sie in Brevo. Die schnelle und schmutzige Antwort ist, die Datei als .csv zu speichern und zu importieren, das ist für einmal okay. Für alles, was du wiederholt machst (wöchentliche Übergabe aus dem Vertrieb, eine Arbeitsmappe, die dein Team täglich aktualisiert, eine Partnerliste, die regelmäßig erneuert wird), willst du einen Button direkt in Excel, der die Synchronisation übernimmt.
Diese Anleitung deckt die zwei Wege ab, die dafür wirklich Sinn ergeben:
- Ein VBA-Makro, eingebettet in der Arbeitsmappe – keine Lizenz, keine Cloud, funktioniert offline, läuft in dem Moment, in dem ein Nutzer auf einen Button klickt. Die richtige Antwort für etwa 80 % aller “Excel-zu-Brevo”-Fälle.
- Office Scripts + Power Automate – TypeScript statt VBA, läuft in der Cloud, unterstützt geplante Trigger. Die richtige Antwort, wenn die Arbeitsmappe in OneDrive/SharePoint liegt und du unbeaufsichtigte Synchronisation willst, aber achte auf die Power-Automate-Lizenzierung.
Wenn du das Google-Sheets-Äquivalent suchst, schau dir den begleitenden Artikel zu Apps Script an. Und wenn du nur einen einmaligen CSV-Import per Skript von deinem Laptop willst, hat die CSV-Import-Anleitung Versionen für Python, Node.js und cURL.
Was das Makro macht
Wenn die Nutzer:innen auf einen “Sync to Brevo”-Button auf dem Sheet klicken:
- Liest jede Zeile aus dem aktiven Arbeitsblatt (Header-Zeile zuerst, ein Kontakt pro Zeile).
- Baut ein JSON-Array, geformt für Brevos
jsonBody-Parameter. - POSTet es mit dem in der Arbeitsmappe gespeicherten API-Schlüssel an
https://api.brevo.com/v3/contacts/import. - Zeigt das Ergebnis in einer Message-Box.
Das war’s. Etwa 120 Zeilen VBA. Unten findest du das vollständige, funktionierende Modul.
Sheet-Layout, das das Makro erwartet
| firstName | lastName | company | city | |
|---|---|---|---|---|
| [email protected] | Jane | Doe | Acme | Berlin |
| [email protected] | John | Smith | Globex | Paris |
email ist Pflicht. Jede andere Spalte wird zu einem Brevo-Kontakt-Attribut, zugeordnet über den Spaltenheader (in Großbuchstaben) zum Attributnamen. Also firstName → FIRSTNAME, company → COMPANY. Benutzerdefinierte Attribute (alles über die Standardmenge hinaus) müssen vorher in deinem Brevo-Konto existieren, lege sie unter Kontakte → Einstellungen → Kontakt-Attribute an.
Schritt 1: VBA-Editor öffnen
In Excel: drück Alt + F11. Der VBA-Editor öffnet sich. Im Projekt-Bereich links rechtsklick auf deine Arbeitsmappe und Einfügen → Modul wählen. Ein leeres Module1 erscheint.
Schritt 2: Vollständiges Makro einfügen
Ersetze den Inhalt von Module1 durch das hier:
' ===========================================================================' Brevo contact sync for Excel' Reads the active sheet's rows and POSTs them to Brevo's import API.' ===========================================================================Option Explicit
Private Const BREVO_API_BASE As String = "https://api.brevo.com/v3"Private Const BREVO_LIST_ID As Long = 42 ' <- your Brevo list IDPrivate Const BATCH_SIZE As Long = 1000
' --- Public entry points (the ones you assign to ribbon buttons) -----------
Public Sub SyncSheetToBrevo() Dim apiKey As String apiKey = GetApiKey() If apiKey = "" Then MsgBox "No API key configured. Run ConfigureApiKey first.", _ vbExclamation, "Brevo Sync" Exit Sub End If
Dim ws As Worksheet Set ws = ActiveSheet
Dim emailCol As Long emailCol = FindEmailColumn(ws) If emailCol = 0 Then MsgBox "Sheet must have an 'email' column in row 1.", _ vbExclamation, "Brevo Sync" Exit Sub End If
Dim lastRow As Long, lastCol As Long lastRow = ws.Cells(ws.Rows.Count, emailCol).End(xlUp).Row lastCol = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column If lastRow < 2 Then MsgBox "No contact rows found.", vbInformation, "Brevo Sync" Exit Sub End If
Dim contacts As Collection Set contacts = New Collection
Dim r As Long, c As Long For r = 2 To lastRow Dim email As String email = LCase(Trim(CStr(ws.Cells(r, emailCol).Value))) If email <> "" And InStr(email, "@") > 0 Then Dim json As String json = "{""email"":""" & EscapeJson(email) & """,""attributes"":{"
Dim attrFirst As Boolean attrFirst = True For c = 1 To lastCol If c <> emailCol Then Dim val As String val = CStr(ws.Cells(r, c).Value) If val <> "" Then If Not attrFirst Then json = json & "," Dim attrName As String attrName = UCase(Trim(CStr(ws.Cells(1, c).Value))) json = json & """" & attrName & """:""" & EscapeJson(val) & """" attrFirst = False End If End If Next c
json = json & "}}" contacts.Add json End If Next r
If contacts.Count = 0 Then MsgBox "No valid contact rows found.", vbInformation, "Brevo Sync" Exit Sub End If
Dim totalSent As Long Dim batchNum As Long Dim okCount As Long, failCount As Long Dim batchStart As Long
For batchStart = 1 To contacts.Count Step BATCH_SIZE batchNum = batchNum + 1 Dim batchEnd As Long batchEnd = batchStart + BATCH_SIZE - 1 If batchEnd > contacts.Count Then batchEnd = contacts.Count
Dim payload As String payload = "{""jsonBody"":[" Dim i As Long For i = batchStart To batchEnd If i > batchStart Then payload = payload & "," payload = payload & contacts(i) Next i payload = payload & "],""listIds"":[" & BREVO_LIST_ID & _ "],""updateExistingContacts"":true,""emptyContactsAttributes"":false}"
Dim ok As Boolean ok = PostToBrevo(apiKey, payload) If ok Then okCount = okCount + 1 totalSent = totalSent + (batchEnd - batchStart + 1) Else failCount = failCount + 1 End If Next batchStart
MsgBox "Sent " & totalSent & " contact(s) in " & batchNum & " batch(es)." _ & vbCrLf & "Successful batches: " & okCount _ & vbCrLf & "Failed batches: " & failCount, _ vbInformation, "Brevo Sync"End Sub
Public Sub ConfigureApiKey() Dim key As String key = InputBox("Paste your Brevo API key (xkeysib-...):", "Brevo API Key") If key = "" Then Exit Sub key = Trim(key) If Left(key, 8) <> "xkeysib-" Then MsgBox "That doesn't look like a Brevo API key (should start with xkeysib-).", _ vbExclamation, "Brevo API Key" Exit Sub End If
On Error Resume Next ThisWorkbook.CustomDocumentProperties("BrevoApiKey").Delete On Error GoTo 0
ThisWorkbook.CustomDocumentProperties.Add _ Name:="BrevoApiKey", _ LinkToContent:=False, _ Type:=msoPropertyTypeString, _ Value:=key
ThisWorkbook.Save MsgBox "API key saved inside the workbook.", vbInformation, "Brevo API Key"End Sub
' --- Private helpers --------------------------------------------------------
Private Function GetApiKey() As String On Error Resume Next GetApiKey = ThisWorkbook.CustomDocumentProperties("BrevoApiKey").Value On Error GoTo 0End Function
Private Function FindEmailColumn(ws As Worksheet) As Long Dim lastCol As Long, c As Long lastCol = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column For c = 1 To lastCol If LCase(Trim(CStr(ws.Cells(1, c).Value))) = "email" Then FindEmailColumn = c Exit Function End If Next c FindEmailColumn = 0End Function
Private Function PostToBrevo(apiKey As String, payload As String) As Boolean Dim http As Object Set http = CreateObject("MSXML2.XMLHTTP") http.Open "POST", BREVO_API_BASE & "/contacts/import", False http.SetRequestHeader "api-key", apiKey http.SetRequestHeader "Content-Type", "application/json" http.SetRequestHeader "Accept", "application/json" http.Send payload PostToBrevo = (http.Status = 202) If Not PostToBrevo Then Debug.Print "Brevo error " & http.Status & ": " & http.responseText End IfEnd Function
Private Function EscapeJson(s As String) As String Dim r As String r = Replace(s, "\", "\\") r = Replace(r, """", "\""") r = Replace(r, vbCrLf, "\n") r = Replace(r, vbLf, "\n") r = Replace(r, vbCr, "\n") r = Replace(r, vbTab, "\t") EscapeJson = rEnd FunctionSchritt 3: Arbeitsmappe als .xlsm speichern
VBA-Makros bleiben nur in makrofähigen Arbeitsmappen erhalten. Speichern unter → Excel-Arbeitsmappe mit Makros (.xlsm) wählen. Das einfache .xlsx-Format entfernt Makros stillschweigend, viele verlieren Code beim ersten Mal genau so.
Schritt 4: API-Schlüssel konfigurieren
Führe ConfigureApiKey einmal aus. Entweder:
- Im VBA-Editor irgendwo in das Sub
ConfigureApiKeyklicken undF5drücken, oder - In Excel Entwicklertools → Makros,
ConfigureApiKeyauswählen, Ausführen.
Füg deinen xkeysib-...-Schlüssel ein. Das Makro speichert ihn als benutzerdefinierte Dokumenteigenschaft direkt in der Arbeitsmappe, er steckt nicht im Quellcode, nicht in der Registry und reist mit der Datei (also Vorsicht: wenn du die .xlsm jemandem mailst, geht der API-Schlüssel mit).
Wenn du den Schlüssel lieber außerhalb der Arbeitsmappe ablegen willst, tausche die Speicherung gegen die Windows-Registry:
' Replace the body of ConfigureApiKey with:SaveSetting "Brevo", "Sync", "ApiKey", key
' And GetApiKey with:GetApiKey = GetSetting("Brevo", "Sync", "ApiKey", "")SaveSetting/GetSetting schreibt unter HKCU\Software\VB and VBA Program Settings\Brevo\Sync, pro Nutzer:in, nicht pro Arbeitsmappe. Nutze das, wenn mehrere Arbeitsmappen einen Schlüssel teilen sollen oder du den Schlüssel nicht in der Datei haben willst.
Schritt 5: Button auf dem Sheet hinzufügen
Das macht es zu einem Ein-Klick-Erlebnis für nicht-technische Nutzer:innen.
Einfügen → Formen → Rechteck, leg eines auf das Sheet, beschrifte es mit “Sync to Brevo.” Rechtsklick auf die Form → Makro zuweisen → SyncSheetToBrevo wählen. Fertig.
Oder, für eine polierter UI, füg über den Office Custom UI Editor einen eigenen Ribbon-Tab hinzu, aber für die meisten internen Tools reicht die Form-als-Button.
Schritt 6: Loslegen
Klick den Button. Das Makro liest die Zeilen, batcht sie, postet jeden Batch an Brevo und zeigt eine Zusammenfassungs-Box. Brevos Import ist asynchron, die Erfolgsmeldung bedeutet also “Brevo hat den Batch angenommen”, die eigentliche Kontaktanlage passiert serverseitig in den nächsten Sekunden. Du bekommst von Brevo eine E-Mail-Zusammenfassung, wenn alles durch ist (außer du setzt disableNotification: true).
Häufige Stolpersteine
Der Button macht nichts und es gibt keinen Fehler. Makros sind deaktiviert. Schau auf den gelben Sicherheitsbalken oben am Sheet, klick Bearbeitung aktivieren. Wenn dein Unternehmen Makros blockiert, schau dir den Trust-Center- bzw. Code-Signing-Pfad weiter unten an.
Compile error: User-defined type not defined. Du bist auf Mac Excel, das hat kein MSXML2.XMLHTTP. Mac-VBA kann keine HTTPS-Requests direkt machen, nutze stattdessen den Office-Scripts-Pfad weiter unten.
400 Bad Request von Brevo ohne offensichtlichen Grund. Fast immer einer von zwei Punkten: (a) ein benutzerdefiniertes Attribut in deinem Sheet existiert noch nicht in Brevo, leg es zuerst an; (b) ein JSON-Escaping-Bug, Anführungszeichen oder Backslashes in Zellwerten, die nicht escaped wurden. Die EscapeJson-Funktion im Code deckt die Standardfälle ab. Wenn deine Daten seltsame Zeichen enthalten, log payload ins Direktfenster (Debug.Print payload) und schau rein.
401 Unauthorized. Falscher Header. Es ist api-key (kleingeschrieben, mit Bindestrich), nicht Authorization. Das Makro nutzt den richtigen, aber wenn du irgendwo anders ein Snippet kopiert hast, prüf nach.
Excel friert bei großen Importen ein. Das Makro läuft synchron auf dem UI-Thread. Bei 50.000+ Zeilen wirst du Excel 10 bis 30 Sekunden hängen sehen, während es das JSON baut und auf Brevo wartet. Akzeptier das, oder wechsle MSXML2.XMLHTTP auf seine asynchrone Variante. Bei der Größenordnung bist du allerdings besser in Power Automate aufgehoben (nächster Abschnitt).
Wenn VBA nicht reicht: Office Scripts + Power Automate
VBA kann keine geplante Cloud-Synchronisation. Wenn du brauchst:
- Die Arbeitsmappe synchronisiert stündlich zu Brevo, ohne dass jemand sie öffnet
- Die Arbeitsmappe in OneDrive/SharePoint, im Web bearbeitet
- Eine IT-Abteilung, die Desktop-Makros verbietet
…dann willst du Office Scripts (Microsofts Cloud-Äquivalent zu Apps Script) plus Power Automate (deren Scheduling- und HTTP-Schicht).
Die Aufteilung: Office Scripts liest das Sheet und gibt die Kontaktdaten zurück. Power Automate nimmt diese Daten und POSTet sie auf einen Trigger an Brevo.
Das Office Script (Excel im Web → Automatisieren → Neues Skript):
function main(workbook: ExcelScript.Workbook): {email: string, attributes: Record<string, string>}[] { const sheet = workbook.getActiveWorksheet(); const range = sheet.getUsedRange(); if (!range) return [];
const values = range.getValues() as string[][]; if (values.length < 2) return [];
const headers = values[0].map(h => String(h).trim()); const emailIdx = headers.findIndex(h => h.toLowerCase() === "email"); if (emailIdx === -1) throw new Error("Sheet must have an 'email' column");
const contacts: {email: string, attributes: Record<string, string>}[] = []; for (let r = 1; r < values.length; r++) { const row = values[r]; const email = String(row[emailIdx] ?? "").trim().toLowerCase(); if (!email || !email.includes("@")) continue;
const attributes: Record<string, string> = {}; for (let c = 0; c < headers.length; c++) { if (c === emailIdx) continue; const v = row[c]; if (v === null || v === "") continue; attributes[headers[c].toUpperCase()] = String(v); } contacts.push({ email, attributes }); } return contacts;}Der Power-Automate-Flow:
- Trigger: Wiederholung (jede Stunde), oder manueller Button, oder “Wenn eine Zeile geändert wird”, wenn du änderungsgesteuerte Synchronisation willst.
- Aktion: Excel Online → Skript ausführen, zeig auf deine Arbeitsmappe und das Skript oben. Speichere den Rückgabewert als
contacts. - Aktion: HTTP (das ist der Premium-Connector, siehe Lizenzhinweis unten).
- Methode:
POST - URI:
https://api.brevo.com/v3/contacts/import - Header:
api-key: xkeysib-...,Content-Type: application/json - Body:
{"jsonBody": @{outputs('Run_script')?['body/result']},"listIds": [42],"updateExistingContacts": true}
- Methode:
- Aktion: Bedingung, wenn Statuscode ≠ 202, schick eine Teams- oder E-Mail-Warnung.
Lizenz-Realitätscheck: Die HTTP-Aktion ist ein Power-Automate-Premium-Connector. Auf Microsoft 365 Business Basic/Standard hast du die Standard-Connectoren, aber kein Premium. Die günstigste Umgehung ist das Power Automate Premium-Add-on (etwa 15 USD pro Nutzer:in pro Monat, Stand Schreiben), oder die HTTP-Aktion in eine kleine Azure Function verlagern, die der Standard-Flow aufrufen kann. Wenn du ohnehin auf E3/E5 mit Premium drin bist, bist du fertig.
Das ist der Hauptgrund, warum die Apps-Script-Story sauberer ist: Apps Scripts UrlFetchApp ist gratis und uneingeschränkt, während das Microsoft-Äquivalent den Netzwerkaufruf hinter eine bezahlte Connector-Stufe legt.
VBA vs. Office Scripts vs. Apps Script: Wann was wählen
| Bedarf | Beste Option |
|---|---|
| Ein-Klick-Button in einer Arbeitsmappe, die dein Team täglich offen hat | VBA-Makro (diese Anleitung, obere Hälfte) |
| Arbeitsmappe in OneDrive/SharePoint, stündliche Auto-Sync | Office Scripts + Power Automate (Premium für HTTP nötig) |
| Nur Mac Excel, kann VBA nicht nutzen | Office Scripts + Power Automate |
| Daten leben in Google Sheets, nicht Excel | Apps Script (gratis, geplante Trigger eingebaut) |
| Einmaliger Import, danach nie wieder | Speichern unter → CSV und das CSV-Import-Skript nutzen |
| Bulk-Import aus einer Datei >10 MB | CSV mit fileUrl, siehe CSV-Anleitung |
Warum das besser ist als Zapier oder andere No-Code-Plattformen
Für einen wiederkehrenden Excel-zu-Brevo-Job berechnen Drittanbieter-Automatisierungstools (Zapier, Make, n8n) pro Task und schieben einen Dritten zwischen deine Daten und Brevo. Der VBA-Ansatz hat null laufende Kosten, keinen Drittanbieter-Datenfluss und lebt in der Datei selbst, wenn die Arbeitsmappe wandert, wandert die Integration mit. Office Scripts + Power Automate ist ähnlich, mit Microsoft als Dritten (schon in deinem Stack, wenn du auf M365 bist).
Der ganze Sinn von Brevos POST /v3/contacts/import ist, dass du keine Klebe-Plattform brauchst, deine Tools wissen schon, wie man HTTP-Requests macht.