Kako zgraditi Google Apps Script za uvoz stikov iz lista v Brevo
Pošiljajte stike iz Google Sheet v Brevo samodejno. Popoln Apps Script s shranjevanjem API ključa, izvajanjem ob urejanju, časovno temelječimi sprožilci, menijem po meri in obravnavanjem napak. Brez strežnika.
Če vaša ekipa že živi v Google Sheet (prodajni leadi, prijave na dogodke, partnerski seznam stikov), pridobivanje teh podatkov v Brevo ne potrebuje izvažanja CSV in ročnega ponovnega uvažanja. Google Apps Script vam omogoča, da list povežete neposredno z Brevo API. Skripta teče znotraj Googlove infrastrukture, zato ni ničesar gostiti, namestiti ali pestovati.
Ta vodnik vodi skozi delujočo skripto: postavko menija po meri Sync to Brevo v vašem listu, samodejni urni sprožilec, varno shranjevanje API ključa, obravnavo paketov in malo strukturiranega beleženja, da boste lahko povedali, kaj se je zgodilo.
Kaj potrebujete
- Google Sheet s stiki (ena vrstica na stik, najprej vrstica glave)
- Račun Brevo in API ključ (Settings → SMTP & API → API Keys)
- Številčni ID seznama Brevo, na katerega želite dodati stike
To je vse. Brez npm, brez Pythona, brez strežnika.
Postavitev lista
Skripta v tem vodniku pričakuje vrstico glave, ki ji sledi en stik na vrstico. Stolpci se mapirajo prek imena glave na atribute Brevo:
| firstName | lastName | company | city | |
|---|---|---|---|---|
| [email protected] | Jane | Doe | Acme | Berlin |
| [email protected] | John | Smith | Globex | Paris |
email je obvezen in se ujema neodvisno od velikih in malih črk. Vse ostalo se pošlje v Brevo kot atribut stika. Lastni atributi (karkoli onstran standardnih, kot sta FIRSTNAME, LASTNAME) morajo najprej obstajati v vašem računu Brevo. Definirajte jih pod Contacts → Settings → Contact attributes, ali prek Brevo API.
Odprite urejevalnik Apps Script
V vašem listu: Extensions → Apps Script. Odpre se nov zavihek s praznim Code.gs. Nadomestite vsebino s spodnjo skripto.
Celotna skripta
const BREVO_API_BASE = 'https://api.brevo.com/v3';const BREVO_LIST_ID = 42; // <- the Brevo list to import contacts intoconst SHEET_NAME = 'Contacts'; // <- name of the sheet tab to readconst BATCH_SIZE = 1000; // contacts per import call
/** * Adds a "Brevo" menu to the Sheet so users can run the sync from the UI. * Triggered automatically when the Sheet opens. */function onOpen() { SpreadsheetApp.getUi() .createMenu('Brevo') .addItem('Sync sheet to Brevo', 'syncSheetToBrevo') .addItem('Configure API key', 'configureApiKey') .addToUi();}
/** * Reads every contact row from the sheet, batches them, and sends each batch * to Brevo's import endpoint. Returns a summary string for logging. */function syncSheetToBrevo() { const apiKey = getApiKey_(); if (!apiKey) { SpreadsheetApp.getUi().alert( 'No Brevo API key configured. Run "Configure API key" first.' ); return; }
const contacts = readContactsFromSheet_(); if (contacts.length === 0) { SpreadsheetApp.getUi().alert('No contacts found in the sheet.'); return; }
const batches = chunk_(contacts, BATCH_SIZE); const results = [];
for (let i = 0; i < batches.length; i++) { const result = importBatchToBrevo_(apiKey, batches[i]); results.push(result); Logger.log( `Batch ${i + 1}/${batches.length}: ${result.ok ? 'ok' : 'FAILED'} ` + `(processId=${result.processId || '-'}, status=${result.status})` ); }
const summary = `Sent ${contacts.length} contacts in ${batches.length} batch(es). ` + `Successful: ${results.filter(r => r.ok).length}/${results.length}.`; Logger.log(summary); SpreadsheetApp.getActiveSpreadsheet().toast(summary, 'Brevo sync', 5); return summary;}
/** * Reads the active spreadsheet's "Contacts" tab into an array of * { email, attributes } objects shaped for Brevo's jsonBody. */function readContactsFromSheet_() { const sheet = SpreadsheetApp .getActiveSpreadsheet() .getSheetByName(SHEET_NAME); if (!sheet) { throw new Error(`Sheet tab "${SHEET_NAME}" not found`); }
const range = sheet.getDataRange().getValues(); if (range.length < 2) return [];
const headers = range[0].map(String); const emailColumn = headers.findIndex(h => h.toLowerCase() === 'email'); if (emailColumn === -1) { throw new Error('Sheet must have an "email" column'); }
const contacts = []; for (let i = 1; i < range.length; i++) { const row = range[i]; const email = String(row[emailColumn] || '').trim().toLowerCase(); if (!email || !email.includes('@')) continue; // skip invalid
const attributes = {}; for (let c = 0; c < headers.length; c++) { if (c === emailColumn) continue; const value = row[c]; if (value === '' || value === null) continue; // Brevo convention: ATTRIBUTES ARE UPPERCASE attributes[headers[c].toUpperCase()] = value; } contacts.push({ email, attributes }); } return contacts;}
/** * POSTs a batch of contacts to Brevo's import endpoint. * Returns { ok, status, processId, error }. */function importBatchToBrevo_(apiKey, contacts) { const payload = { jsonBody: contacts, listIds: [BREVO_LIST_ID], updateExistingContacts: true, emptyContactsAttributes: false, };
const response = UrlFetchApp.fetch(`${BREVO_API_BASE}/contacts/import`, { method: 'post', contentType: 'application/json', headers: { 'api-key': apiKey, 'accept': 'application/json', }, payload: JSON.stringify(payload), muteHttpExceptions: true, // we'll inspect status ourselves });
const status = response.getResponseCode(); const body = response.getContentText();
if (status === 202) { const json = JSON.parse(body); return { ok: true, status, processId: json.processId }; } return { ok: false, status, error: body };}
/** * Stores the Brevo API key in script properties — encrypted at rest by Google * and not visible in the source code or to viewers of the sheet. */function configureApiKey() { const ui = SpreadsheetApp.getUi(); const response = ui.prompt( 'Brevo API key', 'Paste your Brevo API key (xkeysib-...). It will be stored in Script Properties.', ui.ButtonSet.OK_CANCEL ); if (response.getSelectedButton() !== ui.Button.OK) return; const key = response.getResponseText().trim(); if (!key.startsWith('xkeysib-')) { ui.alert('That doesn\'t look like a Brevo API key (should start with xkeysib-).'); return; } PropertiesService.getScriptProperties().setProperty('BREVO_API_KEY', key); ui.alert('API key saved.');}
function getApiKey_() { return PropertiesService.getScriptProperties().getProperty('BREVO_API_KEY');}
function chunk_(arr, size) { const out = []; for (let i = 0; i < arr.length; i += size) out.push(arr.slice(i, i + size)); return out;}To je celotna stvar. Shranite jo (⌘S / Ctrl+S), poimenujte projekt nekaj v slogu „Brevo sync” in se vrnite v svoj list.
Prvo izvajanje
Ponovno naložite list. Na vrhu se pojavi nov meni Brevo.
- Kliknite Brevo → Configure API key, prilepite svoj
xkeysib-...ključ, kliknite OK. - Kliknite Brevo → Sync sheet to Brevo. Google bo prvič vprašal za dovoljenja:
- „View and manage your spreadsheets”, potrebno za branje vrstic
- „Connect to an external service”, potrebno za klic api.brevo.com
- Odobrite. Skripta teče. Zelena obvestila v spodnjem desnem kotu vam povedo, koliko stikov je odšlo.
Če ne uspe, kliknite Extensions → Apps Script → View → Logs in si oglejte statusno kodo na paket od Brevo. Najpogostejša napaka je 400 zaradi manjkajočega lastnega atributa, glejte spodaj odsek o odpravljanju težav.
Zaženite po urniku
V urejevalniku Apps Script: Triggers (ikona ure v levi stranski vrstici) → Add Trigger.
- Choose function:
syncSheetToBrevo - Event source: Time-driven
- Type: Hour timer (ali Day timer za sinhronizacijo enkrat na dan)
- Interval: vsako uro (ali kar vam ustreza)
Shranite. Google bo funkcijo izvajal v tej kadenci za vedno, brez strežnika, brez crona, brez vzdrževanja.
Lahko uporabite tudi From spreadsheet → On edit, če želite, da vsaka sprememba celice sproži sinhronizacijo. Bodite previdni s tem. Tudi kozmetične urejevanje bodo sprožile sprožilec, kar lahko hitro zadene dnevno kvoto Apps Script na zaposlenih listih. Urni časovni sprožilec je skoraj vedno pravi odgovor.
Kvote Apps Script, ki jih morate poznati
Brezplačna raven Apps Script ima omejitve, ki jih je vredno spoštovati:
| Omejitev | Vrednost (brezplačna raven) |
|---|---|
| Skupni čas izvajanja na dan | 90 minut |
| Čas enega izvajanja | 6 minut |
Klici UrlFetchApp na dan | 20.000 |
Velikost tovora UrlFetchApp | 50 MB |
Velikost glav UrlFetchApp | 8 KB |
| Sprožilci na uporabnika na skripto | 20 |
Za tipično sinhronizacijo stikov (nekaj tisoč stikov, urna) niste blizu nobeni od teh. Edina, ki jo morate spremljati, je 6-minutno enojno izvajanje. Če kdaj sinhronizirate sto tisoče stikov v enem zamahu, jih razdelite na manjše dele (zgornja skripta to že počne prek BATCH_SIZE).
Obravnavanje uvoza asinhrono
Brevo uvozni endpoint je asinhron: takoj dobite processId in sam uvoz teče na strani strežnika. Za večino sinhronizacij listov je to v redu, izstrelite in pozabite, Brevo bo poslal e-pošto s povzetkom, ko vsak paket konča.
Če želite blokirati, dokler uvoz dejansko ni končan, preverjajte endpoint statusa procesa:
function waitForImport_(apiKey, processId, timeoutMs = 5 * 60 * 1000) { const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { const resp = UrlFetchApp.fetch(`${BREVO_API_BASE}/processes/${processId}`, { headers: { 'api-key': apiKey }, muteHttpExceptions: true, }); if (resp.getResponseCode() === 200) { const status = JSON.parse(resp.getContentText()).status; if (status === 'completed' || status === 'failed') return status; } Utilities.sleep(5000); // 5s between checks } return 'timeout';}Utilities.sleep je ekvivalent Apps Script za blokirno čakanje. Ne spite predolgo. Imate skupno 6 minut na izvajanje.
Dodajanje notify webhooka
Čistejši vzorec kot preverjanje: namestite svoj Apps Script kot Web App in posredujte njegov URL kot notifyUrl. Brevo bo nanj poslal POST, ko se uvoz konča.
// Add to Code.gsfunction doPost(e) { const payload = JSON.parse(e.postData.contents); Logger.log(`Brevo import ${payload.processId} finished: ${payload.status}`); // optionally: write the result back to a "Sync log" tab in the sheet return ContentService.createTextOutput('ok');}Namestite: Deploy → New deployment → Web app, nastavite „Who has access” na Anyone, kopirajte nastali URL in ga posredujte kot notifyUrl v vašem uvoznem tovoru:
payload.notifyUrl = 'https://script.google.com/macros/s/AKfy.../exec';Zdaj Brevo pošlje rezultat nazaj na lasten skript vašega lista, kar zaključi zanko brez zunanje infrastrukture.
Odpravljanje težav
400 Bad Request z error: "Attribute X not found". Stolpec v vašem listu se mapira na atribut, ki ga Brevo ne pozna. Bodisi preimenujte stolpec lista, da se ujema z obstoječim atributom, ali ustvarite atribut v Brevo (Contacts → Settings → Contact attributes).
401 Unauthorized. API ključ je napačen ali pretečen. Ponovno zaženite Configure API key, prilepite svež ključ z nadzorne plošče Brevo.
429 Too Many Requests. Zadevate Brevo rate limit. Uvozni endpoint dovoljuje približno 30 klicev na minuto. Če agresivno paketirate, dodajte Utilities.sleep(2000) med pakete v zanki.
Skripta tiho ne teče po urniku. Preverite Triggers v urejevalniku Apps Script. Če sprožilec večkrat ne uspe, ga Google onemogoči. Kliknite na sprožilec, da vidite razlog napake, običajno gre za težavo z dovoljenji, ki jo lahko ponovno avtorizirate.
Meni Brevo se ni pojavil. onOpen teče samo, ko (ponovno) odprete list iz nič. Ponovno naložite zavihek brskalnika.
Pojavno okno z dovoljenji se vrača. Verjetno ste uredili obsege skripte (dodali novo Googlovo storitev). Apps Script ponovno pozove za avtorizacijo vsakič, ko se potrebna dovoljenja spremenijo. Zaženite katerokoli funkcijo enkrat iz urejevalnika, da sprožite poziv, in odobrite.
Zakaj to premaga Zapier in prijatelje
Apps Script je brezplačen, živi znotraj Googlove infrastrukture in ima neposreden dostop do podatkov lista. Brez sprožanja dogodkov po vrstici, brez cen na nalogo, brez rate limitov razen Googlove kvote (ki je za to vrsto opravila radodarna). Druga stran: zavezujete se k pisanju in vzdrževanju majhnega kosa kode. Za sinhronizacijo stikov je to približno 100 vrstic in v bistvu nič tekočega vzdrževanja.
Združite to z dnevnim sprožilcem in listom, ki ga vaša prodajna ekipa že posodablja, in imate cevovod stikov v Brevo z nič ponavljajočega se dela.