Na tejto stránke
Hárok s dvanástimi prihláškami na podujatie ľahko vyexportujete raz. Pri druhom importe je jedna adresa dvakrát a ďalší človek sa medzičasom odhlásil v Brevo. Opakovateľný import musí zachytiť obe situácie.
Skript používa endpointy Brevo na čítanie, vytváranie a aktualizáciu kontaktov. Slúži na malé plánované importy, nie na hromadné nahrávanie alebo ako natívny konektor Google Sheets. E-maily neodosiela. Za dôkaz súhlasu a prevádzku skriptu zodpovedáte Vy.
Pripravte hárok a zoznam
Vytvorte kartu Contacts presne s týmito hlavičkami. Pre consent použite zaškrtávacie políčka a TRUE nastavte len so súhlasom na zamýšľaný marketing.
| firstName | lastName | consent | |
|---|---|---|---|
| [email protected] | Jane | Doe | TRUE |
| [email protected] | Alex | Lee | FALSE |
Vytvorte alebo vyberte zoznam Brevo a jeho číselné ID zadajte do LIST_ID. Overte, či atribúty FNAME a LNAME v Brevo existujú. Mapujú sa iba tieto dve polia. Doklad o čase a spôsobe získania súhlasu uchovajte inde, ak ho hárok nevie preukázať. Samotná bunka TRUE dôkazom nie je.
V Rozšírenia → Apps Script → Nastavenia projektu → Vlastnosti skriptu pridajte BREVO_API_KEY. Vlastnosti zdieľajú používatelia skriptu, preto obmedzte právo upravovať projekt. Kľúč nedávajte do bunky ani do zdrojového kódu. Pozrite pravidlá prístupu Google.
Skript
Nahraďte Code.gs týmto príkladom a pred prvým spustení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, vyberte syncSheetToBrevo a kliknite na Spustiť. Google požiada o prístup k Sheets a externým požiadavkám. V Spusteniach nájdete súhrn {written, skipped}. Odpoveď 404 pri čítaní vedie k vytvoreniu, 200 k aktualizácii. Iné chyby zastavia beh. Úspešný zápis potvrdzuje kontaktnú požiadavku, nie odoslanú kampaň.
Skript odstráni duplicitné adresy počas jedného behu a pri ďalšom Brevo znovu preverí. Nikdy nenastavuje emailBlacklisted na false ani nemení e-mail. Ak Brevo ukáže blokovanie alebo odhlásenie z tejto listiny, riadok preskočí aj pri TRUE v hárku. Nepoužívajte ho na zrušenie odhlásenia. Brevo upozorňuje, že zmena adresy blokovaného kontaktu môže blokovanie odstrániť.
Skontrolujte beh pred plánovaním
Použite skúšobný zoznam a kontakty pod Vašou kontrolou. Spustite jeden riadok TRUE, jeden FALSE a duplicitnú adresu: čakajte jeden zápis a dva preskočené riadky. Skúšobný kontakt v Brevo odhláste a opakujte: nesmie sa zapísať. Pre skúšku chyby dočasne nastavte neplatné LIST_ID, skontrolujte HTTP chybu v Spusteniach, vráťte správne ID a skúste znova. Nepoužívajte skutočné zákaznícke dáta. Kód bol miestne overený aj s napodobnenými odpoveďami Brevo, bez živého zápisu.
Pre pravidelné spustenia vyberte Apps Script → Spúšťače → Pridať spúšťač, funkciu syncSheetToBrevo a časový interval. Inštalovateľné spúšťače bežia pod účtom svojho tvorcu, ktorý musí mať prístup k hárku. Pri malom hárku začnite denne a sledujte chyby. Google obmedzuje čas a volania URL Fetch; veľké zdroje rozdeľte na ohraničené úlohy.
Pri 429 skript číta x-sib-ratelimit-reset a zopakuje požiadavku najviac dvakrát. Ak nutné čakanie presiahne 60 sekúnd, vráti 429 pre neskorší beh. Iné chyby beh zastavia; ďalší začne od začiatku. Kontakt sa môže odhlásiť medzi čítaním a zápisom. Tento príklad použite len vtedy, ak je krátke okno prijateľné. Prísnejšie záruky vyžadujú zosúladenie udalostí, autoritatívny zdroj súhlasu a poslednú kontrolu pred odoslaním.
Pre väčšie hárky asynchrónny importný endpoint Brevo vracia processId. Potvrdzuje prijatie úlohy, nie dokončenie. Pred vyhlásením úspechu overte výsledok. Táto cesta po jednotlivých kontaktoch ukáže chybu pri dotknutom riadku.
Často Kladené Otázky
Potrebujete natívny konektor Google Sheets?
Nie. Príklad beží v projekte Google Apps Script prepojenom s hárkom a volá kontaktné API Brevo. Skript, API kľúč aj plánovaný spúšťač spravujete Vy.
Ktoré riadky sa odošlú do Brevo?
Iba riadky s TRUE v consent a platnou e-mailovou adresou. Skript odstráni duplicity pri každom spustení a pred zápisom preverí Brevo.
Pri ďalšom spustení sa odhlásený kontakt znovu prihlási?
Skript preskočí kontakty, ktoré Brevo označí ako emailBlacklisted alebo odhlásené z cieľového zoznamu. Nikdy neposiela emailBlacklisted: false ani nemení adresu. Pred produkciou preverujte aj ďalšie zdroje súhlasu.
Ako zopakovať neúspešný beh?
Prečítajte si chybu v spusteniach Apps Script, opravte príčinu a spustite rovnakú funkciu znova. Zápisy používajú tú istú adresu a ID zoznamu, takže úspešný riadok nevytvorí druhý kontakt.