ایک شیٹ کے گاہکوں کو Brevo میں امپورٹ کرنے کے لیے Google Apps Script کیسے بنائیں
Google Sheet سے گاہکوں کو خودکار طور پر Brevo میں پش کریں۔ API کلید اسٹوریج، ایڈٹ پر چلنے، وقت کی بنیاد پر ٹرگرز، کسٹم مینو اور ایرر ہینڈلنگ کے ساتھ ایک مکمل Apps Script، کوئی سرور درکار نہیں۔
اگر آپ کی ٹیم پہلے سے Google Sheet میں رہتی ہے (سیلز لیڈز، ایونٹ سائن اپس، پارٹنر کانٹیکٹ لسٹ)، تو اس ڈیٹا کو Brevo میں لانے کے لیے CSVs ایکسپورٹ کرنا اور انہیں ہاتھ سے دوبارہ امپورٹ کرنا ضروری نہیں۔ Google Apps Script آپ کو شیٹ کو براہ راست Brevo کے API سے جوڑنے دیتا ہے۔ اسکرپٹ Google کے انفراسٹرکچر کے اندر چلتا ہے، تو ہوسٹ کرنے، ڈپلائے کرنے یا دیکھ بھال کرنے کے لیے کچھ نہیں ہے۔
یہ گائیڈ ایک ورکنگ اسکرپٹ سے گزرتی ہے: آپ کی شیٹ میں ایک کسٹم Sync to Brevo مینو آئٹم، ایک خودکار گھنٹہ وار ٹرگر، محفوظ API کلید اسٹوریج، بیچ ہینڈلنگ، اور تھوڑی سی اسٹرکچرڈ لاگنگ تاکہ آپ کو پتہ چل سکے کیا ہوا۔
آپ کو کیا چاہیے
- گاہکوں والی ایک Google Sheet (فی گاہک ایک قطار، ہیڈر قطار پہلے)
- ایک Brevo اکاؤنٹ اور ایک API کلید (Settings → SMTP & API → API Keys)
- اس Brevo فہرست کا نمبری ID جس میں آپ گاہک شامل کرنا چاہتے ہیں
بس یہی ہے۔ کوئی npm نہیں، کوئی Python نہیں، کوئی سرور نہیں۔
شیٹ لے آؤٹ
اس گائیڈ میں اسکرپٹ ایک ہیڈر قطار کی توقع کرتا ہے جس کے بعد فی قطار ایک گاہک ہو۔ کالم ہیڈر کے نام سے Brevo اٹریبیوٹس میں میپ ہوتے ہیں:
| firstName | lastName | company | city | |
|---|---|---|---|---|
| [email protected] | Jane | Doe | Acme | Berlin |
| [email protected] | John | Smith | Globex | Paris |
email لازمی ہے اور کیس کے لحاظ سے بے حساس میچ ہوتا ہے۔ باقی سب کچھ Brevo کو گاہک اٹریبیوٹ کے طور پر بھیجا جاتا ہے۔ کسٹم اٹریبیوٹس (FIRSTNAME، LASTNAME جیسے معیاریوں سے باہر کوئی بھی چیز) آپ کے Brevo اکاؤنٹ میں پہلے موجود ہونی چاہئیں، انہیں Contacts → Settings → Contact attributes کے تحت یا Brevo API کے ذریعے بنائیں۔
Apps Script ایڈیٹر کھولیں
اپنی شیٹ میں: Extensions → Apps Script۔ ایک نیا ٹیب کھلتا ہے ایک خالی Code.gs کے ساتھ۔ مواد کو نیچے دیے گئے اسکرپٹ سے بدلیں۔
مکمل اسکرپٹ
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” جیسا رکھیں اور اپنی شیٹ پر واپس جائیں۔
پہلی بار چلانا
شیٹ کو ری لوڈ کریں، نیا Brevo مینو اوپر ظاہر ہوتا ہے۔
- Brevo → Configure API key پر کلک کریں، اپنی
xkeysib-...کلید پیسٹ کریں، OK کلک کریں۔ - Brevo → Sync sheet to Brevo پر کلک کریں۔ پہلی بار Google اجازتیں مانگے گا:
- “اپنی اسپریڈ شیٹس دیکھیں اور منظم کریں”: قطاریں پڑھنے کے لیے ضروری
- “ایک بیرونی سروس سے جڑیں”: api.brevo.com کو کال کرنے کے لیے ضروری
- منظوری دیں۔ اسکرپٹ چلتا ہے۔ نیچے دائیں طرف ایک سبز ٹوسٹ آپ کو بتاتا ہے کتنے گاہک باہر گئے۔
اگر یہ ناکام ہوتا ہے، Brevo سے فی بیچ سٹیٹس کوڈ دیکھنے کے لیے Extensions → Apps Script → View → Logs پر کلک کریں۔ سب سے عام ناکامی ایک گمشدہ کسٹم اٹریبیوٹ کی وجہ سے 400 ہے، نیچے ٹربل شوٹنگ سیکشن دیکھیں۔
شیڈول پر چلائیں
Apps Script ایڈیٹر میں: Triggers (بائیں سائڈ بار میں گھڑی کا آئیکن) → Add Trigger۔
- فنکشن منتخب کریں:
syncSheetToBrevo - ایونٹ ماخذ: Time-driven
- قسم: Hour timer (یا ایک بار روزانہ سنک کے لیے Day timer)
- وقفہ: ہر گھنٹہ (یا جو فٹ ہو)
سیو کریں۔ Google ہمیشہ کے لیے اس تال پر فنکشن چلائے گا، کوئی سرور نہیں، کوئی cron نہیں، کوئی دیکھ بھال نہیں۔
اگر آپ چاہتے ہیں کہ ہر سیل تبدیلی سنک کو ٹرگر کرے تو From spreadsheet → On edit بھی استعمال کر سکتے ہیں۔ اس کے ساتھ احتیاط کریں، یہاں تک کہ کاسمیٹک ایڈٹس بھی ٹرگر کو فائر کریں گی، جو مصروف شیٹس پر Apps Script کے روزانہ کوٹے کو تیزی سے ہٹ کر سکتی ہے۔ گھنٹہ وار ٹائم ٹرگر تقریباً ہمیشہ صحیح جواب ہے۔
جاننے کے لیے Apps Script کوٹے
مفت Apps Script ٹیئر میں احترام کرنے کے قابل حدود ہیں:
| حد | قدر (مفت ٹیئر) |
|---|---|
| فی دن کل رن ٹائم | 90 منٹ |
| واحد ایگزیکیوشن کا وقت | 6 منٹ |
فی دن UrlFetchApp کالز | 20,000 |
UrlFetchApp پے لوڈ سائز | 50 MB |
UrlFetchApp ہیڈرز سائز | 8 KB |
| فی صارف فی اسکرپٹ ٹرگرز | 20 |
ایک عام گاہک سنک کے لیے (کچھ ہزار گاہک، گھنٹہ وار)، آپ ان میں سے کسی کے قریب نہیں ہیں۔ صرف ایک پر نظر رکھنی ہے 6 منٹ کا واحد ایگزیکیوشن: اگر آپ کبھی ایک ساتھ سینکڑوں ہزار گاہک سنک کرتے ہیں، انہیں چھوٹے ٹکڑوں میں توڑیں (اوپر اسکرپٹ پہلے ہی BATCH_SIZE کے ذریعے یہ کرتا ہے)۔
امپورٹ کو ایسنکرونس طریقے سے ہینڈل کرنا
Brevo کا امپورٹ اینڈ پوائنٹ ایسنکرونس ہے: آپ فوراً ایک processId واپس پاتے ہیں اور اصل امپورٹ سرور سائیڈ پر چلتا ہے۔ زیادہ تر شیٹ سنک کے لیے یہ ٹھیک ہے، فائر اور بھول جاؤ، Brevo ہر بیچ ختم ہونے پر خلاصہ ای میل کرے گا۔
اگر آپ جب تک امپورٹ واقعی ہو نہ جائے بلاک کرنا چاہتے ہیں، پراسس سٹیٹس اینڈ پوائنٹ کو پول کریں:
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 منٹ ہیں۔
ایک نوٹیفائی ویب ہک شامل کرنا
پولنگ کے بجائے ایک صاف ستھرا پیٹرن: اپنے 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 → New deployment → Web app، “Who has access” کو Anyone پر سیٹ کریں، نتیجے میں آنے والا URL کاپی کریں اور اپنے امپورٹ پے لوڈ میں notifyUrl کے طور پر پاس کریں:
payload.notifyUrl = 'https://script.google.com/macros/s/AKfy.../exec';اب Brevo نتیجہ آپ کی شیٹ کے اپنے اسکرپٹ کو واپس پوسٹ کرتا ہے، بغیر بیرونی انفراسٹرکچر کے لوپ کو بند کر دیتا ہے۔
ٹربل شوٹنگ
400 Bad Request کے ساتھ error: "Attribute X not found": آپ کی شیٹ میں ایک کالم ایک اٹریبیوٹ پر میپ ہوتا ہے جس کے بارے میں Brevo کو معلوم نہیں۔ یا تو شیٹ کالم کا نام بدل کر موجودہ اٹریبیوٹ سے میچ کریں، یا Brevo میں اٹریبیوٹ بنائیں (Contacts → Settings → Contact attributes)۔
401 Unauthorized: API کلید غلط ہے یا ختم ہو چکی ہے۔ Configure API key دوبارہ چلائیں، Brevo کے ڈیش بورڈ سے ایک تازہ کلید پیسٹ کریں۔
429 Too Many Requests: آپ Brevo کی ریٹ لمٹ کو ہٹ کر رہے ہیں۔ امپورٹ اینڈ پوائنٹ تقریباً 30 کالز فی منٹ کی اجازت دیتا ہے۔ اگر آپ جارحانہ طور پر بیچنگ کر رہے ہیں، لوپ میں بیچ کے درمیان Utilities.sleep(2000) شامل کریں۔
اسکرپٹ خاموشی سے شیڈول پر نہیں چلتا: Apps Script ایڈیٹر میں Triggers چیک کریں۔ اگر کوئی ٹرگر بار بار ناکام ہو رہا ہے، Google اسے غیر فعال کر دیتا ہے۔ ناکامی کی وجہ دیکھنے کے لیے ٹرگر پر کلک کریں، عام طور پر یہ ایک اجازت کا مسئلہ ہوتا ہے جسے آپ دوبارہ منظور کر سکتے ہیں۔
Brevo مینو ظاہر نہیں ہوا: onOpen صرف اس وقت چلتا ہے جب آپ شیٹ کو شروع سے (دوبارہ) کھولتے ہیں۔ براؤزر ٹیب کو ری لوڈ کریں۔
اجازتوں کا پاپ اپ بار بار آتا رہتا ہے: آپ نے غالباً اسکرپٹ کے دائرہ کار میں ترمیم کی (نئی Google سروس شامل کی)۔ Apps Script ضرورت پڑنے والی اجازتوں میں کسی بھی تبدیلی کے بعد دوبارہ منظوری مانگتا ہے۔ پرامپٹ کو ٹرگر کرنے اور منظوری دینے کے لیے ایڈیٹر سے کوئی فنکشن ایک بار چلائیں۔
یہ Zapier اور دوستوں کو کیوں شکست دیتا ہے
Apps Script مفت ہے، Google کی انفراسٹرکچر کے اندر رہتا ہے، اور اسے شیٹ کے ڈیٹا تک براہ راست رسائی ہے، کوئی قطار در قطار ایونٹ فائرنگ نہیں، کوئی فی ٹاسک قیمت نہیں، Google کے کوٹے کے علاوہ کوئی ریٹ لمٹ نہیں (جو اس قسم کے کام کے لیے سخی ہے)۔ دوسری طرف: آپ کوڈ کا ایک چھوٹا سا ٹکڑا لکھنے اور برقرار رکھنے کا عہد کر رہے ہیں۔ گاہک سنک کے لیے، یہ تقریباً 100 لائنیں ہیں اور بنیادی طور پر کوئی جاری دیکھ بھال نہیں۔
اسے روزانہ ٹرگر اور ایسی شیٹ کے ساتھ جوڑیں جسے آپ کی سیلز ٹیم پہلے سے اپ ڈیٹ کر رہی ہے، اور آپ کے پاس Brevo میں ایک کانٹیکٹ پائپ لائن ہے جس میں صفر بار بار آنے والا کام ہے۔