Ako vytvoriť Google Apps Script na import kontaktov z hárka do Brevo

Posielajte kontakty z Google Sheet do Brevo automaticky. Kompletný Apps Script s ukladaním API kľúča, spustením pri úprave, časovými triggermi, vlastným menu a spracovaním chýb. Bez servera.

Featured image for article: Ako vytvoriť Google Apps Script na import kontaktov z hárka do Brevo

Ak váš tím už žije v Google Sheet (predajné leady, registrácie na podujatia, partnerský zoznam kontaktov), získanie tých údajov do Brevo nemusí zahŕňať exportovanie CSV a opätovné importovanie ručne. Google Apps Script vám umožňuje pripojiť hárok priamo na API Brevo. Skript beží vo vnútri infraštruktúry Googlu, takže nie je čo hostovať, nasadzovať ani opatrovať.

Táto príručka prechádza funkčným skriptom: vlastnú položku menu Sync to Brevo vo vašom hárku, automatický hodinový trigger, bezpečné ukladanie API kľúča, spracovanie dávok a malý kus štruktúrovaného logovania, aby ste mohli povedať, čo sa stalo.

Čo potrebujete

  • Google Sheet s kontaktmi (jeden riadok na kontakt, najprv riadok hlavičky)
  • Účet Brevo a API kľúč (Settings → SMTP & API → API Keys)
  • Číselné ID zoznamu Brevo, do ktorého chcete kontakty pridať

To je všetko. Žiadne npm, žiadny Python, žiadny server.

Layout hárka

Skript v tejto príručke očakáva riadok hlavičky nasledovaný jedným kontaktom na riadok. Stĺpce sa mapujú podľa názvu hlavičky na atribúty Brevo:

emailfirstNamelastNamecompanycity
[email protected]JaneDoeAcmeBerlin
[email protected]JohnSmithGlobexParis

email je povinný a porovnáva sa nezávisle na veľkosti písmen. Všetko ostatné sa odošle do Brevo ako atribút kontaktu. Vlastné atribúty (čokoľvek nad rámec štandardných ako FIRSTNAME, LASTNAME) musia najprv existovať vo vašom účte Brevo. Definujte ich pod Contacts → Settings → Contact attributes, alebo cez API Brevo.

Otvorte editor Apps Script

Vo vašom hárku: Extensions → Apps Script. Otvorí sa nová karta s prázdnym Code.gs. Nahraďte obsah skriptom nižšie.

Celý skript

Code.gs
const BREVO_API_BASE = 'https://api.brevo.com/v3';
const BREVO_LIST_ID = 42; // <- the Brevo list to import contacts into
const SHEET_NAME = 'Contacts'; // <- name of the sheet tab to read
const 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 celá vec. Uložte to (⌘S / Ctrl+S), pomenujte projekt niečo ako „Brevo sync” a vráťte sa do svojho hárka.

Prvé spustenie

Znovu načítajte hárok. V hornej časti sa objaví nové menu Brevo.

  1. Kliknite na Brevo → Configure API key, vložte svoj kľúč xkeysib-..., kliknite na OK.
  2. Kliknite na Brevo → Sync sheet to Brevo. Google si pri prvom raze vyžiada povolenia:
    • „View and manage your spreadsheets”, potrebné na čítanie riadkov
    • „Connect to an external service”, potrebné na volanie api.brevo.com
  3. Schváľte. Skript beží. Zelený toast vpravo dole vám povie, koľko kontaktov odišlo.

Ak zlyhá, kliknite na Extensions → Apps Script → View → Logs a pozrite si stavový kód na dávku z Brevo. Najčastejším zlyhaním je 400 z dôvodu chýbajúceho vlastného atribútu, pozrite si nižšie sekciu o riešení problémov.

Spustite to podľa rozvrhu

V editore Apps Script: Triggers (ikona hodín v ľavom bočnom paneli) → Add Trigger.

  • Choose function: syncSheetToBrevo
  • Event source: Time-driven
  • Type: Hour timer (alebo Day timer pre jednorazovú dennú synchronizáciu)
  • Interval: každú hodinu (alebo čo vám vyhovuje)

Uložte. Google bude funkciu spúšťať v takej kadencii navždy, bez servera, bez cron, bez údržby.

Môžete tiež použiť From spreadsheet → On edit, ak chcete, aby každá zmena bunky spustila synchronizáciu. Buďte s tým opatrní. Aj kozmetické úpravy spustia trigger, čo môže rýchlo dosiahnuť dennú kvótu Apps Script na rušných hárkoch. Hodinový časový trigger je takmer vždy správna odpoveď.

Kvóty Apps Script, o ktorých treba vedieť

Bezplatná úroveň Apps Script má limity, ktoré stojí za to rešpektovať:

LimitHodnota (bezplatná úroveň)
Celkový čas spustenia za deň90 minút
Čas jedného spustenia6 minút
Volania UrlFetchApp za deň20 000
Veľkosť payloadu UrlFetchApp50 MB
Veľkosť hlavičiek UrlFetchApp8 KB
Triggery na používateľa na skript20

