วิธีนำเข้ารายชื่อจาก CSV หรือ Excel ไปยัง Brevo ด้วยสคริปต์ (Python, Node.js, cURL)
นำเข้ารายชื่อจำนวนมากจากไฟล์ CSV หรือ Excel ไปยัง Brevo ผ่าน import-contacts API พร้อมสคริปต์ Python และ Node.js ที่ใช้งานได้ทันที, cURL หนึ่งบรรทัด, การจัดการข้อผิดพลาด และเคล็ดลับสำหรับไฟล์ใหญ่กว่า 10 MB
ถ้าคุณเคยต้องส่งรายชื่อหลายพันรายชื่อจากสเปรดชีตเข้า 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/importContent-Type: application/jsonapi-key: YOUR_API_KEY
{ "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ถ้าตั้งเป็นfalseBrevo จะข้ามรายชื่อที่มีอีเมลซ้ำอยู่แล้ว เหมาะกับงานที่ต้องการ เพิ่มเฉพาะรายใหม่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
export BREVO_API_KEY="xkeysib-..."Python: อ่าน CSV แล้วส่งเข้า Brevo
นี่คือสคริปต์ที่เรียบง่ายที่สุด: อ่าน CSV ในเครื่องด้วย standard library แล้วส่ง body ตรงเข้า Brevo ไม่ต้องใช้ third-party 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 คือ header ชื่อคอลัมน์จะถูกแมปเข้ากับ attribute ของ Brevo ด้วย ตัวพิมพ์ใหญ่และต้องตรงกันทุกตัวอักษร: EMAIL, FIRSTNAME, LASTNAME พร้อม custom attribute ใดก็ตามที่คุณนิยามไว้ EMAIL เป็น field บังคับ
EMAIL,FIRSTNAME,LASTNAME,COMPANY,CITYนี่คือทั้งหมดของ flow สคริปต์คืนค่าภายในเสี้ยววินาที ส่วนการ import จริงทำงานฝั่ง server ใช้เวลาตั้งแต่ไม่กี่วินาทีถึงไม่กี่นาทีตามปริมาณข้อมูล
ไฟล์ Excel: สามทางเลือกที่ใช้ได้จริง
Import endpoint ของ Brevo อ่าน .xlsx โดยตรงไม่ได้ จึงมีสามทางเลือก ขึ้นกับว่างานต้องอยู่ที่ใด:
- รัน VBA macro ภายในเวิร์กบุ๊ก ซิงค์ในคลิกเดียวจาก Excel โดยไม่ต้องใช้สคริปต์ภายนอก คำตอบที่ใช่เมื่อไฟล์อยู่บนเดสก์ท็อปและทีมต้องการปุ่มบนชีต โค้ดเต็มอยู่ใน คู่มือ VBA macro สำหรับ Excel ไป Brevo
- Office Scripts + Power Automate ใช้เมื่อเวิร์กบุ๊กอยู่ใน OneDrive/SharePoint และต้องการซิงค์ตามตารางเวลาอัตโนมัติ มีอธิบายใน คู่มือ Excel เช่นกัน
- แปลง
.xlsxเป็น CSV ด้วยสคริปต์ เป็นเนื้อหาที่ส่วนที่เหลือของหัวข้อนี้ครอบคลุม เหมาะที่สุดถ้าคุณรัน Python หรือ Node อยู่บน server แล้ว แค่ต้องการดึงเวิร์กบุ๊กเข้ามาวันละครั้ง
สำหรับทางเลือกที่ 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 เป็น dependency ใช้ 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 ตัวนี้ดูแลเรื่อง auth, retry และโครงสร้าง request แบบมี type ให้ครบ:
// 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: หนึ่งบรรทัดสำหรับ import เฉพาะกิจ
เมื่อแค่อยากทดสอบ endpoint หรือดันไฟล์เล็กๆ เข้าไปด้วยมือ:
# 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 . ดูดไฟล์ทั้งก้อนแล้ว 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": [ { "attributes": { "FIRSTNAME": "Jane", "LASTNAME": "Doe", "COMPANY": "Acme", }, }, { "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