Hogyan építs Google Apps Scriptet, ami egy tábla kapcsolatait a Brevóba importálja

Tedd át a kapcsolatokat egy Google Sheetből a Brevóba automatikusan. Egy teljes Apps Script API-kulcs tárolással, szerkesztésre futtatással, idő-alapú triggerekkel, egyéni menüvel és hibakezeléssel, szerver nélkül.

Featured image for article: Hogyan építs Google Apps Scriptet, ami egy tábla kapcsolatait a Brevóba importálja

Ha a csapatod már egy Google Sheetben él, értékesítési leadek, eseményregisztrációk, partner-kapcsolatlista, akkor az adat Brevóba juttatása nem kell, hogy kézi CSV exportokról és újraimportokról szóljon. A Google Apps Script lehetővé teszi, hogy közvetlenül a táblát kösd a Brevo API-jához. A script a Google infrastruktúráján belül fut, így nincs mit hosztolni, deployolni vagy babázni.

Ez az útmutató végigmegy egy működő scripten: egy egyéni Sync to Brevo menüpont a tábládban, automatikus óránkénti trigger, biztonságos API kulcs tárolás, csomagolt feldolgozás és egy kis strukturált naplózás, hogy lásd, mi történt.

Mire van szükséged

  • Egy Google Sheet kapcsolatokkal (egy sor egy kapcsolat, fejlécsor először)
  • Egy Brevo fiók és egy API kulcs (Settings -> SMTP & API -> API Keys)
  • Annak a Brevo listának a numerikus azonosítója, amibe a kapcsolatokat hozzá akarod adni

Ennyi. Nincs npm, nincs Python, nincs szerver.

Tábla-elrendezés

Az ebben az útmutatóban szereplő script egy fejlécsort vár, utána egy kapcsolatot soronként. Az oszlopok fejlécnév alapján vannak hozzárendelve a Brevo attribútumokhoz:

emailfirstNamelastNamecompanycity
[email protected]JaneDoeAcmeBerlin
[email protected]JohnSmithGlobexParis

Az email kötelező és kis-nagybetű érzéketlenül van egyeztetve. Minden más kapcsolat-attribútumként megy a Brevónak. Az egyéni attribútumoknak (a standardokon, mint FIRSTNAME, LASTNAME túl bárminek) először létezniük kell a Brevo fiókodban, definiáld őket a Contacts -> Settings -> Contact attributes alatt, vagy a Brevo API-n keresztül.

Nyisd meg az Apps Script szerkesztőt

A tábládban: Extensions -> Apps Script. Egy új fül nyílik üres Code.gs fájllal. Cseréld le a tartalmat az alábbi scriptre.

A teljes script

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;
}

Ennyi az egész. Mentsd el (⌘S / Ctrl+S), nevezd el a projektet valami olyannak, mint “Brevo sync”, és menj vissza a tábládba.

Első futtatás

Töltsd újra a táblát, az új Brevo menü megjelenik a tetején.

  1. Kattints a Brevo -> Configure API key menüpontra, illeszd be az xkeysib-... kulcsodat, kattints az OK gombra.
  2. Kattints a Brevo -> Sync sheet to Brevo menüpontra. A Google először engedélyt kér:
    • “View and manage your spreadsheets”, a sorok olvasásához kell
    • “Connect to an external service”, az api.brevo.com hívásához kell
  3. Hagyd jóvá. A script lefut. Egy zöld toast a jobb alsó sarokban megmondja, hány kapcsolat ment ki.

Ha hibát ad, kattints az Extensions -> Apps Script -> View -> Logs menüpontra, hogy lásd a Brevótól érkező csomagonkénti státuszkódot. A leggyakoribb hiba egy 400, ami egy hiányzó egyéni attribútum miatt jön, lásd a hibaelhárítási szakaszt alább.

Futtasd ütemezve

Az Apps Script szerkesztőben: Triggers (az óra ikon a bal oldalsávon) -> Add Trigger.

  • Choose function: syncSheetToBrevo
  • Event source: Time-driven
  • Type: Hour timer (vagy Day timer napi egyszeri szinkronra)
  • Interval: óránként (vagy bármi, ami illik)

Mentsd el. A Google örökre futtatja a függvényt azzal az ütemezéssel, szerver nélkül, cron nélkül, karbantartás nélkül.

A From spreadsheet -> On edit triggert is használhatod, ha minden cellaváltozás indítson szinkronizálást. Ezzel óvatosan, még a kozmetikai szerkesztések is elsütik a triggert, ami forgalmas táblákon gyorsan lemeríti az Apps Script napi kvótát. Az óránkénti idő-trigger szinte mindig a megfelelő válasz.

Apps Script kvóták, amiket érdemes ismerni

Az ingyenes Apps Script szintnek vannak korlátai, amiket érdemes tisztelni:

KorlátÉrték (ingyenes szint)
Teljes futási idő naponta90 perc
Egyetlen futás ideje6 perc
UrlFetchApp hívások naponta20 000
UrlFetchApp payload mérete50 MB
UrlFetchApp fejlécek mérete8 KB
Triggerek scriptenként és felhasználónként20

Egy tipikus kapcsolatszinkron (néhány ezer kapcsolat, óránként) egyik közelébe sem kerül. Az egyetlen, amit figyelni kell, a 6 perces egyetlen futás, ha valaha százezreket szinkronizálsz egyszerre, csomagold kisebb darabokra (a fenti script ezt már megteszi a BATCH_SIZE-on keresztül).

