Google Apps Script कैसे बनाएँ जो शीट के कॉन्टैक्ट्स को Brevo में इम्पोर्ट करे

Google Sheet से कॉन्टैक्ट्स को Brevo में स्वचालित रूप से पुश करें। API key स्टोरेज, ऑन-एडिट रन, टाइम-बेस्ड ट्रिगर्स, कस्टम मेन्यू, और एरर हैंडलिंग के साथ एक पूरी Apps Script, कोई सर्वर नहीं चाहिए।

Featured image for article: Google Apps Script कैसे बनाएँ जो शीट के कॉन्टैक्ट्स को Brevo में इम्पोर्ट करे

यदि आपकी टीम पहले से ही एक Google Sheet में रहती है, सेल्स लीड्स, इवेंट साइनअप्स, एक पार्टनर कॉन्टैक्ट लिस्ट, उस डेटा को Brevo में लाने के लिए हाथ से CSVs एक्सपोर्ट और रीइम्पोर्ट करने की ज़रूरत नहीं है। Google Apps Script आपको Sheet को सीधे Brevo के API से वायर करने देता है। स्क्रिप्ट Google के इन्फ़्रास्ट्रक्चर के अंदर चलती है, इसलिए कुछ भी होस्ट, डिप्लॉय, या बेबीसिट करने की ज़रूरत नहीं है।

यह गाइड एक काम करने वाली स्क्रिप्ट के माध्यम से चलती है: आपकी Sheet में एक कस्टम Sync to Brevo मेन्यू आइटम, एक स्वचालित प्रति-घंटा ट्रिगर, सुरक्षित API key स्टोरेज, बैच हैंडलिंग, और थोड़ा सा संरचित लॉगिंग ताकि आप बता सकें कि क्या हुआ।

आपको क्या चाहिए

  • कॉन्टैक्ट्स के साथ एक Google Sheet (एक रो प्रति कॉन्टैक्ट, हेडर रो पहले)
  • एक Brevo अकाउंट और एक API key (Settings -> SMTP & API -> API Keys)
  • Brevo लिस्ट का संख्यात्मक ID जिसमें आप कॉन्टैक्ट्स जोड़ना चाहते हैं

बस इतना ही। कोई npm नहीं, कोई Python नहीं, कोई सर्वर नहीं।

Sheet लेआउट

इस गाइड की स्क्रिप्ट एक हेडर रो की उम्मीद करती है, उसके बाद एक कॉन्टैक्ट प्रति रो। कॉलम हेडर नाम द्वारा Brevo एट्रिब्यूट्स पर मैप किए जाते हैं:

emailfirstNamelastNamecompanycity
[email protected]JaneDoeAcmeBerlin
[email protected]JohnSmithGlobexParis

email अनिवार्य है और केस-इनसेंसिटिवली मैच किया जाता है। बाकी सब Brevo को कॉन्टैक्ट एट्रिब्यूट के रूप में भेजा जाता है। कस्टम एट्रिब्यूट्स (FIRSTNAME, LASTNAME जैसे स्टैंडर्ड वालों से परे कुछ भी) को पहले आपके Brevo अकाउंट में मौजूद होना चाहिए, उन्हें Contacts -> Settings -> Contact attributes के तहत डिफ़ाइन करें, या Brevo API के माध्यम से।

Apps Script एडिटर खोलें

अपनी Sheet में: Extensions -> Apps Script। एक नया टैब खुलता है खाली Code.gs के साथ। कंटेंट को नीचे की स्क्रिप्ट से रिप्लेस करें।

पूरी स्क्रिप्ट

Code.gs
const BREVO_API_BASE = 'https://api.brevo.com/v3';
const BREVO_LIST_ID = 42; // <- the Brevo list to import contacts into
const SHEET_NAME = 'Contacts'; // <- name of the sheet tab to read
const 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 मेन्यू शीर्ष पर दिखाई देता है।

  1. Brevo -> Configure API key क्लिक करें, अपनी xkeysib-... key पेस्ट करें, OK क्लिक करें।
  2. Brevo -> Sync sheet to Brevo क्लिक करें। Google पहली बार अनुमतियाँ माँगेगा:
    • “View and manage your spreadsheets”, रोज़ पढ़ने के लिए चाहिए
    • “Connect to an external service”, api.brevo.com को कॉल करने के लिए चाहिए
  3. अप्रूव करें। स्क्रिप्ट चलती है। नीचे-दाएँ में एक हरा टोस्ट आपको बताता है कि कितने कॉन्टैक्ट्स बाहर गए।

यदि यह विफल होता है, तो प्रति-बैच स्टेटस कोड Brevo से देखने के लिए Extensions -> Apps Script -> View -> Logs क्लिक करें। सबसे आम विफलता एक 400 है क्योंकि एक गुम कस्टम एट्रिब्यूट है, नीचे ट्रबलशूटिंग सेक्शन देखें।

इसे शेड्यूल पर चलाएँ

Apps Script एडिटर में: Triggers (बाएँ साइडबार में घड़ी आइकन) -> Add Trigger

  • Choose function: syncSheetToBrevo
  • Event source: Time-driven
  • Type: Hour timer (या दिन में एक बार सिंक के लिए Day timer)
  • Interval: हर घंटे (या जो भी फ़िट हो)

सेव करें। Google फ़ंक्शन को उस कैडेंस पर हमेशा के लिए चलाएगा, कोई सर्वर नहीं, कोई क्रॉन नहीं, कोई रखरखाव नहीं।

आप 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 के माध्यम से ऐसा करती है)।

इम्पोर्ट को asynchronously हैंडल करना

