كيفية استيراد جهات اتصال CSV أو Excel إلى Brevo باستخدام سكريبت (Python و Node.js و cURL)

استيراد جماعي لجهات الاتصال من ملف CSV أو Excel إلى Brevo باستخدام واجهة import-contacts API. يتضمن سكريبتات جاهزة للتشغيل بلغة Python و Node.js، وأمر cURL من سطر واحد، ومعالجة الأخطاء، ونصائح للملفات الأكبر من 10 ميجابايت.

Featured image for article: كيفية استيراد جهات اتصال CSV أو Excel إلى Brevo باستخدام سكريبت (Python و Node.js و cURL)

إذا احتجت يوماً إلى دفع بضعة آلاف من جهات الاتصال من جدول بيانات إلى Brevo، فإن المسار اليدوي عبر الواجهة يصبح مؤلماً بسرعة: اختر الملف، اربط الأعمدة، اختر القائمة، انتظر، كرر. السكريبت يقوم بنفس المهمة في ثوانٍ، والأهم من ذلك، يمكنك تشغيله مرة أخرى وفق جدول زمني، أو بعد تصدير قاعدة بيانات، أو كلما أخرج CRM الخاص بك CSV جديداً.

يغطي هذا الدليل نقطة نهاية API التي يوفرها Brevo لهذا الغرض بالضبط، مع سكريبتات كاملة وعاملة بـ Python و Node.js وأمر cURL من سطر واحد. كل شيء في هذا الدليل يستخدم POST /v3/contacts/import.

نقطة النهاية بنظرة سريعة

POST https://api.brevo.com/v3/contacts/import
Content-Type: application/json
api-key: YOUR_API_KEY
{
"fileBody": "EMAIL;FIRSTNAME;LASTNAME\n[email protected];Jane;Doe",
"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 في كل طلب. تعامل معه كأنه كلمة مرور، لديه صلاحيات قراءة/كتابة كاملة على حسابك.

نمط نظيف: ضعه في متغير بيئة حتى لا يصل إلى شجرة الكود المصدري الخاصة بك.

Terminal window
export BREVO_API_KEY="xkeysib-..."

Python: اقرأ ملف CSV وادفعه إلى Brevo

هذا أبسط سكريبت ممكن: اقرأ CSV محلياً بالمكتبة القياسية، وأرسل المحتوى مباشرة إلى Brevo. لا حاجة لأي SDK تابع لطرف ثالث بخلاف requests.

import_csv_to_brevo.py
import csv
import io
import os
import sys
import requests
API_KEY = os.environ["BREVO_API_KEY"]
LIST_ID = 42 # the Brevo list to add contacts to
INPUT_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}")

شغّله:

Terminal window
python import_csv_to_brevo.py contacts.csv

الصف الأول من ملف CSV هو رأس الجدول. تُربط أسماء الأعمدة بسمات Brevo عبر مطابقة دقيقة بأحرف كبيرة: EMAIL، FIRSTNAME، LASTNAME، بالإضافة إلى أي سمة مخصصة عرّفتها. EMAIL إلزامي.

EMAIL,FIRSTNAME,LASTNAME,COMPANY,CITY
[email protected],Jane,Doe,Acme,Berlin
[email protected],John,Smith,Globex,Paris

هذه هي العملية كاملة. يعود السكريبت في غضون ثانية؛ الاستيراد الفعلي يعمل من جانب الخادم ويستغرق من بضع ثوانٍ إلى بضع دقائق حسب الحجم.

ملفات Excel: ثلاثة مسارات قابلة للتطبيق

نقطة نهاية الاستيراد في Brevo لا تقرأ .xlsx مباشرة، لذلك لديك ثلاثة خيارات حقيقية حسب المكان الذي يحتاج العمل فيه أن يجري:

  1. شغّل ماكرو VBA داخل المصنّف. مزامنة بنقرة واحدة من Excel نفسه، بدون سكريبت خارجي. هذه الإجابة الصحيحة عندما يكون الملف على سطح المكتب ويريد فريقك زراً على الورقة. الكود الكامل في دليل ماكرو VBA من Excel إلى Brevo.
  2. Office Scripts + Power Automate. إذا كان المصنّف في OneDrive/SharePoint وأردت مزامنة مجدولة بدون إشراف. مغطى أيضاً في دليل Excel.
  3. حوّل .xlsx إلى CSV من سكريبت. ما يغطيه باقي هذا القسم. الأفضل إذا كنت تشغّل بالفعل Python أو Node على خادم وتحتاج فقط إلى سحب مصنّف مرة واحدة في اليوم.

للخيار 3، يجعل pandas الأمر سطراً واحداً:

# pip install pandas openpyxl
import 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_workbook
import 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. يتعامل مع المصادقة وإعادة المحاولة وشكل الطلب المُكتب:

import-csv-to-brevo.mjs
// npm install @getbrevo/brevo
import 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 needed
const 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: سطر واحد للاستيرادات المخصصة

عندما تريد فقط اختبار نقطة النهاية أو دفع ملف صغير يدوياً:

Terminal window
# Convert commas to semicolons, then send the file inline
csv=$(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": [
{
"email": "[email protected]",
"attributes": {
"FIRSTNAME": "Jane",
"LASTNAME": "Doe",
"COMPANY": "Acme",
},
},
{
"email": "[email protected]",
"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. لا حاجة لخادم.

قراءات إضافية

Frequently Asked Questions

ما هو نقطة نهاية Brevo API للاستيراد الجماعي لجهات الاتصال؟
POST https://api.brevo.com/v3/contacts/import. تقبل محتوى CSV (fileBody) أو رابط ملف بعيد (fileUrl) أو مصفوفة JSON (jsonBody). نقطة النهاية غير متزامنة، تُرجع processId فوراً وتُكمل الاستيراد في الخلفية.
ما هو الحد الأقصى لحجم الملف؟
10 ميجابايت لـ CSV المضمّن (fileBody) أو JSON (jsonBody). للملفات الأكبر، استضف الملف في مكان يمكن الوصول إليه ومرر رابطه عبر fileUrl. هذا المسار ليس له حد حجم موثّق.
كيف أستورد ملف .xlsx؟
واجهة استيراد Brevo تقبل فقط .csv أو .txt أو .json. حوّل .xlsx إلى .csv أولاً. يمكن لـ pandas أو openpyxl أو LibreOffice headless القيام بذلك في سطر واحد. السكريبت في هذا الدليل يتضمن تحويل xlsx إلى csv.
هل ستستبدل جهات الاتصال الموجودة؟
بشكل افتراضي، نعم. updateExistingContacts افتراضياً true ويطابق على البريد الإلكتروني. اضبطه على false لتخطي جهات الاتصال الموجودة بالفعل. اضبط emptyContactsAttributes على true إذا أردت أن تمسح خلايا CSV الفارغة القيم الموجودة (وإلا فسيتم تجاهل الخلايا الفارغة).
ابدأ مجانًا مع Brevo