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.

Featured image for article: Cách đẩy danh bạ Excel vào Brevo bằng macro VBA (và lựa chọn Office Scripts)

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 đó:

  1. 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”.
  2. 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:

  1. Đọc mỗi hàng từ worksheet đang hoạt động (hàng header trước, mỗi hàng một danh bạ).
  2. Xây dựng mảng JSON theo hình dạng cho tham số jsonBody của Brevo.
  3. POST nó đến https://api.brevo.com/v3/contacts/import với API key được lưu trong workbook.
  4. 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

emailfirstNamelastNamecompanycity
[email protected]JaneDoeAcmeBerlin
[email protected]JohnSmithGlobexParis

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 firstNameFIRSTNAME, companyCOMPANY. 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 ID
Private 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 0
End 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 = 0
End 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 If
End 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 = r
End Function

Bướ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 ConfigureApiKey và nhấn F5, 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:

  1. 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.
  2. 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.
  3. 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
      }
  4. 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ầuLựa chọn tốt nhất
Nút một-cú-click trong workbook mà nhóm bạn đã mở hàng ngàyMacro 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 VBAOffice Scripts + Power Automate
Dữ liệu sống trong Google Sheets, không phải ExcelApps Script (miễn phí, có sẵn trigger theo lịch)
Import một lần, không bao giờ cần lạiSave As → CSV và dùng script import CSV
Import hàng loạt từ tệp >10 MBCSV 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.

Đọc thêm

Frequently Asked Questions

Một macro VBA có thể thực sự gọi API của Brevo từ bên trong Excel không?
Có. VBA có thể tạo HTTP request thông qua MSXML2.XMLHTTP, có sẵn trong mọi bản cài đặt Windows hiện đại. Macro POST JSON đến api.brevo.com/v3/contacts/import, cùng endpoint mà một script Python hoặc Node sẽ gọi. Mac Excel cũng có thể làm điều đó qua Office Scripts + Power Automate, nhưng Mac VBA trên desktop thì không (không có MSXML).
Tôi có thể chạy nó theo lịch giống trigger thời gian của Apps Script không?
Không thể chỉ từ VBA, Excel phải mở để Application.OnTime kích hoạt. Để đồng bộ theo lịch không cần giám sát, bạn có hai lựa chọn: (1) Windows Task Scheduler mở workbook và chạy macro, hoặc (2) Office Scripts được kích hoạt bởi luồng Power Automate theo lịch (đề cập ở phần sau của hướng dẫn).
VBA có an toàn không? Công ty tôi chặn macro.
Macro VBA chạy với quyền truy cập file system và registry, nên việc chặn chúng theo mặc định là một chính sách hợp lý. Hai cách giải quyết: (1) ký macro với chứng chỉ ký mã và đặt workbook vào Trusted Location, hoặc (2) bỏ qua VBA hoàn toàn và dùng Office Scripts (TypeScript, sandbox, không truy cập file system). Office Scripts là lộ trình hiện đại được Microsoft công nhận.
So sánh với cách Google Apps Script như thế nào?
Apps Script gần với set-and-forget hơn, chạy trên cloud của Google, có sẵn trigger theo lịch, không cần license Excel/Power Automate. VBA thắng cho công việc offline và đồng bộ thủ công một-cú-click từ workbook mà nhóm bạn đã mở sẵn. Office Scripts + Power Automate tương đương cloud/theo lịch ở phía Microsoft, nhưng Power Automate thường cần connector premium trả phí cho HTTP đi ra.
Bắt đầu miễn phí với Brevo