วิธีนำเข้ารายชื่อจาก CSV หรือ Excel ไปยัง Brevo ด้วยสคริปต์ (Python, Node.js, cURL)

นำเข้ารายชื่อจำนวนมากจากไฟล์ CSV หรือ Excel ไปยัง Brevo ผ่าน import-contacts API พร้อมสคริปต์ Python และ Node.js ที่ใช้งานได้ทันที, cURL หนึ่งบรรทัด, การจัดการข้อผิดพลาด และเคล็ดลับสำหรับไฟล์ใหญ่กว่า 10 MB

Featured image for article: วิธีนำเข้ารายชื่อจาก CSV หรือ Excel ไปยัง Brevo ด้วยสคริปต์ (Python, Node.js, cURL)

ถ้าคุณเคยต้องส่งรายชื่อหลายพันรายชื่อจากสเปรดชีตเข้า Brevo คงคุ้นเคยกับขั้นตอนผ่าน UI: เลือกไฟล์ จับคู่คอลัมน์ เลือก list รอ แล้วทำซ้ำ ซึ่งน่าเบื่อพอสมควร สคริปต์ทำงานเดียวกันได้ในไม่กี่วินาที และที่สำคัญกว่านั้น สามารถรันซ้ำตามตารางเวลา หลังการ export จากฐานข้อมูล หรือทุกครั้งที่ CRM ของคุณส่ง CSV ใหม่ออกมา

คู่มือนี้ครอบคลุม API endpoint ที่ Brevo เตรียมไว้สำหรับงานนี้โดยเฉพาะ พร้อมสคริปต์ใช้งานได้จริงทั้ง Python, Node.js และ cURL หนึ่งบรรทัด ทุกอย่างในคู่มือนี้เรียกใช้ POST /v3/contacts/import

ภาพรวมของ endpoint

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

ก่อนเขียนโค้ดมีหลายจุดที่ควรรู้:

  • Body จะเป็น CSV (fileBody), JSON (jsonBody) หรือ URL ภายนอก (fileUrl) ก็ได้ เลือกอย่างใดอย่างหนึ่ง รูปแบบ CSV ใช้ semicolon (;) คั่นคอลัมน์ ไม่ใช่ comma นี่เป็นจุดพลาดที่พบบ่อย
  • fileBody และ jsonBody จำกัดที่ 10 MB Brevo แนะนำให้อยู่ราว 8 MB เพื่อความปลอดภัย ถ้าใหญ่กว่านี้ให้อัปโหลดไฟล์ขึ้น S3, GCS หรือ HTTPS host ใดก็ได้ แล้วส่ง fileUrl แทน
  • Custom attribute ที่ไม่มีอยู่ในบัญชีของคุณจะถูกข้ามไปเงียบๆ สร้าง attribute ใน UI ของ Brevo (หรือผ่าน Attributes API) ก่อนนำเข้าแถวที่ใช้ attribute นั้น ไม่อย่างนั้นข้อมูลจะหายไปเฉยๆ
  • updateExistingContacts ค่าเริ่มต้นคือ true ถ้าตั้งเป็น false Brevo จะข้ามรายชื่อที่มีอีเมลซ้ำอยู่แล้ว เหมาะกับงานที่ต้องการ เพิ่มเฉพาะรายใหม่
  • emptyContactsAttributes ควบคุมว่าเซลล์ว่างใน CSV จะลบค่าเดิมหรือไม่ ค่าเริ่มต้น false (เซลล์ว่างถูกข้าม) ตั้งเป็น true ถ้า CSV เป็นแหล่งข้อมูลหลักและต้องการให้ค่าว่างล้างข้อมูลเก่าออก

ขอ API key

เข้า Brevo → Settings → SMTP & API → API Keys → สร้าง key ใหม่ จะมีรูปแบบ xkeysib-... ส่งเป็น HTTP header api-key กับทุก request ปฏิบัติกับ key เหมือนรหัสผ่าน เพราะมีสิทธิ์อ่านและเขียนเต็มในบัญชี

แนวทางที่ดี: เก็บไว้ใน environment variable เพื่อไม่ให้หลุดเข้าไปอยู่ใน source code

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

Python: อ่าน CSV แล้วส่งเข้า Brevo

