Auf dieser Seite
Ein Sheet mit zwölf Event-Anmeldungen lässt sich einmalig leicht exportieren. Beim zweiten Import steht eine Adresse doppelt darin, während sich ein anderer Kontakt seitdem in Brevo abgemeldet hat. Genau diese beiden Fälle muss ein wiederholbarer Import beachten.
Das folgende Skript verwendet Brevos Endpunkte zum Lesen, Anlegen und Aktualisieren von Kontakten. Es ist für kleine, geplante Importe gedacht, nicht für Massenimporte oder einen nativen Google-Sheets-Connector. Es versendet keine E-Mails. Für Einwilligungsnachweise und Betrieb bist du selbst verantwortlich.
Sheet und Liste vorbereiten
Lege einen Tab Contacts mit genau diesen Spalten an. Verwende Checkboxen für consent und setze TRUE nur, wenn du für die beabsichtigte Marketing-Nutzung eine Einwilligung hast.
| firstName | lastName | consent | |
|---|---|---|---|
| [email protected] | Jane | Doe | TRUE |
| [email protected] | Alex | Lee | FALSE |
Erstelle oder wähle eine Brevo-Liste und trage ihre numerische ID unten als LIST_ID ein. Prüfe, ob die Kontaktattribute FNAME und LNAME in Brevo existieren. Nur diese beiden Felder werden zugeordnet. Bewahre den Nachweis auf, wann und wie die Person zugestimmt hat. Eine TRUE-Zelle allein ist kein Nachweis.
Öffne Erweiterungen → Apps Script → Projekteinstellungen → Skripteigenschaften und füge BREVO_API_KEY hinzu. Skripteigenschaften sind für Nutzer:innen des Skripts gemeinsam zugänglich. Begrenze daher die Bearbeitungsrechte. Schreibe den Schlüssel weder in eine Zelle noch in den Quelltext. Beachte die Zugriffsregeln von Google.
Das Skript
Ersetze Code.gs durch dieses Beispiel und setze vor dem ersten Lauf LIST_ID.
const LIST_ID = 42; // Replace with your existing Brevo list ID.const TAB = 'Contacts';const BASE = 'https://api.brevo.com/v3';
function syncSheetToBrevo() { const key = PropertiesService.getScriptProperties().getProperty('BREVO_API_KEY'); if (!key) throw new Error('Set BREVO_API_KEY in Script Properties first'); const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(TAB); if (!sheet) throw new Error('Contacts tab missing'); const values = sheet.getDataRange().getValues(); const headers = values.shift().map(h => String(h).trim()); const columns = ['email', 'firstName', 'lastName', 'consent'] .map(name => headers.indexOf(name)); if (columns.includes(-1)) throw new Error('Required header missing'); const [emailCol, firstCol, lastCol, consentCol] = columns; const seen = new Set(); const result = { written: 0, skipped: 0 };
for (const row of values) { const email = String(row[emailCol] || '').trim().toLowerCase(); if (row[consentCol] !== true || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) || seen.has(email)) { result.skipped++; continue; } seen.add(email); const path = '/contacts/' + encodeURIComponent(email); const current = brevo_(key, path, 'get'); if (current.code !== 200 && current.code !== 404) { throw new Error(`Contact read failed (${current.code}) for row ${result.written + result.skipped + 2}`); } if (current.code === 200) { const contact = JSON.parse(current.text); if (contact.emailBlacklisted || (contact.listUnsubscribed || []).includes(LIST_ID)) { result.skipped++; continue; } } const attributes = {}; if (row[firstCol] !== '') attributes.FNAME = String(row[firstCol]); if (row[lastCol] !== '') attributes.LNAME = String(row[lastCol]); const body = { attributes, listIds: [LIST_ID] }; const saved = current.code === 404 ? brevo_(key, '/contacts', 'post', { email, ...body }) : brevo_(key, path, 'put', body); if (![201, 204].includes(saved.code)) { throw new Error(`Contact write failed (${saved.code}) for ${email}: ${saved.text.slice(0, 200)}`); } result.written++; } Logger.log(JSON.stringify(result)); return result;}
function brevo_(key, path, method, body) { for (let attempt = 0; attempt < 3; attempt++) { const response = UrlFetchApp.fetch(BASE + path, { method, contentType: 'application/json', headers: { 'api-key': key }, payload: body ? JSON.stringify(body) : undefined, muteHttpExceptions: true, }); const code = response.getResponseCode(); if (code !== 429) return { code, text: response.getContentText() }; if (attempt === 2) return { code, text: response.getContentText() }; const reset = Object.entries(response.getHeaders()).find( ([name]) => name.toLowerCase() === 'x-sib-ratelimit-reset' ); const raw = Number(reset && reset[1]); const seconds = Number.isFinite(raw) && raw > 0 ? raw : 2 ** attempt; const waitSeconds = seconds + 2 ** attempt; if (waitSeconds > 60) return { code, text: response.getContentText() }; Utilities.sleep(waitSeconds * 1000); }}Speichere das Projekt, wähle syncSheetToBrevo und klicke auf Ausführen. Google fragt nach Berechtigungen für Sheets und externe Anfragen. Unter Ausführungen findest du am Ende das Protokoll {written, skipped}. Ein 404 beim Lesen führt zum Anlegen, ein 200 zum Aktualisieren. Andere Lesefehler stoppen den Lauf. Ein erfolgreicher Schreibvorgang bedeutet, dass die Kontaktanfrage abgeschlossen ist; es wurde keine Kampagne versendet.
Das Skript entfernt doppelte E-Mail-Adressen innerhalb eines Laufs und fragt Brevo beim nächsten Lauf erneut ab. Es setzt emailBlacklisted nie auf false und ändert keine E-Mail-Adresse. Meldet Brevo eine globale Sperre oder eine Abmeldung von dieser Liste, überspringt es die Zeile auch bei TRUE im Sheet. Verwende es nicht, um eine Abmeldung rückgängig zu machen. Brevo warnt, dass eine Adressänderung bei gesperrten Kontakten die Sperre aufheben kann.
Einen Lauf prüfen und dann planen
Verwende eine Testliste und eigene Testkontakte. Starte das Skript mit einer TRUE-Zeile, einer FALSE-Zeile und einer doppelten Adresse: Erwartet werden ein Schreibvorgang und zwei übersprungene Zeilen. Melde den Testkontakt in Brevo ab und starte erneut: Für ihn darf kein Schreibvorgang erfolgen. Setze für einen Fehlertest kurz eine ungültige LIST_ID, prüfe den HTTP-Fehler unter Ausführungen, stelle die ID wieder her und starte erneut. Nutze dafür keine echten Kundendaten. Der Code dieses Artikels wurde lokal mit simulierten Brevo-Antworten geprüft; es gab keinen Live-Schreibtest.
Für automatische Läufe wähle Apps Script → Trigger → Trigger hinzufügen, syncSheetToBrevo und einen zeitgesteuerten Intervall. Installierbare Trigger laufen unter dem Konto ihrer Ersteller:innen. Dieses Konto braucht dauerhaft Zugriff auf das Sheet. Beginne bei kleinen Sheets täglich und kontrolliere fehlgeschlagene Ausführungen. Google begrenzt Laufzeit und URL-Fetch-Aufrufe. Teile große Quellen in begrenzte Jobs auf.
Bei 429 liest das Skript x-sib-ratelimit-reset, versucht höchstens zweimal erneut und gibt bei einer nötigen Wartezeit über 60 Sekunden 429 für einen späteren Lauf zurück. Andere Fehler stoppen den Lauf. Beim nächsten manuellen oder geplanten Start beginnt es wieder oben. Zwischen Lesen und Schreiben kann sich ein Kontakt abmelden; dieses Beispiel eignet sich nur, wenn dieses kleine Zeitfenster akzeptabel ist. Für strengere Sperrgarantien brauchst du einen ereignisbasierten Abgleich mit einer maßgeblichen Einwilligungsquelle und einer letzten Prüfung vor dem Versand.
Für große Sheets bietet Brevo einen asynchronen Import-Endpunkt. Dessen processId bestätigt die Annahme eines Jobs, nicht seinen Abschluss. Prüfe das Prozessergebnis, bevor du einen Massenimport als erfolgreich meldest. Bei diesem kleinen Kontaktpfad ist ein Fehler direkt an der betroffenen Zeile sichtbar.
Häufig gestellte Fragen
Brauche ich dafür einen nativen Google-Sheets-Connector?
Nein. Das Beispiel läuft als an das Sheet gebundenes Google Apps Script und ruft die Brevo-Kontakt-API auf. Du pflegst Skript, API-Schlüssel und Zeittrigger selbst.
Welche Zeilen werden an Brevo übertragen?
Nur Zeilen mit TRUE in consent und gültiger E-Mail-Adresse. Das Skript entfernt doppelte Adressen pro Lauf und prüft Brevo vor jedem Schreibvorgang.
Meldet ein erneuter Lauf abgemeldete Kontakte wieder an?
Das Skript überspringt Kontakte, die Brevo als emailBlacklisted oder als von der Zielliste abgemeldet meldet. Es setzt emailBlacklisted nie auf false und ändert keine E-Mail-Adresse. Prüfe zusätzliche Einwilligungsquellen vor dem produktiven Einsatz.
Wie wiederhole ich einen fehlgeschlagenen Lauf?
Lies den Fehler in den Apps-Script-Ausführungen, behebe die Ursache und starte dieselbe Funktion erneut. Die Schreibvorgänge verwenden dieselbe E-Mail-Adresse und Listen-ID; eine zuvor erfolgreiche Zeile erzeugt deshalb keinen zweiten Kontakt.