Bir Google Sheet'in Kişilerini Brevo'ya Aktarmak için Google Apps Script Nasıl Oluşturulur
Bir Google Sheet'ten kişileri Brevo'ya otomatik olarak gönderin. API anahtarı saklama, düzenleme üzerine çalıştırma, zaman tabanlı tetikleyiciler, özel bir menü ve hata yönetimi içeren tam bir Apps Script. Sunucu gerekmez.
Ekibiniz zaten bir Google Sheet’te yaşıyorsa (satış adayları, etkinlik kayıtları, bir iş ortağı kişi listesi), bu verileri Brevo’ya almak için CSV’leri dışa aktarmak ve elle yeniden içe aktarmak gerekmez. Google Apps Script, Sheet’i doğrudan Brevo API’sine bağlamanızı sağlar. Komut dosyası Google’ın altyapısında çalışır, dolayısıyla barındırılacak, dağıtılacak veya bakılacak hiçbir şey yoktur.
Bu kılavuz çalışan bir betik üzerinden ilerler: Sheet’inizde özel bir Brevo’ya Senkronize Et menü öğesi, otomatik saatlik tetikleyici, güvenli API anahtarı saklama, grup işleme ve neyin olduğunu anlamanızı sağlayacak küçük bir yapılandırılmış günlüğe alma.
İhtiyacınız olanlar
- Kişileri olan bir Google Sheet (kişi başına bir satır, önce başlık satırı)
- Bir Brevo hesabı ve bir API anahtarı (Settings → SMTP & API → API Keys)
- Kişilerin eklenmesini istediğiniz Brevo listesinin sayısal kimliği
Hepsi bu. npm yok, Python yok, sunucu yok.
Sayfa düzeni
Bu kılavuzdaki betik, ardından satır başına bir kişi gelen bir başlık satırı bekler. Sütunlar başlık adıyla Brevo özniteliklerine eşlenir:
| firstName | lastName | company | city | |
|---|---|---|---|---|
| [email protected] | Jane | Doe | Acme | Berlin |
| [email protected] | John | Smith | Globex | Paris |
email zorunludur ve büyük/küçük harf duyarsız olarak eşleştirilir. Diğer her şey Brevo’ya bir kişi özniteliği olarak gönderilir. Özel öznitelikler (FIRSTNAME, LASTNAME gibi standart olanların ötesindeki herhangi bir şey) Brevo hesabınızda önce mevcut olmalıdır. Bunları Kişiler → Ayarlar → Kişi öznitelikleri altında veya Brevo API üzerinden tanımlayın.
Apps Script editörünü açın
Sheet’inizde: Extensions → Apps Script. Boş bir Code.gs ile yeni bir sekme açılır. İçeriği aşağıdaki betikle değiştirin.
Tam betik
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;}İşte hepsi bu. Kaydedin (⌘S / Ctrl+S), projeye “Brevo sync” gibi bir ad verin ve Sheet’inize geri dönün.
İlk çalıştırma
Sheet’i yeniden yükleyin: yeni Brevo menüsü üstte görünür.
- Brevo → Configure API key öğesine tıklayın,
xkeysib-...anahtarınızı yapıştırın, OK’ye tıklayın. - Brevo → Sync sheet to Brevo öğesine tıklayın. Google ilk seferde izin ister:
- “View and manage your spreadsheets”: satırları okumak için gerekli
- “Connect to an external service”: api.brevo.com çağırmak için gerekli
- Onaylayın. Komut dosyası çalışır. Sağ alttaki yeşil bir bildirim, kaç kişinin gönderildiğini söyler.
Başarısız olursa, Extensions → Apps Script → View → Logs öğesine tıklayarak Brevo’dan grup başına durum kodunu görün. En yaygın hata, eksik bir özel öznitelik nedeniyle 400 hatasıdır. Aşağıdaki sorun giderme bölümüne bakın.
Zamanlanmış olarak çalıştırın
Apps Script editöründe: Triggers (sol kenar çubuğundaki saat simgesi) → Add Trigger.
- Choose function:
syncSheetToBrevo - Event source: Time-driven
- Type: Hour timer (veya günde bir kez senkronizasyon için Day timer)
- Interval: her saat (veya size uygun olan)
Kaydedin. Google işlevi sonsuza kadar bu kadansta çalıştıracak, sunucu yok, cron yok, bakım yok.
Her hücre değişikliğinin bir senkronizasyonu tetiklemesini istiyorsanız From spreadsheet → On edit de kullanabilirsiniz. Buna dikkat edin, kozmetik düzenlemeler bile tetikleyiciyi ateşler ve bu, yoğun sayfalarda Apps Script’in günlük kotasını hızla tüketebilir. Saatlik zaman tetikleyicisi neredeyse her zaman doğru cevaptır.
Bilinmesi gereken Apps Script kotaları
Ücretsiz Apps Script katmanının saygı duyulması gereken sınırları vardır:
| Sınır | Değer (ücretsiz katman) |
|---|---|
| Günlük toplam çalışma süresi | 90 dakika |
| Tek yürütme süresi | 6 dakika |
Günlük UrlFetchApp çağrıları | 20.000 |
UrlFetchApp yük boyutu | 50 MB |
UrlFetchApp üst bilgi boyutu | 8 KB |
| Kullanıcı başına komut dosyası başına tetikleyici | 20 |
Tipik bir kişi senkronizasyonu için (birkaç bin kişi, saatlik), bunların hiçbirine yakın değilsiniz. İzlenmesi gereken tek şey 6 dakikalık tek yürütme: bir seferde yüz binlerce kişiyi senkronize ederseniz, bunları daha küçük parçalara gruplayın (yukarıdaki betik bunu zaten BATCH_SIZE ile yapar).
İçe aktarmayı asenkron olarak işleme
Brevo’nun içe aktarma uç noktası asenkrondur: hemen bir processId geri alırsınız ve gerçek içe aktarma sunucu tarafında çalışır. Çoğu sayfa senkronizasyonu için bu iyidir, başlat ve unut, Brevo her grup bittiğinde bir özet e-postası gönderecek.
İçe aktarma gerçekten bitene kadar bloke etmek istiyorsanız, süreç durumu uç noktasını sorgulayın:
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’teki engelleyen beklemenin eşdeğeridir. Çok uzun uyumayın, yürütme başına toplam 6 dakikanız var.
Bir notify webhook’u ekleme
Sorgulamaktan daha temiz bir desen: Apps Script’inizi bir Web App olarak dağıtın ve URL’sini notifyUrl olarak iletin. Brevo, içe aktarma bittiğinde ona POST yapacak.
// 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');}Dağıtın: Deploy → New deployment → Web app, “Who has access” değerini Anyone olarak ayarlayın, sonuçtaki URL’yi kopyalayın ve bunu içe aktarma yükünüzde notifyUrl olarak iletin:
payload.notifyUrl = 'https://script.google.com/macros/s/AKfy.../exec';Şimdi Brevo sonucu Sheet’inizin kendi betiğine geri gönderir, döngüyü harici altyapı olmadan kapatır.
Sorun giderme
error: "Attribute X not found" ile 400 Bad Request: Sheet’inizdeki bir sütun, Brevo’nun bilmediği bir özniteliğe eşleniyor. Ya Sheet sütununu mevcut bir öznitelikle eşleşecek şekilde yeniden adlandırın ya da özniteliği Brevo’da oluşturun (Kişiler → Ayarlar → Kişi öznitelikleri).
401 Unauthorized: API anahtarı yanlış veya süresi dolmuş. Configure API key’i yeniden çalıştırın, Brevo panosundan yeni bir anahtar yapıştırın.
429 Too Many Requests: Brevo’nun hız sınırına ulaşıyorsunuz. İçe aktarma uç noktası dakikada yaklaşık 30 çağrıya izin verir. Agresif olarak gruplandırıyorsanız, döngüde gruplar arasında Utilities.sleep(2000) ekleyin.
Komut dosyası zamanlanmış olarak sessizce çalışmıyor: Apps Script editöründe Triggers’ı kontrol edin. Bir tetikleyici tekrar tekrar başarısız oluyorsa, Google bunu devre dışı bırakır. Başarısızlık nedenini görmek için tetikleyiciye tıklayın, genellikle yeniden yetkilendirebileceğiniz bir izin sorunudur.
Brevo menüsü görünmedi: onOpen yalnızca Sheet’i sıfırdan (yeniden) açtığınızda çalışır. Tarayıcı sekmesini yeniden yükleyin.
İzin açılır penceresi geri gelmeye devam ediyor: Muhtemelen betiğin kapsamlarını düzenlediniz (yeni bir Google hizmeti eklediniz). Apps Script, gerekli izinler değiştiğinde her seferinde yeniden yetkilendirme ister. İstemi tetiklemek ve onaylamak için editörden herhangi bir işlevi bir kez çalıştırın.
Bu neden Zapier ve arkadaşlarını yener
Apps Script ücretsizdir, Google’ın altyapısında yaşar ve Sheet verilerine doğrudan erişimi vardır. Satır satır olay tetikleme yok, görev başına fiyatlandırma yok, Google’ın kotası dışında hız sınırı yok (bu tür bir iş için cömerttir). Diğer yandan: küçük bir kod parçası yazmaya ve sürdürmeye söz veriyorsunuz. Bir kişi senkronizasyonu için, bu yaklaşık 100 satır ve aslında sıfır süregelen bakım demektir.
Bunu günlük bir tetikleyici ve satış ekibinizin zaten güncellediği bir sayfayla eşleştirin ve sıfır yinelenen işle Brevo’ya bir kişi pipeline’ınız olur.