Az import aszinkron kezelése

A Brevo import végpontja aszinkron: azonnal visszakapsz egy processId-t, és a tényleges import a szerver oldalon fut. A legtöbb tábla-szinkronra ez rendben van, kilőni és elfelejteni, a Brevo e-mailt küld összegzéssel, amikor minden csomag befejeződik.

Ha blokkolni szeretnél, amíg az import tényleg elkészül, kérdezd le a folyamat-állapot végpontot:

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';
}

Az Utilities.sleep az Apps Script megfelelője a blokkoló várakozásnak. Ne aludj túl sokat, összesen 6 perced van futásonként.

Notify webhook hozzáadása

Tisztább minta, mint a lekérdezés: deployold az Apps Scriptet Web App-ként, és add át az URL-jét notifyUrl-ként. A Brevo POST-ol rá, amikor az import befejeződik.

// 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');
}

Deploy: Deploy -> New deployment -> Web app, állítsd a “Who has access” beállítást Anyone-ra, másold ki a kapott URL-t, és add át notifyUrl-ként az import payloadban:

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

A Brevo most visszaposztolja az eredményt a tábla saját scriptjére, lezárva a kört külső infrastruktúra nélkül.

Hibaelhárítás

400 Bad Request error: "Attribute X not found" üzenettel. Egy oszlop a tábládban olyan attribútumra mutat, amit a Brevo nem ismer. Vagy nevezd át a tábla oszlopát egy létező attribútum nevére, vagy hozd létre az attribútumot a Brevóban (Contacts -> Settings -> Contact attributes).

401 Unauthorized. Az API kulcs rossz vagy lejárt. Futtasd újra a Configure API key menüpontot, illeszd be a friss kulcsot a Brevo dashboardjáról.

429 Too Many Requests. A Brevo rate limitjébe ütközöl. Az import végpont nagyjából percenként 30 hívást enged. Ha agresszíven csomagolsz, adj Utilities.sleep(2000) hívást a csomagok közé a ciklusban.

A script csendben nem fut ütemezve. Ellenőrizd a Triggers részt az Apps Script szerkesztőben. Ha egy trigger ismételten meghibásodik, a Google letiltja. Kattints bele a triggerbe, hogy lásd a hiba okát, általában egy engedélyezési probléma, amit újra kell hagynod.

A Brevo menü nem jelent meg. Az onOpen csak akkor fut, amikor (újra)megnyitod a táblát nulláról. Töltsd újra a böngészőfület.

Az engedélykérő ablak újra és újra felugrik. Valószínűleg módosítottad a script scope-jait (új Google szolgáltatást adtál hozzá). Az Apps Script újra kéri az engedélyt, valahányszor a szükséges jogosultságok változnak. Futtass bármilyen függvényt egyszer a szerkesztőből, hogy elindítsd a kérést, és hagyd jóvá.

Miért jobb ez, mint a Zapier és társai

Az Apps Script ingyenes, a Google infrájában él, és közvetlenül hozzáfér a tábla adataihoz, nincs soronkénti esemény, nincs feladatonkénti árazás, nincs más rate limit, mint a Google kvótája (ami nagyvonalú erre a fajta munkára). A másik oldal: elkötelezed magad egy kis kód megírása és karbantartása mellett. Egy kapcsolatszinkronra ez nagyjából 100 sor és gyakorlatilag nulla folyamatos karbantartás.

Párosítsd ezt egy napi triggerrel és egy táblával, amit az értékesítési csapat úgyis frissít, és máris kész egy kapcsolat-pipeline-od a Brevóba, nulla ismétlődő munkával.

További olvasmányok

Frequently Asked Questions

Kell szerver vagy bármilyen infrastruktúra ahhoz, hogy egy Google Sheetet a Brevóval szinkronizáljak?
Nem. A Google Apps Script magán a Google Sheets környezetén belül fut. Megírsz egy függvényt, elmented a projektet, és a Google hosztolja és futtatja. A script közvetlenül tudja hívni a Brevo API-t az UrlFetchApp segítségével.
Hol tároljam a Brevo API kulcsot?
Használd a PropertiesService.getScriptProperties() szolgáltatást, ez egy Google által kezelt kulcs/érték tár, az Apps Script projektre szűkítve. Ne kódold be a kulcsot a forrásba, a tábla közreműködői látnák.
Hogyan futtatom automatikusan minden nap?
Nyisd meg az Apps Scriptet -> Triggers -> Add Trigger. Válaszd a syncSheetToBrevo függvényt, válaszd a 'Time-driven' opciót, és állíts be napi vagy óránkénti ütemet. A Google ingyenes szinten napi 90 percnyi teljes Apps Script futási időt ad, bőven elég egy kapcsolatszinkronra.
Van sor-korlát?
A Brevo import végpontja kb. 10 MB inline JSON-t fogad el. Ez nagyjából 30 000-50 000 kapcsolat, attól függően, hány attribútum van mindegyiken. Az Apps Script UrlFetchApp 50 MB payloadot tud küldeni, így a szűk keresztmetszet a Brevo, nem az Apps Script. Nagyobb munkáknál csomagold a sorokat.
Kezdje ingyen a Brevo-val