Как загружать контакты из Excel в Brevo с помощью VBA-макроса (и альтернатива на Office Scripts)
Рабочий VBA-макрос, отправляющий контакты с листа Excel в API Brevo одним кликом. Плюс когда вместо него использовать Office Scripts + Power Automate, и сравнение с подходом на Google Apps Script.
У Вас есть контакты в Excel, и Вы хотите загрузить их в Brevo. Быстрый и грубый ответ - сохранить файл как .csv и импортировать, что вполне годится для одного раза. Но для всего, что Вы делаете регулярно (еженедельная передача от продаж, книга, которую команда обновляет каждый день, список партнёров, который освежается), хочется иметь кнопку прямо внутри Excel, которая делает синхронизацию.
Это руководство охватывает два пути, которые реально имеют смысл:
- VBA-макрос, встроенный в книгу - без лицензий, без облака, работает офлайн, запускается в момент клика по кнопке. Правильный ответ для ~80% случаев “Excel в Brevo”.
- Office Scripts + Power Automate - TypeScript вместо VBA, работает в облаке, поддерживает триггеры по расписанию. Правильный ответ, если книга живёт в OneDrive/SharePoint и Вам нужна автономная синхронизация, но учтите лицензирование Power Automate.
Если Вам нужен эквивалент для Google Sheets, смотрите сопутствующую статью про Apps Script. А если Вам нужен разовый импорт CSV скриптом на ноутбуке, в руководстве по импорту CSV есть версии на Python, Node.js и cURL.
Что делает макрос
Когда пользователь нажимает кнопку “Sync to Brevo” на листе:
- Прочитать каждую строку с активного листа (сначала строка заголовков, по одному контакту в строке).
- Собрать JSON-массив в форме, которую ждёт параметр
jsonBodyBrevo. - Отправить POST на
https://api.brevo.com/v3/contacts/importс API-ключом, сохранённым в книге. - Показать результат в окне сообщения.
И всё. Около 120 строк VBA. Ниже полный рабочий модуль.
Структура листа, которую ожидает макрос
| firstName | lastName | company | city | |
|---|---|---|---|---|
| [email protected] | Jane | Doe | Acme | Berlin |
| [email protected] | John | Smith | Globex | Paris |
email обязателен. Каждый другой столбец становится атрибутом контакта в Brevo и сопоставляется по заголовку столбца (в верхнем регистре) с именем атрибута. То есть firstName → FIRSTNAME, company → COMPANY. Пользовательские атрибуты (всё за пределами стандартного набора) должны существовать в Вашем аккаунте Brevo заранее, определите их в Contacts → Settings → Contact attributes.
Шаг 1: открыть редактор VBA
В Excel: нажмите Alt + F11. Откроется редактор VBA. В панели Project слева кликните правой кнопкой по своей книге и выберите Insert → Module. Появится пустой Module1.
Шаг 2: вставить полный макрос
Замените содержимое Module1 на это:
' ===========================================================================' 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 FunctionШаг 3: сохранить книгу как .xlsm
VBA-макросы сохраняются только в книгах с поддержкой макросов. Save As → выберите Excel Macro-Enabled Workbook (.xlsm). Обычный формат .xlsx молча вырезает макросы, многие так теряют код в первый раз.
Шаг 4: настроить API-ключ
Запустите ConfigureApiKey один раз. Любым способом:
- В редакторе VBA кликните внутри подпрограммы
ConfigureApiKeyи нажмитеF5, или - В Excel: Developer → Macros, выберите
ConfigureApiKey, Run.
Вставьте Ваш ключ xkeysib-.... Макрос сохраняет его как пользовательское свойство документа внутри самой книги: его нет в исходном коде, нет в реестре, и он путешествует вместе с файлом (имейте в виду: если Вы пошлёте кому-то .xlsm по email, API-ключ уйдёт вместе с файлом).
Если Вы предпочитаете хранить ключ вне книги, поменяйте хранилище на реестр Windows:
' Replace the body of ConfigureApiKey with:SaveSetting "Brevo", "Sync", "ApiKey", key
' And GetApiKey with:GetApiKey = GetSetting("Brevo", "Sync", "ApiKey", "")SaveSetting/GetSetting пишут в HKCU\Software\VB and VBA Program Settings\Brevo\Sync, на пользователя, а не на книгу. Используйте этот вариант, если несколько книг должны делить один ключ или если Вы не хотите видеть ключ в файле.
Шаг 5: добавить кнопку на лист
Это превращает всё в опыт в один клик для нетехнических пользователей.
Insert → Shapes → Rectangle, киньте прямоугольник на лист, подпишите его “Sync to Brevo”. Кликните правой кнопкой по фигуре → Assign Macro → выберите SyncSheetToBrevo. Готово.
Или, для более полированного интерфейса, добавьте свою вкладку ленты через Office Custom UI Editor, но для большинства внутренних инструментов фигуры в роли кнопки более чем достаточно.
Шаг 6: запустить
Кликните по кнопке. Макрос читает строки, разбивает их на батчи, отправляет каждый батч в Brevo и показывает итоговое окно сообщения. Импорт Brevo асинхронный, так что сообщение об успехе означает “Brevo принял батч”: фактическое создание контактов происходит на стороне сервера в ближайшие несколько секунд. Brevo пришлёт сводку на email по завершении (если Вы не задали disableNotification: true).
Распространённые проблемы
Кнопка ничего не делает, и нет ошибки. Макросы отключены. Посмотрите на жёлтую полосу безопасности в верхней части листа и нажмите Enable Content. Если в организации макросы заблокированы, см. путь через trust-center / подпись кода ниже.
Compile error: User-defined type not defined. Вы на Mac Excel, в котором нет MSXML2.XMLHTTP. Mac VBA не может делать HTTPS-запросы напрямую, используйте путь Office Scripts ниже.
400 Bad Request от Brevo без явной причины. Почти всегда одно из: (a) пользовательский атрибут на Вашем листе ещё не существует в Brevo, создайте его сначала; (b) ошибка экранирования JSON, кавычки или обратные слэши в значениях ячеек, которые не были экранированы. Функция EscapeJson в коде покрывает стандартные случаи; если в данных встречаются необычные символы, выведите payload в Immediate window (Debug.Print payload) и осмотрите.
401 Unauthorized. Неправильный заголовок. Это api-key (нижний регистр, через дефис), а не Authorization. Макрос использует правильный заголовок, но если Вы скопировали фрагмент откуда-то ещё, перепроверьте.
Excel зависает на больших импортах. Макрос работает синхронно в потоке UI. Для 50 000+ строк Excel будет висеть 10-30 секунд, пока собирает JSON и ждёт ответа Brevo. Либо смиритесь, либо переключите MSXML2.XMLHTTP на его асинхронный вариант, но при таких объёмах Вам лучше в Power Automate (следующий раздел).
Когда VBA не хватает: Office Scripts + Power Automate
VBA не умеет облачную синхронизацию по расписанию. Если нужно:
- Книга синхронизируется с Brevo каждый час, и никто её не открывает
- Книга в OneDrive/SharePoint, редактируется через веб
- IT-отдел запретил десктопные макросы
…то Вам нужны Office Scripts (облачный эквивалент Apps Script от Microsoft) плюс Power Automate (их слой расписаний и HTTP).
Разделение труда: Office Scripts читает лист и возвращает данные о контактах. Power Automate забирает эти данные и отправляет POST в Brevo по триггеру.
Office Script (Excel for the 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;}Поток Power Automate:
- Триггер: Recurrence (каждый час), или ручная кнопка, или “When a row is modified”, если хотите синхронизацию на изменения.
- Действие: Excel Online → Run script - укажите Вашу книгу и скрипт выше. Сохраните возвращаемое значение как
contacts. - Действие: HTTP (это Premium-коннектор, см. примечание о лицензировании ниже).
- 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:
- Действие: Condition → если код статуса не равен 202, отправить уведомление в Teams/email.
Реальность лицензирования: действие HTTP - это Premium-коннектор Power Automate. На планах Microsoft 365 Business Basic/Standard Вы получаете стандартные коннекторы, но не Premium. Самый дешёвый обходной путь - дополнение Power Automate Premium (около $15/пользователь/месяц на момент написания), либо вынести HTTP-вызов в небольшую Azure Function, которую может вызвать стандартный поток. Если Вы уже на E3/E5 с включённым Premium, всё в порядке.
Это и есть главная причина, по которой история с Apps Script чище: UrlFetchApp в Apps Script бесплатен и без ограничений, тогда как эквивалент Microsoft прячет сетевой вызов за платным уровнем коннектора.
VBA, Office Scripts или Apps Script: что когда выбирать
| Потребность | Лучший вариант |
|---|---|
| Кнопка в один клик в книге, которую команда и так открывает каждый день | VBA-макрос (это руководство, верхняя половина) |
| Книга в OneDrive/SharePoint, авто-синхронизация раз в час | Office Scripts + Power Automate (нужен Premium для HTTP) |
| Только Mac Excel, VBA недоступен | Office Scripts + Power Automate |
| Данные живут в Google Sheets, а не в Excel | Apps Script (бесплатно, встроенные триггеры по расписанию) |
| Разовый импорт, больше не понадобится | Save As → CSV и скрипт импорта CSV |
| Массовый импорт из файла >10 MB | CSV с fileUrl - см. руководство по CSV |
Почему это лучше Zapier и no-code платформ
Для повторяющейся задачи “Excel в Brevo” сторонние инструменты автоматизации (Zapier, Make, n8n) берут плату за каждое действие и ставят третью сторону между Вашими данными и Brevo. У VBA-подхода нулевая постоянная стоимость, нет потока данных через третью сторону, и он живёт внутри файла: когда книга переезжает, интеграция переезжает вместе с ней. Office Scripts + Power Automate похожи, но с Microsoft в роли третьей стороны (которая и так уже в Вашем стеке, если Вы на M365).
Весь смысл эндпоинта POST /v3/contacts/import от Brevo в том, что Вам не нужна склейка-платформа: Ваши инструменты и так умеют делать HTTP-запросы.