Cách đẩy danh bạ Excel vào Brevo bằng macro VBA (và lựa chọn Office Scripts)
Một macro VBA hoạt động được, đăng danh bạ từ một sheet Excel lên API của Brevo chỉ với một cú click. Cộng với khi nào nên dùng Office Scripts + Power Automate, và sự đánh đổi so với cấu hình Google Apps Script.
Bạn có danh bạ trong Excel và muốn chúng vào Brevo. Câu trả lời nhanh và bẩn là lưu tệp dưới dạng .csv và import nó, ổn cho một lần. Cho bất cứ thứ gì bạn làm lặp đi lặp lại (chuyển giao bán hàng hàng tuần, một workbook mà nhóm bạn cập nhật hàng ngày, một danh sách đối tác được làm mới), bạn muốn một nút ngay bên trong Excel để thực hiện đồng bộ.
Hướng dẫn này bao gồm hai lộ trình thực sự có ý nghĩa cho việc đó:
- Một macro VBA nhúng trong workbook: không license, không cloud, hoạt động offline, chạy ngay khoảnh khắc người dùng bấm nút. Câu trả lời đúng cho khoảng 80% các trường hợp “Excel sang Brevo”.
- Office Scripts + Power Automate: TypeScript thay vì VBA, chạy trên cloud, hỗ trợ trigger theo lịch. Câu trả lời đúng nếu workbook nằm trên OneDrive/SharePoint và bạn muốn đồng bộ không cần giám sát, nhưng hãy chú ý đến license của Power Automate.
Nếu bạn tìm phiên bản tương đương cho Google Sheets, xem bài viết đồng hành về Apps Script. Và nếu bạn chỉ muốn import CSV một lần từ một script trên laptop, hướng dẫn import CSV có các phiên bản Python, Node.js và cURL.
Macro làm gì
Khi người dùng bấm nút “Sync to Brevo” trên sheet:
- Đọc mỗi hàng từ worksheet đang hoạt động (hàng header trước, mỗi hàng một danh bạ).
- Xây dựng mảng JSON theo hình dạng cho tham số
jsonBodycủa Brevo. - POST nó đến
https://api.brevo.com/v3/contacts/importvới API key được lưu trong workbook. - Hiển thị message box với kết quả.
Vậy thôi. Khoảng 120 dòng VBA. Bên dưới là module đầy đủ, hoạt động được.
Bố cục sheet mà macro mong đợi
| firstName | lastName | company | city | |
|---|---|---|---|---|
| [email protected] | Jane | Doe | Acme | Berlin |
| [email protected] | John | Smith | Globex | Paris |
email là bắt buộc. Mỗi cột khác trở thành một contact attribute của Brevo, được ánh xạ bằng header cột (đã viết hoa) tới tên attribute. Vậy firstName → FIRSTNAME, company → COMPANY. Custom attributes (bất cứ thứ gì ngoài bộ chuẩn) cần tồn tại trong tài khoản Brevo của bạn trước. Hãy định nghĩa chúng trong Contacts → Settings → Contact attributes.
Bước 1: Mở trình soạn thảo VBA
Trong Excel: nhấn Alt + F11. Trình soạn thảo VBA mở. Trong khung Project bên trái, click chuột phải vào workbook của bạn và chọn Insert → Module. Một Module1 trống xuất hiện.
Bước 2: Dán macro đầy đủ
Thay nội dung của Module1 bằng đoạn này:
' ===========================================================================' Brevo contact sync for Excel' Reads the active sheet's rows and POSTs them to Brevo's import API.' ===========================================================================Option Explicit
Private Const BREVO_API_BASE As String = "https://api.brevo.com/v3"Private Const BREVO_LIST_ID As Long = 42 ' <- your Brevo list IDPrivate Const BATCH_SIZE As Long = 1000
' --- Public entry points (the ones you assign to ribbon buttons) -----------
Public Sub SyncSheetToBrevo() Dim apiKey As String apiKey = GetApiKey() If apiKey = "" Then MsgBox "No API key configured. Run ConfigureApiKey first.", _ vbExclamation, "Brevo Sync" Exit Sub End If
Dim ws As Worksheet Set ws = ActiveSheet
Dim emailCol As Long emailCol = FindEmailColumn(ws) If emailCol = 0 Then MsgBox "Sheet must have an 'email' column in row 1.", _ vbExclamation, "Brevo Sync" Exit Sub End If
Dim lastRow As Long, lastCol As Long lastRow = ws.Cells(ws.Rows.Count, emailCol).End(xlUp).Row lastCol = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column If lastRow < 2 Then MsgBox "No contact rows found.", vbInformation, "Brevo Sync" Exit Sub End If
Dim contacts As Collection Set contacts = New Collection
Dim r As Long, c As Long For r = 2 To lastRow Dim email As String email = LCase(Trim(CStr(ws.Cells(r, emailCol).Value))) If email <> "" And InStr(email, "@") > 0 Then Dim json As String json = "{""email"":""" & EscapeJson(email) & """,""attributes"":{"
Dim attrFirst As Boolean attrFirst = True For c = 1 To lastCol If c <> emailCol Then Dim val As String val = CStr(ws.Cells(r, c).Value) If val <> "" Then If Not attrFirst Then json = json & "," Dim attrName As String attrName = UCase(Trim(CStr(ws.Cells(1, c).Value))) json = json & """" & attrName & """:""" & EscapeJson(val) & """" attrFirst = False End If End If Next c
json = json & "}}" contacts.Add json End If Next r
If contacts.Count = 0 Then MsgBox "No valid contact rows found.", vbInformation, "Brevo Sync" Exit Sub End If
Dim totalSent As Long Dim batchNum As Long Dim okCount As Long, failCount As Long Dim batchStart As Long
For batchStart = 1 To contacts.Count Step BATCH_SIZE batchNum = batchNum + 1 Dim batchEnd As Long batchEnd = batchStart + BATCH_SIZE - 1 If batchEnd > contacts.Count Then batchEnd = contacts.Count
Dim payload As String payload = "{""jsonBody"":[" Dim i As Long For i = batchStart To batchEnd If i > batchStart Then payload = payload & "," payload = payload & contacts(i) Next i payload = payload & "],""listIds"":[" & BREVO_LIST_ID & _ "],""updateExistingContacts"":true,""emptyContactsAttributes"":false}"
Dim ok As Boolean ok = PostToBrevo(apiKey, payload) If ok Then okCount = okCount + 1 totalSent = totalSent + (batchEnd - batchStart + 1) Else failCount = failCount + 1 End If Next batchStart
MsgBox "Sent " & totalSent & " contact(s) in " & batchNum & " batch(es)." _ & vbCrLf & "Successful batches: " & okCount _ & vbCrLf & "Failed batches: " & failCount, _ vbInformation, "Brevo Sync"End Sub
Public Sub ConfigureApiKey() Dim key As String key = InputBox("Paste your Brevo API key (xkeysib-...):", "Brevo API Key") If key = "" Then Exit Sub key = Trim(key) If Left(key, 8) <> "xkeysib-" Then MsgBox "That doesn't look like a Brevo API key (should start with xkeysib-).", _ vbExclamation, "Brevo API Key" Exit Sub End If
On Error Resume Next ThisWorkbook.CustomDocumentProperties("BrevoApiKey").Delete On Error GoTo 0
ThisWorkbook.CustomDocumentProperties.Add _ Name:="BrevoApiKey", _ LinkToContent:=False, _ Type:=msoPropertyTypeString, _ Value:=key
ThisWorkbook.Save MsgBox "API key saved inside the workbook.", vbInformation, "Brevo API Key"End Sub
' --- Private helpers --------------------------------------------------------
Private Function GetApiKey() As String On Error Resume Next GetApiKey = ThisWorkbook.CustomDocumentProperties("BrevoApiKey").Value On Error GoTo 0End Function
Private Function FindEmailColumn(ws As Worksheet) As Long Dim lastCol As Long, c As Long lastCol = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column For c = 1 To lastCol If LCase(Trim(CStr(ws.Cells(1, c).Value))) = "email" Then FindEmailColumn = c Exit Function End If Next c FindEmailColumn = 0End Function
Private Function PostToBrevo(apiKey As String, payload As String) As Boolean Dim http As Object Set http = CreateObject("MSXML2.XMLHTTP") http.Open "POST", BREVO_API_BASE & "/contacts/import", False http.SetRequestHeader "api-key", apiKey http.SetRequestHeader "Content-Type", "application/json" http.SetRequestHeader "Accept", "application/json" http.Send payload PostToBrevo = (http.Status = 202) If Not PostToBrevo Then Debug.Print "Brevo error " & http.Status & ": " & http.responseText End IfEnd Function
Private Function EscapeJson(s As String) As String Dim r As String r = Replace(s, "\", "\\") r = Replace(r, """", "\""") r = Replace(r, vbCrLf, "\n") r = Replace(r, vbLf, "\n") r = Replace(r, vbCr, "\n") r = Replace(r, vbTab, "\t") EscapeJson = rEnd FunctionBước 3: Lưu workbook dưới dạng .xlsm
Macro VBA chỉ tồn tại trong workbook hỗ trợ macro. Save As → chọn Excel Macro-Enabled Workbook (.xlsm). Định dạng .xlsx thuần loại bỏ macro một cách âm thầm. Nhiều người mất code theo cách này lần đầu.
Bước 4: Cấu hình API key của bạn
Chạy ConfigureApiKey một lần. Hoặc:
- Trong trình soạn thảo VBA, click vào bất kỳ vị trí nào bên trong sub
ConfigureApiKeyvà nhấnF5, hoặc - Trong Excel, Developer → Macros, chọn
ConfigureApiKey, Run.
Dán key xkeysib-... của bạn. Macro lưu nó dưới dạng custom document property bên trong chính workbook, không nằm trong source code, không nằm trong registry và đi cùng tệp (vậy hãy lưu ý: nếu bạn email .xlsm cho ai đó, API key đi cùng nó).
Nếu bạn muốn đặt key ở đâu đó bên ngoài workbook, hãy đổi nơi lưu trữ sang Windows registry:
' Replace the body of ConfigureApiKey with:SaveSetting "Brevo", "Sync", "ApiKey", key
' And GetApiKey with:GetApiKey = GetSetting("Brevo", "Sync", "ApiKey", "")SaveSetting/GetSetting ghi vào HKCU\Software\VB and VBA Program Settings\Brevo\Sync (mỗi user, không phải mỗi workbook). Dùng cách này nếu nhiều workbook nên chia sẻ một key, hoặc nếu bạn không muốn key nằm trong tệp.
Bước 5: Thêm nút trên sheet
Đây là điều biến nó thành trải nghiệm một-cú-click cho người dùng phi kỹ thuật.
Insert → Shapes → Rectangle, thả một cái lên sheet, đặt nhãn “Sync to Brevo”. Click chuột phải vào shape → Assign Macro → chọn SyncSheetToBrevo. Xong.
Hoặc, để có UI hoàn thiện hơn, thêm tab ribbon tùy chỉnh qua Office Custom UI Editor. Nhưng với hầu hết các công cụ nội bộ, shape-làm-nút là đủ.
Bước 6: Chạy nó
Bấm nút. Macro đọc các hàng, batch chúng, post mỗi batch đến Brevo và hiển thị message box tóm tắt. Quá trình import của Brevo bất đồng bộ, nên thông báo thành công có nghĩa là “Brevo đã chấp nhận batch”, việc tạo danh bạ thực tế xảy ra ở phía server trong vài giây tiếp theo. Bạn sẽ nhận được email tóm tắt từ Brevo khi nó hoàn tất (trừ khi bạn đặt disableNotification: true).
Cạm bẫy thường gặp
Nút không làm gì cả và không có lỗi. Macro bị tắt. Nhìn vào thanh bảo mật màu vàng ở đầu sheet, click Enable Content. Nếu tổ chức của bạn chặn macro, xem lộ trình trust-center / ký mã bên dưới.
Compile error: User-defined type not defined. Bạn đang dùng Mac Excel, không có MSXML2.XMLHTTP. Mac VBA không thể tạo HTTPS request trực tiếp; hãy dùng lộ trình Office Scripts bên dưới thay thế.
400 Bad Request từ Brevo không có nguyên nhân rõ ràng. Hầu như luôn là một trong các trường hợp: (a) một custom attribute trong sheet chưa tồn tại trong Brevo (tạo nó trước); (b) lỗi escape JSON (dấu nháy hoặc backslash trong giá trị ô không được escape). Hàm EscapeJson trong code xử lý các trường hợp tiêu chuẩn; nếu dữ liệu của bạn có ký tự lạ, log payload ra cửa sổ Immediate (Debug.Print payload) và kiểm tra.
401 Unauthorized. Sai header. Đó là api-key (chữ thường, có dấu gạch nối), không phải Authorization. Macro dùng đúng cái, nhưng nếu bạn copy snippet từ nơi khác, hãy kiểm tra lại.
Excel đứng máy với import lớn. Macro chạy đồng bộ trên thread UI. Với 50.000+ hàng, bạn sẽ thấy Excel treo từ 10-30 giây trong khi xây dựng JSON và chờ Brevo. Hoặc chấp nhận điều đó, hoặc chuyển MSXML2.XMLHTTP sang biến thể async, nhưng ở quy mô đó, bạn nên dùng Power Automate (phần tiếp theo).
Khi VBA không đủ: Office Scripts + Power Automate
VBA không thể đồng bộ cloud theo lịch. Nếu bạn cần:
- Workbook đồng bộ với Brevo mỗi giờ mà không cần ai mở nó
- Workbook nằm trên OneDrive/SharePoint, được chỉnh sửa từ web
- Phòng IT cấm macro desktop
…thì bạn muốn Office Scripts (phiên bản tương đương cloud của Microsoft cho Apps Script) cộng với Power Automate (lớp lập lịch và HTTP của họ).
Phân chia: Office Scripts đọc sheet và trả về dữ liệu danh bạ. Power Automate lấy dữ liệu đó và POST đến Brevo theo trigger.
Office Script (Excel cho web → Automate → New Script):
function main(workbook: ExcelScript.Workbook): {email: string, attributes: Record<string, string>}[] { const sheet = workbook.getActiveWorksheet(); const range = sheet.getUsedRange(); if (!range) return [];
const values = range.getValues() as string[][]; if (values.length < 2) return [];
const headers = values[0].map(h => String(h).trim()); const emailIdx = headers.findIndex(h => h.toLowerCase() === "email"); if (emailIdx === -1) throw new Error("Sheet must have an 'email' column");
const contacts: {email: string, attributes: Record<string, string>}[] = []; for (let r = 1; r < values.length; r++) { const row = values[r]; const email = String(row[emailIdx] ?? "").trim().toLowerCase(); if (!email || !email.includes("@")) continue;
const attributes: Record<string, string> = {}; for (let c = 0; c < headers.length; c++) { if (c === emailIdx) continue; const v = row[c]; if (v === null || v === "") continue; attributes[headers[c].toUpperCase()] = String(v); } contacts.push({ email, attributes }); } return contacts;}Luồng Power Automate:
- Trigger: Recurrence (mỗi 1 giờ), hoặc nút thủ công, hoặc “When a row is modified” nếu bạn muốn đồng bộ theo sự thay đổi.
- Action: Excel Online → Run script: trỏ nó đến workbook của bạn và script ở trên. Lưu giá trị trả về của nó là
contacts. - Action: HTTP (đây là connector Premium, xem ghi chú license bên dưới).
- Method:
POST - URI:
https://api.brevo.com/v3/contacts/import - Headers:
api-key: xkeysib-...,Content-Type: application/json - Body:
{"jsonBody": @{outputs('Run_script')?['body/result']},"listIds": [42],"updateExistingContacts": true}
- Method:
- Action: Condition → nếu mã trạng thái khác 202, gửi cảnh báo Teams/email.
Kiểm tra thực tế về license: action HTTP là một connector Premium của Power Automate. Trên các gói Microsoft 365 Business Basic/Standard, bạn nhận các connector chuẩn nhưng không có Premium. Cách giải quyết rẻ nhất là add-on Power Automate Premium (khoảng 15 USD/người dùng/tháng tại thời điểm viết bài), hoặc chuyển HTTP sang một Azure Function nhỏ mà luồng chuẩn có thể gọi. Nếu bạn đã ở E3/E5 với Premium đi kèm, bạn đã sẵn sàng.
Đây là lý do chính tại sao câu chuyện Apps Script sạch hơn: UrlFetchApp của Apps Script miễn phí và không bị hạn chế, trong khi phiên bản tương đương của Microsoft đặt cuộc gọi mạng sau một tier connector trả phí.
VBA so với Office Scripts so với Apps Script: khi nào chọn cái gì
| Nhu cầu | Lựa chọn tốt nhất |
|---|---|
| Nút một-cú-click trong workbook mà nhóm bạn đã mở hàng ngày | Macro VBA (hướng dẫn này, nửa đầu) |
| Workbook trên OneDrive/SharePoint, tự động đồng bộ hàng giờ | Office Scripts + Power Automate (cần Premium cho HTTP) |
| Chỉ Mac Excel, không thể dùng VBA | Office Scripts + Power Automate |
| Dữ liệu sống trong Google Sheets, không phải Excel | Apps Script (miễn phí, có sẵn trigger theo lịch) |
| Import một lần, không bao giờ cần lại | Save As → CSV và dùng script import CSV |
| Import hàng loạt từ tệp >10 MB | CSV với fileUrl: xem hướng dẫn CSV |
Tại sao điều này thắng Zapier / nền tảng no-code
Cho công việc Excel sang Brevo lặp đi lặp lại, các công cụ tự động hóa bên thứ ba (Zapier, Make, n8n) tính phí theo task và đặt một bên thứ ba giữa dữ liệu của bạn và Brevo. Cách tiếp cận VBA có chi phí định kỳ bằng 0, không có luồng dữ liệu bên thứ ba và sống bên trong tệp (khi workbook di chuyển, tích hợp di chuyển cùng nó). Office Scripts + Power Automate tương tự nhưng với Microsoft là bên thứ ba (đã nằm trong stack của bạn nếu bạn dùng M365).
Toàn bộ ý nghĩa của endpoint POST /v3/contacts/import của Brevo là bạn không cần một nền tảng kết dính, các công cụ của bạn đã biết cách tạo HTTP request.