Πώς να χτίσετε ένα Google Apps Script για εισαγωγή των επαφών ενός φύλλου στο Brevo
Στείλτε επαφές από Google Sheet στο Brevo αυτόματα. Ένα ολοκληρωμένο Apps Script με αποθήκευση API key, run-on-edit, time-based triggers, custom μενού και διαχείριση σφαλμάτων. Δεν χρειάζεται server.
Αν η ομάδα σας ήδη ζει σε Google Sheet, leads πωλήσεων, εγγραφές εκδηλώσεων, λίστα επαφών συνεργατών, η μεταφορά αυτών των δεδομένων στο Brevo δεν χρειάζεται να περιλαμβάνει εξαγωγή CSV και χειροκίνητη επανεισαγωγή. Το Google Apps Script σας επιτρέπει να συνδέσετε το Sheet απευθείας με το API του Brevo. Το script τρέχει μέσα στην υποδομή της Google, οπότε δεν υπάρχει τίποτα να φιλοξενήσετε, να deploy-άρετε ή να φροντίσετε.
Αυτός ο οδηγός περπατά μέσα από ένα λειτουργικό script: ένα custom μενού Sync to Brevo στο Sheet σας, ένα αυτόματο ωριαίο trigger, ασφαλή αποθήκευση API key, διαχείριση batch και λίγο δομημένο logging ώστε να μπορείτε να καταλάβετε τι συνέβη.
Τι χρειάζεστε
- Ένα Google Sheet με επαφές (μία γραμμή ανά επαφή, γραμμή κεφαλίδων πρώτα)
- Λογαριασμό Brevo και API key (Settings → SMTP & API → API Keys)
- Το αριθμητικό ID της λίστας Brevo στην οποία θέλετε να προστεθούν οι επαφές
Αυτό είναι. Χωρίς npm, χωρίς Python, χωρίς server.
Διάταξη φύλλου
Το script σε αυτόν τον οδηγό αναμένει μια γραμμή κεφαλίδων ακολουθούμενη από μία επαφή ανά γραμμή. Οι στήλες αντιστοιχίζονται με βάση το όνομα κεφαλίδας στα attributes του Brevo:
| firstName | lastName | company | city | |
|---|---|---|---|---|
| [email protected] | Jane | Doe | Acme | Berlin |
| [email protected] | John | Smith | Globex | Paris |
Το email είναι υποχρεωτικό και αντιστοιχίζεται case-insensitive. Όλα τα άλλα στέλνονται στο Brevo ως attribute επαφής. Τα προσαρμοσμένα attributes (οτιδήποτε πέρα από τα standard όπως FIRSTNAME, LASTNAME) πρέπει πρώτα να υπάρχουν στον λογαριασμό σας στο Brevo, ορίστε τα στο Contacts → Settings → Contact attributes, ή μέσω του API του Brevo.
Ανοίξτε τον editor Apps Script
Στο Sheet σας: Extensions → Apps Script. Μια νέα καρτέλα ανοίγει με ένα κενό Code.gs. Αντικαταστήστε τα περιεχόμενα με το script παρακάτω.
Το πλήρες script
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;}Αυτό είναι όλο. Αποθηκεύστε το (⌘S / Ctrl+S), ονομάστε το έργο κάτι σαν «Brevo sync» και επιστρέψτε στο Sheet σας.
Πρώτη εκτέλεση
Επαναφορτώστε το Sheet, το νέο μενού Brevo εμφανίζεται στην κορυφή.
- Κάντε κλικ Brevo → Configure API key, επικολλήστε το κλειδί
xkeysib-..., κάντε κλικ OK. - Κάντε κλικ Brevo → Sync sheet to Brevo. Η Google θα ζητήσει άδειες την πρώτη φορά:
- «View and manage your spreadsheets», απαιτείται για ανάγνωση των γραμμών
- «Connect to an external service», απαιτείται για κλήση στο api.brevo.com
- Εγκρίνετε. Το script τρέχει. Ένα πράσινο toast στο κάτω δεξιά σας λέει πόσες επαφές βγήκαν.
Αν αποτύχει, κάντε κλικ Extensions → Apps Script → View → Logs για να δείτε τον status code ανά batch από το Brevo. Η πιο συνηθισμένη αποτυχία είναι ένα 400 λόγω ελλιπούς προσαρμοσμένου attribute, δείτε την ενότητα αντιμετώπισης προβλημάτων παρακάτω.
Εκτέλεση σε προγραμματισμένη βάση
Στον editor Apps Script: Triggers (το εικονίδιο ρολογιού στην αριστερή πλευρική μπάρα) → Add Trigger.
- Choose function:
syncSheetToBrevo - Event source: Time-driven
- Type: Hour timer (ή Day timer για συγχρονισμό μία φορά την ημέρα)
- Interval: κάθε ώρα (ή ό,τι σας ταιριάζει)
Αποθηκεύστε. Η Google θα τρέχει τη συνάρτηση σε αυτή τη συχνότητα για πάντα, χωρίς server, χωρίς cron, χωρίς συντήρηση.
Μπορείτε επίσης να χρησιμοποιήσετε From spreadsheet → On edit αν θέλετε κάθε αλλαγή κελιού να ενεργοποιεί συγχρονισμό. Προσοχή με αυτό, ακόμη και κοσμητικές επεξεργασίες θα ενεργοποιήσουν το trigger, που μπορεί να εξαντλήσει την ημερήσια ποσόστωση Apps Script γρήγορα σε πολυσύχναστα φύλλα. Το ωριαίο time trigger είναι σχεδόν πάντα η σωστή απάντηση.
Ποσοστώσεις Apps Script που πρέπει να γνωρίζετε
Το δωρεάν tier του Apps Script έχει όρια που αξίζει να σεβαστείτε:
| Όριο | Τιμή (δωρεάν tier) |
|---|---|
| Συνολικός χρόνος εκτέλεσης ανά ημέρα | 90 λεπτά |
| Χρόνος μίας εκτέλεσης | 6 λεπτά |
Κλήσεις UrlFetchApp ανά ημέρα | 20.000 |
Μέγεθος payload UrlFetchApp | 50 MB |
Μέγεθος headers UrlFetchApp | 8 KB |
| Triggers ανά χρήστη ανά script | 20 |
Για έναν τυπικό συγχρονισμό επαφών (μερικές χιλιάδες επαφές, ωριαίο), δεν είστε καθόλου κοντά σε κανένα από αυτά. Το μόνο που πρέπει να προσέχετε είναι 6 λεπτά για μία εκτέλεση, αν ποτέ συγχρονίσετε εκατοντάδες χιλιάδες επαφές με τη μία, χωρίστε τις σε μικρότερα κομμάτια (το script παραπάνω ήδη το κάνει μέσω BATCH_SIZE).
Διαχείριση της εισαγωγής ασύγχρονα
Το endpoint εισαγωγής του Brevo είναι ασύγχρονο: παίρνετε ένα processId πίσω αμέσως, και η πραγματική εισαγωγή τρέχει στην πλευρά του server. Για τους περισσότερους συγχρονισμούς φύλλου αυτό είναι εντάξει, σπρώξε και ξέχνα, το Brevo θα στείλει email σύνοψης όταν τελειώσει κάθε batch.
Αν θέλετε να μπλοκάρετε μέχρι η εισαγωγή να είναι πραγματικά ολοκληρωμένη, κάντε poll στο endpoint κατάστασης διεργασίας:
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 είναι το ισοδύναμο μπλοκαρίσματος αναμονής στο Apps Script. Μη κοιμάστε πολύ, έχετε 6 λεπτά συνολικά ανά εκτέλεση.
Προσθήκη webhook ειδοποίησης
Ένα πιο καθαρό μοτίβο από polling: deploy-άρετε το Apps Script σας ως Web App και περάστε το URL του ως notifyUrl. Το Brevo θα κάνει POST σε αυτό όταν τελειώσει η εισαγωγή.
// 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');}Deploy: Deploy → New deployment → Web app, ορίστε «Who has access» σε Anyone, αντιγράψτε το URL που προκύπτει, και περάστε το ως notifyUrl στο payload εισαγωγής σας:
payload.notifyUrl = 'https://script.google.com/macros/s/AKfy.../exec';Τώρα το Brevo στέλνει το αποτέλεσμα πίσω στο δικό σας script του Sheet, κλείνοντας το loop χωρίς εξωτερική υποδομή.
Αντιμετώπιση προβλημάτων
400 Bad Request με error: "Attribute X not found". Μια στήλη στο Sheet σας αντιστοιχίζεται σε attribute που το Brevo δεν γνωρίζει. Είτε μετονομάστε τη στήλη του Sheet ώστε να ταιριάζει σε υπάρχον attribute, είτε δημιουργήστε το attribute στο Brevo (Contacts → Settings → Contact attributes).
401 Unauthorized. Το API key είναι λάθος ή έχει λήξει. Εκτελέστε ξανά Configure API key, επικολλήστε φρέσκο κλειδί από το dashboard του Brevo.
429 Too Many Requests. Χτυπάτε το rate limit του Brevo. Το endpoint εισαγωγής επιτρέπει περίπου 30 κλήσεις ανά λεπτό. Αν κάνετε batches επιθετικά, προσθέστε Utilities.sleep(2000) μεταξύ batches στο loop.
Το script σιωπηλά δεν τρέχει σε πρόγραμμα. Ελέγξτε Triggers στον editor Apps Script. Αν ένα trigger αποτυγχάνει επανειλημμένα, η Google το απενεργοποιεί. Κάντε κλικ μέσα στο trigger για να δείτε τον λόγο αποτυχίας, συνήθως είναι ένα ζήτημα αδειών που μπορείτε να επανεξουσιοδοτήσετε.
Το μενού Brevo δεν εμφανίστηκε. Το onOpen τρέχει μόνο όταν (επαν)ανοίγετε το Sheet από την αρχή. Επαναφορτώστε την καρτέλα του browser.
Το popup αδειών συνεχίζει να επιστρέφει. Πιθανότατα επεξεργαστήκατε τα scopes του script (προσθέσατε νέα υπηρεσία Google). Το Apps Script ζητάει ξανά εξουσιοδότηση κάθε φορά που οι απαιτούμενες άδειες αλλάζουν. Εκτελέστε οποιαδήποτε συνάρτηση μία φορά από τον editor για να ενεργοποιήσετε το prompt και να εγκρίνετε.
Γιατί αυτό κερδίζει τα Zapier και άλλα
Το Apps Script είναι δωρεάν, ζει μέσα στην υποδομή της Google και έχει άμεση πρόσβαση στα δεδομένα του Sheet, χωρίς event firing γραμμή προς γραμμή, χωρίς χρέωση ανά task, χωρίς rate limits άλλα από την ποσόστωση της Google (που είναι γενναιόδωρη για αυτό το είδος εργασίας). Η άλλη πλευρά: δεσμεύεστε να γράψετε και να συντηρήσετε ένα μικρό κομμάτι κώδικα. Για συγχρονισμό επαφών, αυτό είναι περίπου 100 γραμμές και βασικά μηδενική συνεχιζόμενη συντήρηση.
Συνδυάστε αυτό με ένα ημερήσιο trigger και ένα φύλλο που η ομάδα πωλήσεών σας ήδη ενημερώνει, και έχετε pipeline επαφών στο Brevo με μηδενική επαναλαμβανόμενη εργασία.