استيراد جهات اتصال Google Sheets إلى Brevo باستخدام Apps Script

مثال Apps Script قابل للتكرار يتحقق من الموافقة ويربط الحقول ويحفظ إلغاء الاشتراك ويعرض أخطاء API.

google apps script brevo

ما الإجابة المختصرة؟

أنشئ أعمدة email وfirstName وlastName وconsent في Sheet. ضع BREVO_API_KEY في Script Properties ومعرّف القائمة في الشيفرة، ثم شغل syncSheetToBrevo. يعالج السكربت الصفوف الموافق عليها فقط، ويتحقق من الإلغاءات في Brevo، ويزيل التكرار ويتوقف عند أخطاء API. هذا Apps Script تديره بنفسك، وليس موصلًا مدمجًا.

معرفة المزيد
في هذه الصفحة

يسهل تصدير اثني عشر تسجيلًا لفعالية مرة واحدة. في الاستيراد الثاني قد يتكرر بريد، وقد يكون شخص آخر ألغى اشتراكه في Brevo. يجب أن يعرف الاستيراد المتكرر الحالتين.

يستخدم السكربت نقاط نهاية Brevo لقراءة جهات الاتصال وإنشائها وتحديثها. وهو لاستيراد صغير مجدول، وليس محمّلًا ضخمًا أو موصل Google Sheets مدمجًا. لا يرسل بريدًا. تقع عليك مسؤولية دليل الموافقة وتشغيل السكربت.

جهز الجدول والقائمة

أنشئ تبويب Contacts بهذه العناوين تمامًا. استخدم مربعات اختيار لـ consent ولا تجعلها TRUE إلا بموافقة للغرض التسويقي المقصود.

emailfirstNamelastNameconsent
[email protected]JaneDoeTRUE
[email protected]AlexLeeFALSE

أنشئ أو اختر قائمة Brevo وانسخ معرّفها الرقمي إلى LIST_ID. تحقق من وجود السمتين FNAME وLNAME في Brevo؛ لا يربط المثال سواهما. احفظ سجلًا منفصلًا لوقت الموافقة وطريقتها إذا لم يثبت الجدول ذلك. الخلية TRUE وحدها ليست دليلًا.

في Extensions → Apps Script → Project Settings → Script Properties أضف BREVO_API_KEY. الخصائص مشتركة بين مستخدمي السكربت، لذا قيد حق التحرير. لا تضع المفتاح في خلية أو في الشيفرة. راجع قواعد وصول Google.

السكربت

استبدل محتوى Code.gs بهذا المثال. اضبط LIST_ID قبل أول تشغيل.

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

احفظ، واختر syncSheetToBrevo، ثم اضغط Run. تطلب Google إذن Sheets والطلبات الخارجية. افحص سجل {written, skipped} في Executions. إذا أعادت القراءة 404 ينشئ جهة اتصال؛ وإذا أعادت 200 يحدثها. توقف الأخطاء الأخرى التشغيل. نجاح كتابة جهة اتصال لا يعني إرسال حملة.

يزيل السكربت البريد المكرر في التشغيل الواحد ويعيد سؤال Brevo في التشغيل التالي. لا يضبط emailBlacklisted على false ولا يغير العنوان. إذا أبلغ Brevo عن حظر عام أو إلغاء من القائمة، يتجاوز الصف حتى لو كانت الخلية TRUE. لا تستخدمه لإلغاء الانسحاب. يحذر Brevo من أن تغيير بريد جهة محظورة قد يرفع الحظر.

تحقق من تشغيل واحد قبل الجدولة

استخدم قائمة وجهات اتصال تجريبية تملكها. شغل صف TRUE وصف FALSE وعنوانًا مكررًا: المتوقع كتابة واحدة وتجاوزان. ألغ اشتراك الجهة التجريبية في Brevo وشغل ثانية: يجب ألا يكتب لها. لاختبار الفشل، ضع LIST_ID غير صالح مؤقتًا، وتحقق من خطأ HTTP في Executions، ثم أعد المعرّف وكرر. لا تختبر على بيانات عملاء حقيقية. فُحصت الشيفرة محليًا بردود Brevo وهمية، دون كتابة حية.

للجدولة افتح Apps Script → Triggers → Add Trigger واختر syncSheetToBrevo والفاصل الزمني. تعمل المشغلات بحساب منشئها، ويجب أن يحتفظ بوصوله للجدول. ابدأ يوميًا لجدول صغير وراقب الأخطاء. تحد Google وقت التنفيذ واستخدام URL Fetch؛ قسم المصادر الكبيرة إلى أعمال محدودة.

عند 429 يقرأ السكربت x-sib-ratelimit-reset ويعيد المحاولة مرتين على الأكثر. إذا تجاوز الانتظار المطلوب 60 ثانية، يعيد 429 لتشغيل لاحق بدل المحاولة المبكرة. توقف الأخطاء الأخرى التشغيل، ويبدأ التشغيل التالي من البداية. قد تلغي جهة اشتراكها بين القراءة والكتابة؛ استخدم المثال إذا كان هذا الفاصل القصير مقبولًا. تحتاج الضمانات الأقوى مطابقة الأحداث ومصدر موافقة موثوقًا وفحصًا أخيرًا قبل الإرسال.

للجداول الكبيرة، تعيد نقطة استيراد Brevo غير المتزامنة قيمة processId. تؤكد قبول المهمة، لا اكتمالها. افحص النتيجة قبل إعلان النجاح. يبين مسار جهة بجهة هنا الصف الذي سبب الخطأ.

الأسئلة المتكررة

هل أحتاج موصلًا أصليًا لـ Google Sheets؟

لا. يعمل المثال في مشروع Apps Script مرتبط بالجدول ويستدعي API جهات اتصال Brevo. تدير السكربت ومفتاح API والمشغل المجدول بنفسك.

أي الصفوف تُرسل إلى Brevo؟

فقط صفوف consent بقيمة TRUE مع بريد صالح. يزيل السكربت العناوين المكررة في كل تشغيل ويتحقق من Brevo قبل الكتابة.

هل يعيد التشغيل اشتراك من ألغاه؟

يتجاوز السكربت جهات الاتصال التي يصنفها Brevo emailBlacklisted أو ملغية الاشتراك في القائمة المستهدفة. لا يرسل emailBlacklisted: false ولا يغير البريد. تحقق من مصادر الموافقة والحظر الأخرى قبل الإنتاج.

كيف أعيد تشغيل محاولة فاشلة؟

اقرأ الخطأ في تنفيذات Apps Script، أصلح السبب، ثم شغل الوظيفة نفسها. تستخدم الكتابات البريد ومعرّف القائمة نفسيهما، فلا ينشئ الصف الناجح جهة ثانية.

اطلب الوصول المبكر

أدخل اسمك الأول وعنوان بريدك الإلكتروني للعمل. سنرسل إليك رابطًا لتأكيد طلب الوصول إلى Tajo.

احصل على Brevo