Brevo का इम्पोर्ट एंडपॉइंट asynchronous है: आपको तुरंत एक 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.gs
function 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 परिणाम को आपकी Sheet की अपनी स्क्रिप्ट पर वापस पोस्ट करता है, बाहरी इन्फ़्रास्ट्रक्चर के बिना लूप को बंद करता है।

ट्रबलशूटिंग

400 Bad Request error: "Attribute X not found" के साथ. आपकी Sheet में एक कॉलम एक एट्रिब्यूट पर मैप करता है जिसके बारे में Brevo को पता नहीं है। या तो Sheet कॉलम को मौजूदा एट्रिब्यूट से मैच करने के लिए रीनेम करें, या Brevo में एट्रिब्यूट बनाएँ (Contacts -> Settings -> Contact attributes)।

401 Unauthorized. API key ग़लत या एक्सपायर्ड है। Configure API key को फिर से चलाएँ, Brevo के डैशबोर्ड से एक ताज़ा key पेस्ट करें।

429 Too Many Requests. आप Brevo की रेट लिमिट हिट कर रहे हैं। इम्पोर्ट एंडपॉइंट प्रति मिनट लगभग 30 कॉल्स की अनुमति देता है। यदि आप आक्रामक रूप से बैच कर रहे हैं, तो लूप में बैचों के बीच Utilities.sleep(2000) जोड़ें।

स्क्रिप्ट चुपचाप शेड्यूल पर नहीं चलती. Apps Script एडिटर में Triggers चेक करें। यदि एक ट्रिगर बार-बार विफल हो रहा है, तो Google इसे डिसेबल कर देता है। विफलता का कारण देखने के लिए ट्रिगर पर क्लिक करें, आमतौर पर एक अनुमति समस्या जिसे आप पुन: अधिकृत कर सकते हैं।

Brevo मेन्यू दिखाई नहीं दिया. onOpen केवल तब चलता है जब आप Sheet को नए सिरे से (पुनः)खोलते हैं। ब्राउज़र टैब को रीलोड करें।

अनुमति पॉपअप बार-बार आता रहता है. आपने शायद स्क्रिप्ट के स्कोप्स को एडिट किया है (एक नई Google सेवा जोड़ी)। Apps Script आवश्यक अनुमतियाँ बदलने पर हर बार ऑथराइज़ेशन के लिए फिर से प्रॉम्प्ट करता है। प्रॉम्प्ट को ट्रिगर करने और अप्रूव करने के लिए एडिटर से कोई भी फ़ंक्शन एक बार चलाएँ।

यह Zapier और दोस्तों से बेहतर क्यों है

Apps Script मुफ़्त है, Google के इन्फ़्रा के अंदर रहता है, और Sheet के डेटा तक सीधी पहुँच है, कोई रो-बाय-रो इवेंट फ़ायरिंग नहीं, कोई प्रति-टास्क प्राइसिंग नहीं, Google के कोटा (जो इस तरह की जॉब के लिए उदार है) के अलावा कोई रेट लिमिट नहीं। पलट कर देखा जाए: आप कोड का एक छोटा टुकड़ा लिखने और बनाए रखने के लिए प्रतिबद्ध हो रहे हैं। एक कॉन्टैक्ट सिंक के लिए, यह लगभग 100 लाइनें और मूल रूप से शून्य चालू रखरखाव है।

इसे एक दैनिक ट्रिगर और एक शीट के साथ जोड़ें जिसे आपकी सेल्स टीम पहले से ही अपडेट कर रही है, और आपके पास Brevo में एक कॉन्टैक्ट पाइपलाइन है शून्य आवर्ती कार्य के साथ।

आगे पढ़ने के लिए

Frequently Asked Questions

क्या Google Sheet को Brevo के साथ सिंक करने के लिए मुझे सर्वर या कोई इन्फ़्रास्ट्रक्चर चाहिए?
नहीं। Google Apps Script Google Sheets के अंदर ही चलता है। आप एक फ़ंक्शन लिखते हैं, प्रोजेक्ट सेव करते हैं, और Google इसे होस्ट करता है और चलाता है। स्क्रिप्ट UrlFetchApp का उपयोग करके Brevo के API को सीधे हिट कर सकती है।
मैं Brevo API key कहाँ स्टोर करूँ?
PropertiesService.getScriptProperties() का उपयोग करें, यह एक Google-प्रबंधित key/value स्टोर है जो Apps Script प्रोजेक्ट तक स्कोप्ड है। key को सोर्स में हार्डकोड न करें, Sheet पर सहयोगी इसे देख सकेंगे।
मैं इसे हर दिन स्वचालित रूप से कैसे चलाऊँ?
Apps Script -> Triggers -> Add Trigger खोलें। syncSheetToBrevo फ़ंक्शन चुनें, 'Time-driven' चुनें, और दैनिक/प्रति घंटा कैडेंस सेट करें। Google का कोटा फ़्री टियर पर कुल Apps Script एक्ज़िक्यूशन समय का 90 मिनट/दिन है, कॉन्टैक्ट सिंक के लिए पर्याप्त है।
क्या कोई रो लिमिट है?
Brevo का इम्पोर्ट एंडपॉइंट लगभग 10 MB इनलाइन JSON स्वीकार करता है। यह लगभग 30,000-50,000 कॉन्टैक्ट्स है, इस पर निर्भर करते हुए कि प्रत्येक के पास कितने एट्रिब्यूट्स हैं। Apps Script का UrlFetchApp 50 MB पेलोड भेज सकता है, इसलिए बॉटलनेक Brevo है, Apps Script नहीं। बड़ी जॉब्स के लिए, रोज़ को बैच करें।
Brevo के साथ मुफ्त में शुरू करें