นี่คือสคริปต์ที่เรียบง่ายที่สุด: อ่าน CSV ในเครื่องด้วย standard library แล้วส่ง body ตรงเข้า Brevo ไม่ต้องใช้ third-party 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 คือ header ชื่อคอลัมน์จะถูกแมปเข้ากับ attribute ของ Brevo ด้วย ตัวพิมพ์ใหญ่และต้องตรงกันทุกตัวอักษร: EMAIL, FIRSTNAME, LASTNAME พร้อม custom attribute ใดก็ตามที่คุณนิยามไว้ EMAIL เป็น field บังคับ

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

นี่คือทั้งหมดของ flow สคริปต์คืนค่าภายในเสี้ยววินาที ส่วนการ import จริงทำงานฝั่ง server ใช้เวลาตั้งแต่ไม่กี่วินาทีถึงไม่กี่นาทีตามปริมาณข้อมูล

ไฟล์ Excel: สามทางเลือกที่ใช้ได้จริง

Import endpoint ของ Brevo อ่าน .xlsx โดยตรงไม่ได้ จึงมีสามทางเลือก ขึ้นกับว่างานต้องอยู่ที่ใด:

  1. รัน VBA macro ภายในเวิร์กบุ๊ก ซิงค์ในคลิกเดียวจาก Excel โดยไม่ต้องใช้สคริปต์ภายนอก คำตอบที่ใช่เมื่อไฟล์อยู่บนเดสก์ท็อปและทีมต้องการปุ่มบนชีต โค้ดเต็มอยู่ใน คู่มือ VBA macro สำหรับ Excel ไป Brevo
  2. Office Scripts + Power Automate ใช้เมื่อเวิร์กบุ๊กอยู่ใน OneDrive/SharePoint และต้องการซิงค์ตามตารางเวลาอัตโนมัติ มีอธิบายใน คู่มือ Excel เช่นกัน
  3. แปลง .xlsx เป็น CSV ด้วยสคริปต์ เป็นเนื้อหาที่ส่วนที่เหลือของหัวข้อนี้ครอบคลุม เหมาะที่สุดถ้าคุณรัน Python หรือ Node อยู่บน server แล้ว แค่ต้องการดึงเวิร์กบุ๊กเข้ามาวันละครั้ง

สำหรับทางเลือกที่ 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 เป็น dependency ใช้ 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 ตัวนี้ดูแลเรื่อง auth, retry และโครงสร้าง request แบบมี type ให้ครบ:

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: หนึ่งบรรทัดสำหรับ import เฉพาะกิจ

เมื่อแค่อยากทดสอบ endpoint หรือดันไฟล์เล็กๆ เข้าไปด้วยมือ:

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 . ดูดไฟล์ทั้งก้อนแล้ว escape เป็น JSON ให้ ช่วยให้ไม่ต้องมาจัดการขึ้นบรรทัดและเครื่องหมายคำพูดเอง

ไฟล์ใหญ่กว่า 10 MB: ใช้ fileUrl

fileBody ถูกจำกัดที่ 10 MB ถ้าข้อมูลรายชื่อใหญ่กว่านั้น ให้โฮสต์ไฟล์ไว้ที่ URL ที่ Brevo ดึงได้ แล้วส่งให้:

body = {
"fileUrl": "https://files.example.com/contacts/2026-04-30.csv",
"listIds": [42],
"updateExistingContacts": True,
}

อะไรก็ตามที่เข้าถึงได้ผ่าน HTTPS URL สาธารณะใช้งานได้: pre-signed S3 URL, GCS, static host ของคุณเอง หรือ GitHub raw URL สำหรับ import ครั้งเดียว Brevo จะดึงไฟล์จาก URL ของคุณแล้วเริ่ม import รูปแบบที่รองรับ: .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 MB เท่ากัน พฤติกรรม asynchronous เหมือนกัน

การ poll สถานะให้เสร็จสมบูรณ์

การ import ทำงานแบบ asynchronous processId คือตัวอ้างอิงที่ใช้ติดตาม มี endpoint แยกสำหรับเช็กสถานะ process:

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 endpoint ให้ Brevo ส่ง POST กลับมาเมื่อ import เสร็จ ไม่ต้อง poll เลย

body["notifyUrl"] = "https://your-app.example.com/webhooks/brevo-import"

ข้อผิดพลาดที่พบบ่อยและวิธีแก้

400 Bad Request ที่ดูไม่ออกว่าเพราะอะไร เกือบทุกครั้งคือ semicolon กับ comma ใน CSV Brevo import คาดหวัง ; ไม่ใช่ , ให้ตรวจ fileBody หลังขั้นตอนการแปลงอีกที

