Na této stránce
List s dvanácti registracemi na akci snadno vyexportujete jednou. Při druhém importu se jedna adresa opakuje a další člověk se mezitím v Brevo odhlásil. Opakovatelný import musí rozpoznat obojí.
Skript používá endpointy Brevo pro čtení, vytvoření a aktualizaci kontaktů. Je určen pro malé plánované importy, ne pro hromadné načítání nebo jako nativní konektor Google Sheets. E-maily neposílá. Za důkaz souhlasu a provoz skriptu odpovídáte Vy.
Připravte list a seznam
Vytvořte kartu Contacts přesně s těmito záhlavími. Pro consent použijte zaškrtávací políčka a TRUE nastavte jen se souhlasem pro zamýšlený marketing.
| firstName | lastName | consent | |
|---|---|---|---|
| [email protected] | Jane | Doe | TRUE |
| [email protected] | Alex | Lee | FALSE |
Vytvořte nebo vyberte seznam Brevo a jeho číselné ID zadejte do LIST_ID. Ověřte, že kontaktní atributy FNAME a LNAME v Brevo existují. Mapují se jen tato dvě pole. Záznam o čase a způsobu získání souhlasu uchovejte zvlášť, pokud jej list nedokáže doložit. Buňka TRUE sama o sobě důkazem není.
V Rozšíření → Apps Script → Nastavení projektu → Vlastnosti skriptu přidejte BREVO_API_KEY. Vlastnosti jsou sdílené mezi uživateli skriptu, proto omezte oprávnění k úpravám. Klíč nedávejte do buňky ani do zdrojového kódu. Viz pravidla přístupu Google.
Skript
Nahraďte Code.gs tímto příkladem a před prvním spuštěním nastavte 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); }}Uložte projekt, vyberte syncSheetToBrevo a klikněte na Spustit. Google požádá o přístup k Sheets a externím požadavkům. Ve Spuštěních najdete souhrn {written, skipped}. Odpověď 404 při čtení vede k vytvoření kontaktu, 200 k aktualizaci. Jiné chyby běh zastaví. Úspěšný zápis potvrzuje kontaktní požadavek, nikoli odeslání kampaně.
Skript odstraní duplicitní adresy v jednom běhu a v dalším se znovu zeptá Brevo. Nikdy nenastavuje emailBlacklisted na false ani nemění e-mail. Pokud Brevo ukazuje blokaci nebo odhlášení z daného seznamu, řádek přeskočí i při TRUE v listu. Nepoužívejte jej k vrácení odhlášení. Brevo upozorňuje, že změna adresy blokovaného kontaktu může blokaci zrušit.
Ověřte jeden běh před plánováním
Použijte testovací seznam a kontakty pod Vaší kontrolou. Spusťte skript s jedním řádkem TRUE, jedním FALSE a duplicitní adresou: očekávejte jeden zápis a dva přeskočené řádky. Testovací kontakt v Brevo odhlaste a spusťte znovu: nesmí dojít k zápisu. Pro test chyby nastavte dočasně neplatné LIST_ID, ověřte HTTP chybu ve Spuštěních, vraťte správné ID a opakujte. Netestujte na skutečných zákaznických datech. Kód byl také ověřen místně s napodobenými odpověďmi Brevo, bez živého zápisu.
Pro pravidelné běhy otevřete Apps Script → Spouštěče → Přidat spouštěč, vyberte syncSheetToBrevo a časový interval. Instalovatelné spouštěče běží pod účtem tvůrce; účet musí mít stále přístup k listu. U malého listu začněte denně a kontrolujte chyby. Google omezuje délku běhu a volání URL Fetch; větší zdroje rozdělte na omezené úlohy.
Při 429 skript čte x-sib-ratelimit-reset a opakuje nejvýše dvakrát. Pokud nutné čekání přesáhne 60 sekund, vrátí 429 pro pozdější běh. Jiné chyby běh zastaví; další začne od začátku. Kontakt se může odhlásit mezi čtením a zápisem. Příklad použijte jen tehdy, když je toto krátké okno přijatelné. Přísnější záruky vyžadují soulad událostí, autoritativní zdroj souhlasu a poslední kontrolu před odesláním.
Pro větší listy asynchronní importní endpoint Brevo vrací processId. Potvrzuje přijetí úlohy, ne dokončení. Před prohlášením úspěchu zkontrolujte výsledek. Tato cesta po jednotlivých kontaktech ukáže chybu u dotčeného řádku.
Často Kladené Otázky
Potřebujete nativní konektor Google Sheets?
Ne. Příklad běží v projektu Google Apps Script navázaném na list a volá kontaktní API Brevo. Skript, API klíč a naplánovaný spouštěč spravujete Vy.
Které řádky se odešlou do Brevo?
Pouze řádky s TRUE ve sloupci consent a platnou e-mailovou adresou. Skript v každém běhu odstraní duplicity a před zápisem zkontroluje Brevo.
Přihlásí další běh znovu odhlášený kontakt?
Skript přeskočí kontakty, které Brevo označuje jako emailBlacklisted nebo odhlášené z cílového seznamu. Nikdy neposílá emailBlacklisted: false ani nemění adresu. Před produkcí ověřte i další zdroje souhlasu.
Jak zopakovat neúspěšný běh?
Přečtěte chybu v záznamu spuštění Apps Script, opravte příčinu a spusťte stejnou funkci znovu. Zápisy používají stejnou adresu a ID seznamu, takže dříve úspěšný řádek nevytvoří druhý kontakt.