Pre typickú synchronizáciu kontaktov (pár tisíc kontaktov, hodinová) ste nikde blízko žiadnemu z týchto. Jediný, ktorý treba sledovať, je 6-minútové jedno spustenie. Ak niekedy synchronizujete státisíce kontaktov v jednom kroku, zoskupte ich do menších kúskov (skript vyššie to už robí cez BATCH_SIZE).

Spracovanie importu asynchrónne

Importný endpoint Brevo je asynchrónny: okamžite dostanete processId a samotný import beží na strane servera. Pre väčšinu synchronizácií hárka je to v poriadku, vystrelte a zabudnite, Brevo pošle e-mail so súhrnom, keď každá dávka skončí.

Ak chcete blokovať, kým import skutočne neskončí, polujte endpoint stavu procesu:

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 pre blokujúce čakanie. Nespite príliš dlho. Máte 6 minút celkom na spustenie.

Pridanie notify webhooku

Čistejší vzor ako polovanie: nasadte svoj Apps Script ako Web App a odovzdajte jeho URL ako notifyUrl. Brevo na ňu pošle POST, keď import skončí.

// Add to Code.gs
function 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');
}

Nasaďte: Deploy → New deployment → Web app, nastavte „Who has access” na Anyone, skopírujte výslednú URL a odovzdajte ju ako notifyUrl vo vašom importnom payloade:

payload.notifyUrl = 'https://script.google.com/macros/s/AKfy.../exec';

Teraz Brevo posiela výsledok späť na vlastný skript vášho hárka, čím uzatvára slučku bez externej infraštruktúry.

Riešenie problémov

400 Bad Request s error: "Attribute X not found". Stĺpec vo vašom hárku sa mapuje na atribút, ktorý Brevo nepozná. Buď premenujte stĺpec hárka tak, aby zodpovedal existujúcemu atribútu, alebo vytvorte atribút v Brevo (Contacts → Settings → Contact attributes).

401 Unauthorized. API kľúč je nesprávny alebo vypršal. Spustite znova Configure API key, vložte čerstvý kľúč z dashboardu Brevo.

429 Too Many Requests. Narážate na rate limit Brevo. Importný endpoint povoľuje okolo 30 volaní za minútu. Ak agresívne dávkujete, pridajte Utilities.sleep(2000) medzi dávky v slučke.

Skript potichu nebeží podľa rozvrhu. Skontrolujte Triggers v editore Apps Script. Ak trigger opakovane zlyháva, Google ho zakáže. Kliknite do triggeru a pozrite si dôvod zlyhania, zvyčajne ide o problém s povolením, ktorý môžete znova autorizovať.

Menu Brevo sa neobjavilo. onOpen sa spúšťa iba pri (znovu)otvorení hárka od základov. Znovu načítajte kartu prehliadača.

Vyskakovacie okno povolení sa stále vracia. Pravdepodobne ste upravili scope skriptu (pridali novú službu Googlu). Apps Script vyzýva na opätovnú autorizáciu vždy, keď sa potrebné povolenia zmenia. Spustite akúkoľvek funkciu raz z editora, aby sa spustila výzva, a schváľte.

Prečo to poráža Zapier a priateľov

Apps Script je zadarmo, žije vo vnútri infraštruktúry Googlu a má priamy prístup k údajom hárka. Žiadne riadkové spúšťanie udalostí, žiadne ceny za úlohu, žiadne rate limits okrem kvóty Googlu (ktorá je pre tento druh úlohy štedrá). Druhá strana mince: zaviazujete sa napísať a udržiavať malý kus kódu. Pre synchronizáciu kontaktov je to asi 100 riadkov a v podstate nulová priebežná údržba.

Spárujte to s denným triggerom a hárkom, ktorý váš obchodný tím už aktualizuje, a máte kontaktný pipeline do Brevo s nulovou opakovanou prácou.

Ďalšie čítanie

Frequently Asked Questions

Potrebujem server alebo nejakú infraštruktúru na synchronizáciu Google Sheet do Brevo?
Nie. Google Apps Script beží vo vnútri samotných Google Sheets. Napíšete funkciu, uložíte projekt a Google ho hostuje a spúšťa. Skript môže zasiahnuť API Brevo priamo cez UrlFetchApp.
Kde mám uložiť API kľúč Brevo?
Použite PropertiesService.getScriptProperties(). Je to úložisko kľúč/hodnota spravované Googlom, vymedzené na projekt Apps Script. Nehardkódujte kľúč v zdroji. Spolupracovníci na hárku by ho videli.
Ako toto spustím automaticky každý deň?
Otvorte Apps Script → Triggers → Add Trigger. Vyberte funkciu syncSheetToBrevo, vyberte 'Time-driven' a nastavte dennú alebo hodinovú kadenciu. Kvóta Googlu je 90 minút denne celkového času spustenia Apps Script na bezplatnej úrovni, pre synchronizáciu kontaktov to bohato stačí.
Existuje limit počtu riadkov?
Importný endpoint Brevo akceptuje asi 10 MB inline JSON. To je zhruba 30 000 až 50 000 kontaktov v závislosti od toho, koľko atribútov má každý. UrlFetchApp v Apps Script dokáže poslať 50 MB payload, takže úzkym hrdlom je Brevo, nie Apps Script. Pre väčšie úlohy zoskupte riadky do dávok.
Začnite zadarmo s Brevo