ค่า custom attribute หายไป attribute นั้นไม่มีในบัญชีของคุณ สร้างที่ Contacts → Settings → Contact attributes ก่อน import หรือใช้ Attributes API สร้างเป็นส่วนหนึ่งของสคริปต์

401 Unauthorized ชื่อ header ผิด ต้องเป็น api-key (ตัวพิมพ์เล็ก มีขีด) ไม่ใช่ Authorization หรือ X-API-Key

Import “สำเร็จ” แต่รายชื่อไม่ปรากฏใน list ตรวจว่า listIds ระบุ list ที่ถูกต้อง และถ้า updateExistingContacts เป็น false กับรายชื่อที่มีอยู่แล้ว Brevo จะข้ามแบบเงียบๆ ไม่เพิ่มเข้า list ซ้ำ

บางแถว import ผ่าน บางแถวไม่ผ่าน Brevo ส่งรายงานข้อผิดพลาดรายแถวให้ทางอีเมลหลัง import เสร็จ (ยกเว้นตั้ง disableNotification: true) รายงานบอกได้ว่าแถวไหนมีอีเมลผิด ขาด field บังคับ หรือมีปัญหา format

เมื่อไรควรใช้สคริปต์ เมื่อไรใช้ UI

UI โอเคสำหรับ import ครั้งเดียวต่ำกว่าหนึ่งพันแถว สคริปต์ชนะทันทีเมื่อ:

  • คุณ import มากกว่าหนึ่งครั้ง (เช่น export รายสัปดาห์จาก CRM)
  • ข้อมูลต้นทางต้องทำความสะอาดก่อนเข้า Brevo (ลบซ้ำ, จัดรูปแบบเบอร์โทร, แยกชื่อเต็มเป็นชื่อกับนามสกุล)
  • ต้องการให้รันตามตารางเวลาโดยไม่มีใครต้องคลิก
  • ไฟล์ใหญ่เกินกว่า UI จะรองรับได้สบายๆ

นำสคริปต์ข้างต้นไปใส่ใน cron job หรือ GitHub Action ก็ได้ระบบซิงค์รายชื่ออัตโนมัติ บทความถัดไปในซีรีส์นี้แสดงวิธีทำเรื่องเดียวกันตรงจาก Google Sheet ด้วย Apps Script โดยไม่ต้องใช้ server

อ่านเพิ่มเติม

Frequently Asked Questions

Brevo มี API endpoint อะไรสำหรับนำเข้ารายชื่อจำนวนมาก
POST https://api.brevo.com/v3/contacts/import โดยรับเนื้อหา CSV (fileBody), URL ของไฟล์ภายนอก (fileUrl) หรือ JSON array (jsonBody) ก็ได้ endpoint ทำงานแบบ asynchronous ตอบกลับ processId ทันทีและประมวลผลการนำเข้าในเบื้องหลัง
ขนาดไฟล์สูงสุดที่รองรับเป็นเท่าไร
10 MB สำหรับ CSV inline (fileBody) หรือ JSON (jsonBody) ถ้าไฟล์ใหญ่กว่านั้นให้โฮสต์ไฟล์ไว้ที่ตำแหน่งที่ Brevo เข้าถึงได้และส่ง URL ผ่าน fileUrl แทน วิธีนี้ไม่มีข้อจำกัดเรื่องขนาดในเอกสาร
นำเข้าไฟล์ .xlsx ได้อย่างไร
Import API ของ Brevo รองรับเฉพาะ .csv, .txt และ .json ต้องแปลง .xlsx ให้เป็น .csv ก่อน ซึ่ง pandas, openpyxl หรือ LibreOffice headless ทำได้ในบรรทัดเดียว สคริปต์ในคู่มือนี้รวมขั้นตอนแปลง xlsx เป็น csv ไว้ด้วย
การนำเข้าจะเขียนทับรายชื่อเดิมหรือไม่
ค่าเริ่มต้นคือเขียนทับ updateExistingContacts ตั้งเป็น true และจับคู่ด้วยอีเมล ถ้าตั้งเป็น false จะข้ามรายชื่อที่มีอยู่แล้ว ถ้าตั้ง emptyContactsAttributes เป็น true เซลล์ว่างใน CSV จะลบค่าเดิมออก (ค่าเริ่มต้นคือไม่สนใจเซลล์ว่าง)
เริ่มต้นฟรีกับ Brevo