Bir etkinlikteki on iki kişiyi bir kez dışa aktarmak kolaydır. İkinci aktarımda aynı adres iki kez bulunabilir ve başka biri Brevo’da abonelikten çıkmış olabilir. Tekrarlanabilir aktarım ikisini de görmelidir.
Skript Brevo kişi okuma, oluşturma ve güncelleme uç noktalarını kullanır. Küçük, zamanlanmış aktarım içindir; toplu yükleyici veya yerleşik Google Sheets bağlayıcısı değildir. E-posta göndermez. İzin kanıtı ve çalıştırma size aittir.
Sheet ve listeyi hazırlayın
Tam bu başlıklarla Contacts sekmesi oluşturun. consent için onay kutusu kullanın ve TRUE değerini yalnızca amaçlanan pazarlama izni varsa verin.
| firstName | lastName | consent | |
|---|---|---|---|
| [email protected] | Jane | Doe | TRUE |
| [email protected] | Alex | Lee | FALSE |
Brevo listesi oluşturun veya seçin; sayısal kimliğini LIST_ID içine yazın. FNAME ve LNAME alanlarının Brevo’da bulunduğunu doğrulayın. Yalnızca bunlar eşlenir. Sheet iznin zamanını ve biçimini kanıtlamıyorsa kaynak kaydı başka yerde saklayın. TRUE hücresi tek başına kanıt değildir.
Extensions → Apps Script → Project Settings → Script Properties içinde BREVO_API_KEY ekleyin. Özellikler skript kullanıcıları arasında paylaşılır; düzenleme yetkisini sınırlayın. Anahtarı hücreye veya koda koymayın. Google erişim kurallarına bakın.
Skript
Code.gs içeriğini bu örnekle değiştirin. İlk çalışmadan önce LIST_ID değerini ayarlayın.
const LIST_ID = 42; // Replace with your existing Brevo list ID.const TAB = 'Contacts';const BASE = 'https://api.brevo.com/v3';
function syncSheetToBrevo() { const key = PropertiesService.getScriptProperties().getProperty('BREVO_API_KEY'); if (!key) throw new Error('Set BREVO_API_KEY in Script Properties first'); const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(TAB); if (!sheet) throw new Error('Contacts tab missing'); const values = sheet.getDataRange().getValues(); const headers = values.shift().map(h => String(h).trim()); const columns = ['email', 'firstName', 'lastName', 'consent'] .map(name => headers.indexOf(name)); if (columns.includes(-1)) throw new Error('Required header missing'); const [emailCol, firstCol, lastCol, consentCol] = columns; const seen = new Set(); const result = { written: 0, skipped: 0 };
for (const row of values) { const email = String(row[emailCol] || '').trim().toLowerCase(); if (row[consentCol] !== true || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) || seen.has(email)) { result.skipped++; continue; } seen.add(email); const path = '/contacts/' + encodeURIComponent(email); const current = brevo_(key, path, 'get'); if (current.code !== 200 && current.code !== 404) { throw new Error(`Contact read failed (${current.code}) for row ${result.written + result.skipped + 2}`); } if (current.code === 200) { const contact = JSON.parse(current.text); if (contact.emailBlacklisted || (contact.listUnsubscribed || []).includes(LIST_ID)) { result.skipped++; continue; } } const attributes = {}; if (row[firstCol] !== '') attributes.FNAME = String(row[firstCol]); if (row[lastCol] !== '') attributes.LNAME = String(row[lastCol]); const body = { attributes, listIds: [LIST_ID] }; const saved = current.code === 404 ? brevo_(key, '/contacts', 'post', { email, ...body }) : brevo_(key, path, 'put', body); if (![201, 204].includes(saved.code)) { throw new Error(`Contact write failed (${saved.code}) for ${email}: ${saved.text.slice(0, 200)}`); } result.written++; } Logger.log(JSON.stringify(result)); return result;}
function brevo_(key, path, method, body) { for (let attempt = 0; attempt < 3; attempt++) { const response = UrlFetchApp.fetch(BASE + path, { method, contentType: 'application/json', headers: { 'api-key': key }, payload: body ? JSON.stringify(body) : undefined, muteHttpExceptions: true, }); const code = response.getResponseCode(); if (code !== 429) return { code, text: response.getContentText() }; if (attempt === 2) return { code, text: response.getContentText() }; const reset = Object.entries(response.getHeaders()).find( ([name]) => name.toLowerCase() === 'x-sib-ratelimit-reset' ); const raw = Number(reset && reset[1]); const seconds = Number.isFinite(raw) && raw > 0 ? raw : 2 ** attempt; const waitSeconds = seconds + 2 ** attempt; if (waitSeconds > 60) return { code, text: response.getContentText() }; Utilities.sleep(waitSeconds * 1000); }}Kaydedin, syncSheetToBrevo seçin ve Run düğmesine basın. Google, Sheets ve dış istek izinlerini sorar. Executions içinde {written, skipped} kaydına bakın. Okumada 404 oluşturma, 200 güncelleme demektir; diğer hatalar çalışmayı durdurur. Başarılı kişi yazma, kampanya gönderildiği anlamına gelmez.
Skript tek çalışmada yinelenen adresleri ayıklar, sonraki çalışmada Brevo’yu yeniden sorgular. emailBlacklisted değerini false yapmaz ve adresi değiştirmez. Brevo genel engel veya hedef listeden çıkma bildirirse, Sheet TRUE olsa bile satır atlanır. Abonelikten çıkmayı geri almak için kullanmayın. Brevo uyarır: engelli kişinin adresini değiştirmek engeli kaldırabilir.
Zamanlamadan önce bir çalışmayı sınayın
Denetlediğiniz test listesi ve kişileri kullanın. Bir TRUE, bir FALSE ve yinelenen adresle çalıştırın: bir yazma, iki atlama bekleyin. Test kişisini Brevo’da abonelikten çıkarıp yeniden çalıştırın: o kişi için yazma olmamalı. Hata testi için geçici geçersiz LIST_ID girin, Executions içinde HTTP hatasını görün, kimliği düzeltip yineleyin. Gerçek müşteri kayıtlarında test yapmayın. Bu yazıdaki kod yerel olarak sahte Brevo yanıtlarıyla sınandı, canlı yazma yapılmadı.
Apps Script → Triggers → Add Trigger menüsünden syncSheetToBrevo ve zaman aralığını seçin. Tetikleyici onu oluşturan hesapta çalışır; hesap Sheet erişimini korumalıdır. Küçük Sheet için günlük başlayıp hataları izleyin. Google çalışma süresini ve URL Fetch kullanımını sınırlar; büyük kaynakları sınırlı işlere bölün.
429 durumunda skript x-sib-ratelimit-reset değerini okur ve en çok iki kez daha dener. Gerekli bekleme 60 saniyeyi aşıyorsa erken denemek yerine 429 döndürür. Diğer hatalarda da durur; sonraki çalışma baştan başlar. Kişi okuma ve yazma arasında abonelikten çıkabilir. Bu dar yarış penceresi kabul edilebiliyorsa örneği kullanın. Daha sıkı güvence için olay mutabakatı, güvenilir izin deposu ve gönderim öncesi son kontrol gerekir.
Büyük Sheet için Brevo asenkron aktarımı processId döndürür. Bu işin kabul edildiğini söyler, bittiğini değil. Başarı bildirmeden sonucu kontrol edin. Buradaki kişi başına işlem başarısız satırı görünür kılar.
Sık Sorulan Sorular
Yerleşik Google Sheets bağlayıcısı gerekli mi?
Hayır. Örnek Sheet’e bağlı Apps Script projesinde çalışır ve Brevo kişi API’sini çağırır. Skripti, API anahtarını ve zamanlanmış tetikleyiciyi siz yönetirsiniz.
Hangi satırlar Brevo’ya gider?
Yalnızca consent TRUE ve e-posta adresi geçerli olanlar. Skript her çalışmada tekrarları ayıklar ve yazmadan önce Brevo’yu kontrol eder.
Tekrar çalıştırma abonelikten çıkan kişiyi yeniden ekler mi?
Brevo’nun emailBlacklisted olarak işaretlediği veya hedef listeden çıkardığı kişileri atlar. emailBlacklisted: false göndermez, e-posta adresini değiştirmez. Üretim öncesinde diğer izin ve engelleme kaynaklarını inceleyin.
Başarısız çalışma nasıl yinelenir?
Apps Script yürütme hatasını okuyup nedeni düzeltin, ardından aynı işlevi yeniden çalıştırın. Aynı adres ve liste kimliği kullanıldığı için başarılı satır ikinci kişi oluşturmaz.