Pe această pagină
O foaie cu douăsprezece înscrieri la un eveniment este ușor de exportat o dată. La al doilea import, o adresă apare de două ori, iar altcineva s-a dezabonat între timp în Brevo. Un import repetabil trebuie să observe ambele situații.
Scriptul folosește endpointurile Brevo pentru citirea, crearea și actualizarea contactelor. Este pentru importuri mici și programate, nu pentru încărcări masive sau ca un conector Google Sheets nativ. Nu trimite e-mailuri. Tu răspunzi de dovada consimțământului și de operarea scriptului.
Pregătește foaia și lista
Creează o filă Contacts cu exact aceste antete. Folosește casete de selectare pentru consent și pune TRUE numai când ai acord pentru utilizarea de marketing propusă.
| firstName | lastName | consent | |
|---|---|---|---|
| [email protected] | Jane | Doe | TRUE |
| [email protected] | Alex | Lee | FALSE |
Creează sau alege o listă Brevo și copiază ID-ul numeric în LIST_ID. Verifică dacă atributele FNAME și LNAME există în Brevo. Doar ele sunt mapate. Păstrează dovada datei și modului de obținere a consimțământului în altă parte dacă foaia nu o poate furniza. O celulă TRUE nu este dovadă.
În Extensii → Apps Script → Setările proiectului → Proprietățile scriptului, adaugă BREVO_API_KEY. Proprietățile sunt comune utilizatorilor scriptului; limitează cine poate edita proiectul. Nu pune cheia într-o celulă sau în cod. Citește regulile de acces Google.
Scriptul
Înlocuiește Code.gs cu acest exemplu și setează LIST_ID înainte de prima rulare.
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); }}Salvează, alege syncSheetToBrevo și apasă Rulează. Google va cere acces la Sheets și la cereri externe. În Execuții verifică jurnalul final {written, skipped}. O citire 404 duce la creare, una 200 la actualizare. Alte erori opresc rularea. O scriere reușită confirmă cererea de contact, nu trimiterea unei campanii.
Scriptul elimină adresele duplicate într-o rulare și verifică Brevo din nou la următoarea. Nu setează niciodată emailBlacklisted la false și nu schimbă adresa. Dacă Brevo raportează blocare globală sau dezabonare de la această listă, rândul este omis chiar dacă foaia spune TRUE. Nu folosi scriptul pentru anularea dezabonării. Brevo avertizează că schimbarea adresei unui contact blocat îi poate elimina blocarea.
Verifică o rulare înainte de programare
Folosește o listă de test și contacte controlate de tine. Rulează cu un rând TRUE, unul FALSE și o adresă duplicată: așteaptă o scriere și două rânduri omise. Dezabonează contactul test în Brevo și rulează iar: nu ar trebui scris. Pentru un test de eroare, setează temporar un LIST_ID invalid, verifică eroarea HTTP în Execuții, restabilește ID-ul și repetă. Nu testa pe date reale ale clienților. Codul a fost verificat local și cu răspunsuri Brevo simulate, fără scrieri live.
Pentru programare, deschide Apps Script → Declanșatoare → Adaugă declanșator, alege syncSheetToBrevo și un interval de timp. Declanșatoarele instalabile rulează sub contul creatorului, care trebuie să păstreze accesul la foaie. Începe cu o rulare zilnică și urmărește eșecurile. Google limitează timpul de execuție și apelurile URL Fetch; împarte sursele mari în sarcini limitate.
La 429, scriptul citește x-sib-ratelimit-reset, reîncearcă cel mult de două ori și returnează 429 pentru o rulare ulterioară dacă așteptarea necesară depășește 60 de secunde. Alte erori opresc rularea; următoarea pornește de la început. Un contact se poate dezabona între citire și scriere. Exemplul este potrivit doar dacă accepți acest interval scurt. Pentru garanții mai stricte, folosește reconciliere pe evenimente, o sursă de încredere a consimțământului și o ultimă verificare înainte de trimitere.
Pentru foi mai mari, endpointul de import asincron Brevo returnează processId. Acesta confirmă acceptarea sarcinii, nu încheierea ei. Verifică rezultatul înainte de a declara reușit un import masiv. Calea pe contact de aici arată eroarea la rândul care a cauzat-o.
Întrebări Frecvente
Este necesar un conector Google Sheets nativ?
Nu. Exemplul rulează într-un proiect Google Apps Script legat de foaie și apelează API-ul de contacte Brevo. Tu întreții scriptul, cheia API și declanșatorul programat.
Ce rânduri ajung în Brevo?
Doar cele cu TRUE în consent și cu adresă de e-mail validă. Scriptul elimină duplicatele la fiecare rulare și verifică Brevo înainte de scriere.
O nouă rulare reabonează pe cineva dezabonat?
Scriptul omite contactele pe care Brevo le marchează emailBlacklisted sau dezabonate de la lista țintă. Nu trimite emailBlacklisted: false și nu schimbă adresa de e-mail. Verifică și alte surse de consimțământ înainte de producție.
Cum reiau o rulare eșuată?
Citește eroarea din execuțiile Apps Script, corectează cauza și rulează din nou aceeași funcție. Scrierile folosesc aceeași adresă și același ID de listă, așa că un rând deja reușit nu creează încă un contact.