كيفية استيراد جهات اتصال CSV أو Excel إلى Brevo باستخدام سكريبت (Python و Node.js و cURL)
استيراد جماعي لجهات الاتصال من ملف CSV أو Excel إلى Brevo باستخدام واجهة import-contacts API. يتضمن سكريبتات جاهزة للتشغيل بلغة Python و Node.js، وأمر cURL من سطر واحد، ومعالجة الأخطاء، ونصائح للملفات الأكبر من 10 ميجابايت.
إذا احتجت يوماً إلى دفع بضعة آلاف من جهات الاتصال من جدول بيانات إلى Brevo، فإن المسار اليدوي عبر الواجهة يصبح مؤلماً بسرعة: اختر الملف، اربط الأعمدة، اختر القائمة، انتظر، كرر. السكريبت يقوم بنفس المهمة في ثوانٍ، والأهم من ذلك، يمكنك تشغيله مرة أخرى وفق جدول زمني، أو بعد تصدير قاعدة بيانات، أو كلما أخرج CRM الخاص بك CSV جديداً.
يغطي هذا الدليل نقطة نهاية API التي يوفرها Brevo لهذا الغرض بالضبط، مع سكريبتات كاملة وعاملة بـ Python و Node.js وأمر cURL من سطر واحد. كل شيء في هذا الدليل يستخدم POST /v3/contacts/import.
نقطة النهاية بنظرة سريعة
POST https://api.brevo.com/v3/contacts/importContent-Type: application/jsonapi-key: YOUR_API_KEY
{ "listIds": [42], "updateExistingContacts": true, "emailBlacklist": false, "smsBlacklist": false}تأتي الاستجابة بسرعة:
{ "processId": 78 }هذا 202 Accepted. قبل Brevo الاستيراد ويعالجه في الخلفية. الـ processId هو مقبضك للتتبع إذا أردت استطلاع الإكمال أو إعداد webhook عبر notifyUrl.
أمور تستحق الملاحظة قبل كتابة الكود:
- يمكن أن يكون المحتوى CSV (
fileBody) أو JSON (jsonBody) أو رابطاً بعيداً (fileUrl). اختر واحداً. تستخدم صيغة CSV الفواصل المنقوطة (;) لفصل الأعمدة، وليس الفواصل العادية. هذا خطأ شائع. fileBodyوjsonBodyمحدودان بـ 10 ميجابايت. يوصي Brevo بالبقاء حول 8 ميجابايت للأمان. لأي شيء أكبر، ارفع الملف إلى S3 أو GCS أو أي مضيف HTTPS ومررfileUrlبدلاً من ذلك.- السمات المخصصة التي لا توجد في حسابك يتم تجاهلها بصمت. أنشئها في واجهة Brevo (أو عبر Attributes API) قبل استيراد الصفوف التي تستخدمها، وإلا فستختفي البيانات.
updateExistingContactsافتراضياًtrue. إذا ضبطته علىfalse، سيتخطى Brevo جهات الاتصال التي يوجد بريدها الإلكتروني بالفعل. مفيد لمهام “إضافة الجديد فقط”.emptyContactsAttributesيتحكم في ما إذا كانت الخلايا الفارغة في CSV تمحو القيم الموجودة. الافتراضيfalse(الخلايا الفارغة يتم تجاهلها). اضبط علىtrueإذا كان CSV هو مصدر الحقيقة وأردت أن تمسح القيم الفارغة البيانات القديمة.
احصل على مفتاح API
سجّل الدخول إلى Brevo، وانتقل إلى Settings → SMTP & API → API Keys وأنشئ مفتاحاً جديداً. يبدو كـ xkeysib-.... ستمرره كرأس HTTP api-key في كل طلب. تعامل معه كأنه كلمة مرور، لديه صلاحيات قراءة/كتابة كاملة على حسابك.
نمط نظيف: ضعه في متغير بيئة حتى لا يصل إلى شجرة الكود المصدري الخاصة بك.
export BREVO_API_KEY="xkeysib-..."Python: اقرأ ملف CSV وادفعه إلى Brevo
هذا أبسط سكريبت ممكن: اقرأ CSV محلياً بالمكتبة القياسية، وأرسل المحتوى مباشرة إلى Brevo. لا حاجة لأي SDK تابع لطرف ثالث بخلاف requests.
import csvimport ioimport osimport sysimport requests
API_KEY = os.environ["BREVO_API_KEY"]LIST_ID = 42 # the Brevo list to add contacts toINPUT_FILE = sys.argv[1] if len(sys.argv) > 1 else "contacts.csv"
# Brevo wants semicolon-separated CSV. If your file uses commas (most do),# convert it on the fly so you don't have to edit the source spreadsheet.def to_brevo_csv(path: str) -> str: with open(path, newline="", encoding="utf-8") as f: reader = csv.reader(f) # default: comma-separated out = io.StringIO() writer = csv.writer(out, delimiter=";") for row in reader: writer.writerow(row) return out.getvalue()
body = { "fileBody": to_brevo_csv(INPUT_FILE), "listIds": [LIST_ID], "updateExistingContacts": True, "emptyContactsAttributes": False, "emailBlacklist": False, "smsBlacklist": False,}
resp = requests.post( "https://api.brevo.com/v3/contacts/import", json=body, headers={"api-key": API_KEY, "Content-Type": "application/json"}, timeout=60,)
if resp.status_code != 202: print(f"Import failed: {resp.status_code} {resp.text}") sys.exit(1)
process_id = resp.json()["processId"]print(f"Import accepted. Process ID: {process_id}")شغّله:
python import_csv_to_brevo.py contacts.csvالصف الأول من ملف CSV هو رأس الجدول. تُربط أسماء الأعمدة بسمات Brevo عبر مطابقة دقيقة بأحرف كبيرة: EMAIL، FIRSTNAME، LASTNAME، بالإضافة إلى أي سمة مخصصة عرّفتها. EMAIL إلزامي.
EMAIL,FIRSTNAME,LASTNAME,COMPANY,CITYهذه هي العملية كاملة. يعود السكريبت في غضون ثانية؛ الاستيراد الفعلي يعمل من جانب الخادم ويستغرق من بضع ثوانٍ إلى بضع دقائق حسب الحجم.
ملفات Excel: ثلاثة مسارات قابلة للتطبيق
نقطة نهاية الاستيراد في Brevo لا تقرأ .xlsx مباشرة، لذلك لديك ثلاثة خيارات حقيقية حسب المكان الذي يحتاج العمل فيه أن يجري:
- شغّل ماكرو VBA داخل المصنّف. مزامنة بنقرة واحدة من Excel نفسه، بدون سكريبت خارجي. هذه الإجابة الصحيحة عندما يكون الملف على سطح المكتب ويريد فريقك زراً على الورقة. الكود الكامل في دليل ماكرو VBA من Excel إلى Brevo.
- Office Scripts + Power Automate. إذا كان المصنّف في OneDrive/SharePoint وأردت مزامنة مجدولة بدون إشراف. مغطى أيضاً في دليل Excel.
- حوّل
.xlsxإلى CSV من سكريبت. ما يغطيه باقي هذا القسم. الأفضل إذا كنت تشغّل بالفعل Python أو Node على خادم وتحتاج فقط إلى سحب مصنّف مرة واحدة في اليوم.
للخيار 3، يجعل pandas الأمر سطراً واحداً:
# pip install pandas openpyxlimport pandas as pd
def xlsx_to_brevo_csv(path: str, sheet_name: str | int = 0) -> str: df = pd.read_excel(path, sheet_name=sheet_name) return df.to_csv(sep=";", index=False)
body["fileBody"] = xlsx_to_brevo_csv("contacts.xlsx")إذا لم تكن تريد pandas كاعتمادية، فإن openpyxl وحده يعمل:
from openpyxl import load_workbookimport csv, io
def xlsx_to_brevo_csv(path: str) -> str: wb = load_workbook(path, read_only=True) ws = wb.active out = io.StringIO() writer = csv.writer(out, delimiter=";") for row in ws.iter_rows(values_only=True): writer.writerow(["" if v is None else v for v in row]) return out.getvalue()Node.js: نفس المهمة، SDK رسمي
ينشر Brevo @getbrevo/brevo لـ Node. يتعامل مع المصادقة وإعادة المحاولة وشكل الطلب المُكتب:
// npm install @getbrevo/brevoimport fs from 'node:fs/promises';import { BrevoClient } from '@getbrevo/brevo';
const API_KEY = process.env.BREVO_API_KEY;const LIST_ID = 42;const INPUT_FILE = process.argv[2] ?? 'contacts.csv';
const client = new BrevoClient({ apiKey: API_KEY });
// Read the CSV and convert commas to semicolons if neededconst raw = await fs.readFile(INPUT_FILE, 'utf-8');const csv = raw.includes(';') ? raw : raw.replace(/,/g, ';');
const result = await client.contacts.importContacts({ fileBody: csv, listIds: [LIST_ID], updateExistingContacts: true, emptyContactsAttributes: false,});
console.log(`Import accepted. Process ID: ${result.processId}`);أو، إذا لم ترد SDK، يعمل fetch العادي بنفس الطريقة:
const resp = await fetch('https://api.brevo.com/v3/contacts/import', { method: 'POST', headers: { 'api-key': process.env.BREVO_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ fileBody: csv, listIds: [42], updateExistingContacts: true, }),});
if (resp.status !== 202) { throw new Error(`Import failed: ${resp.status} ${await resp.text()}`);}
const { processId } = await resp.json();console.log(`Process ID: ${processId}`);cURL: سطر واحد للاستيرادات المخصصة
عندما تريد فقط اختبار نقطة النهاية أو دفع ملف صغير يدوياً:
# Convert commas to semicolons, then send the file inlinecsv=$(sed 's/,/;/g' contacts.csv | jq -Rs .)
curl -X POST https://api.brevo.com/v3/contacts/import \ -H "api-key: $BREVO_API_KEY" \ -H "Content-Type: application/json" \ -d "{\"fileBody\": $csv, \"listIds\": [42], \"updateExistingContacts\": true}"jq -Rs . يبتلع الملف بأكمله ويقوم بـ JSON-escape له. يوفر عليك الـ escape اليدوي للأسطر الجديدة وعلامات الاقتباس.
الملفات الأكبر من 10 ميجابايت: استخدم fileUrl
fileBody محدود بـ 10 ميجابايت. إذا كان dump جهات الاتصال الخاص بك أكبر، استضف الملف في URL يمكن لـ Brevo جلبه ومرره:
body = { "fileUrl": "https://files.example.com/contacts/2026-04-30.csv", "listIds": [42], "updateExistingContacts": True,}أي شيء يمكن الوصول إليه عبر URL HTTPS عام يعمل: روابط S3 الموقّعة مسبقاً، GCS، مضيفك الثابت الخاص، وحتى رابط GitHub raw للاستيرادات لمرة واحدة. يجلب Brevo الملف من URL الخاص بك، ثم يشغّل الاستيراد. الصيغ المقبولة: .csv، .txt، .json.
أرسل JSON بدلاً من CSV
إذا كنت تسحب جهات الاتصال بالفعل من قاعدة بيانات، فلا تحتاج إلى المرور عبر CSV. أرسلها مباشرة كـ JSON:
body = { "jsonBody": [ { "attributes": { "FIRSTNAME": "Jane", "LASTNAME": "Doe", "COMPANY": "Acme", }, }, { "attributes": { "FIRSTNAME": "John", "LASTNAME": "Smith", "COMPANY": "Globex", }, }, ], "listIds": [42],}نفس حد الـ 10 ميجابايت. نفس السلوك غير المتزامن.
استطلاع الإكمال
الاستيراد غير متزامن. الـ processId هو مقبضك لتتبعه. هناك نقطة نهاية منفصلة للتحقق من حالة العملية:
def wait_for_import(process_id: int, timeout_s: int = 600) -> dict: import time deadline = time.time() + timeout_s while time.time() < deadline: r = requests.get( f"https://api.brevo.com/v3/processes/{process_id}", headers={"api-key": API_KEY}, timeout=30, ) r.raise_for_status() status = r.json() if status["status"] in ("completed", "failed"): return status time.sleep(5) raise TimeoutError(f"Import {process_id} did not finish in {timeout_s}s")
result = wait_for_import(process_id)print(f"Import {result['status']}: {result}")للمهام الطويلة، النمط الأنظف هو notifyUrl: مرر نقطة نهاية HTTPS سيقوم Brevo بإرسال POST إليها عند انتهاء الاستيراد، وتخطّ الاستطلاع.
body["notifyUrl"] = "https://your-app.example.com/webhooks/brevo-import"الأخطاء الشائعة وكيفية إصلاحها
400 Bad Request بدون سبب واضح. غالباً ما تكون فواصل منقوطة مقابل فواصل في CSV. يتوقع استيراد Brevo ; وليس ,. تحقق مرة أخرى من fileBody بعد خطوة التحويل.
قيم السمات المخصصة تختفي. السمة غير موجودة في حسابك. أنشئها تحت Contacts → Settings → Contact attributes قبل الاستيراد، أو استخدم Attributes API لإنشائها كجزء من سكريبتك.
401 Unauthorized. اسم رأس خاطئ. إنه api-key (أحرف صغيرة، مع شرطة)، وليس Authorization أو X-API-Key.
الاستيراد “نجح” لكن جهات الاتصال لم تظهر في القائمة. تحقق من أن listIds يحتوي على القائمة الصحيحة. أيضاً: إذا كان updateExistingContacts false وكانت جهات الاتصال موجودة بالفعل، يتخطاها Brevo بصمت بدلاً من إعادة إضافتها إلى القائمة.
بعض الصفوف تم استيرادها، وبعضها لم يتم. يرسل لك Brevo تقرير أخطاء لكل صف بعد انتهاء الاستيراد (إلا إذا ضبطت disableNotification: true). يخبرك التقرير بالصفوف التي بها رسائل بريد إلكتروني سيئة، أو حقول مطلوبة مفقودة، أو مشكلات في التنسيق.
متى تستخدم سكريبت ومتى تستخدم الواجهة
الواجهة جيدة للاستيرادات لمرة واحدة بأقل من ألف صف. يفوز السكريبت بمجرد أن:
- تستورد أكثر من مرة (مثل التصدير الأسبوعي من CRM)
- تحتاج بيانات المصدر إلى تنظيف قبل أن تصل إلى Brevo (إزالة التكرار، تنسيق أرقام الهواتف، تقسيم الأسماء الكاملة إلى أسماء أولى/أخيرة)
- تريد أن يعمل وفق جدول زمني دون أن ينقر أحد على الأزرار
- الملف أكبر من منطقة راحة الواجهة
غلّف السكريبت أعلاه في cron job أو GitHub Action ولديك مزامنة جهات اتصال آلية. تُظهر المنشور التالي في هذه السلسلة كيفية القيام بنفس الشيء مباشرة من Google Sheet باستخدام Apps Script. لا حاجة لخادم.