Как загружать контакты из Excel в Brevo с помощью VBA-макроса (и альтернатива на Office Scripts)

Рабочий VBA-макрос, отправляющий контакты с листа Excel в API Brevo одним кликом. Плюс когда вместо него использовать Office Scripts + Power Automate, и сравнение с подходом на Google Apps Script.

Featured image for article: Как загружать контакты из Excel в Brevo с помощью VBA-макроса (и альтернатива на Office Scripts)

У Вас есть контакты в Excel, и Вы хотите загрузить их в Brevo. Быстрый и грубый ответ - сохранить файл как .csv и импортировать, что вполне годится для одного раза. Но для всего, что Вы делаете регулярно (еженедельная передача от продаж, книга, которую команда обновляет каждый день, список партнёров, который освежается), хочется иметь кнопку прямо внутри Excel, которая делает синхронизацию.

Это руководство охватывает два пути, которые реально имеют смысл:

  1. VBA-макрос, встроенный в книгу - без лицензий, без облака, работает офлайн, запускается в момент клика по кнопке. Правильный ответ для ~80% случаев “Excel в Brevo”.
  2. Office Scripts + Power Automate - TypeScript вместо VBA, работает в облаке, поддерживает триггеры по расписанию. Правильный ответ, если книга живёт в OneDrive/SharePoint и Вам нужна автономная синхронизация, но учтите лицензирование Power Automate.

Если Вам нужен эквивалент для Google Sheets, смотрите сопутствующую статью про Apps Script. А если Вам нужен разовый импорт CSV скриптом на ноутбуке, в руководстве по импорту CSV есть версии на Python, Node.js и cURL.

Что делает макрос

Когда пользователь нажимает кнопку “Sync to Brevo” на листе:

  1. Прочитать каждую строку с активного листа (сначала строка заголовков, по одному контакту в строке).
  2. Собрать JSON-массив в форме, которую ждёт параметр jsonBody Brevo.
  3. Отправить POST на https://api.brevo.com/v3/contacts/import с API-ключом, сохранённым в книге.
  4. Показать результат в окне сообщения.

И всё. Около 120 строк VBA. Ниже полный рабочий модуль.

Структура листа, которую ожидает макрос

emailfirstNamelastNamecompanycity
[email protected]JaneDoeAcmeBerlin
[email protected]JohnSmithGlobexParis

email обязателен. Каждый другой столбец становится атрибутом контакта в Brevo и сопоставляется по заголовку столбца (в верхнем регистре) с именем атрибута. То есть firstNameFIRSTNAME, companyCOMPANY. Пользовательские атрибуты (всё за пределами стандартного набора) должны существовать в Вашем аккаунте 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 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

Шаг 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:

  1. Триггер: Recurrence (каждый час), или ручная кнопка, или “When a row is modified”, если хотите синхронизацию на изменения.
  2. Действие: Excel Online → Run script - укажите Вашу книгу и скрипт выше. Сохраните возвращаемое значение как contacts.
  3. Действие: 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
      }
  4. Действие: 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, а не в ExcelApps Script (бесплатно, встроенные триггеры по расписанию)
Разовый импорт, больше не понадобитсяSave As → CSV и скрипт импорта CSV
Массовый импорт из файла >10 MBCSV с fileUrl - см. руководство по CSV

Почему это лучше Zapier и no-code платформ

Для повторяющейся задачи “Excel в Brevo” сторонние инструменты автоматизации (Zapier, Make, n8n) берут плату за каждое действие и ставят третью сторону между Вашими данными и Brevo. У VBA-подхода нулевая постоянная стоимость, нет потока данных через третью сторону, и он живёт внутри файла: когда книга переезжает, интеграция переезжает вместе с ней. Office Scripts + Power Automate похожи, но с Microsoft в роли третьей стороны (которая и так уже в Вашем стеке, если Вы на M365).

Весь смысл эндпоинта POST /v3/contacts/import от Brevo в том, что Вам не нужна склейка-платформа: Ваши инструменты и так умеют делать HTTP-запросы.

Дополнительные материалы

Frequently Asked Questions

Может ли VBA-макрос действительно вызывать Brevo API из Excel?
Да. VBA умеет делать HTTP-запросы через MSXML2.XMLHTTP, который поставляется с каждой современной установкой Windows. Макрос отправляет POST с JSON на api.brevo.com/v3/contacts/import, тот же эндпоинт, который использовал бы скрипт на Python или Node. Mac Excel тоже может это делать через Office Scripts + Power Automate, но десктопный VBA на Mac не может (нет MSXML).
Можно ли запускать это по расписанию, как триггеры по времени в Apps Script?
Из одного только VBA нет, Excel должен быть открыт, чтобы Application.OnTime сработал. Для автономной синхронизации по расписанию есть два варианта: (1) Windows Task Scheduler, который открывает книгу и запускает макрос, или (2) Office Scripts, запускаемый из Power Automate по расписанию (рассмотрено далее в этом руководстве).
Безопасен ли VBA? Моя компания блокирует макросы.
VBA-макросы выполняются с доступом к файловой системе и реестру, поэтому блокировка по умолчанию - разумная политика. Два пути в обход: (1) подписать макрос сертификатом подписи кода и поместить книгу в Trusted Location, или (2) полностью отказаться от VBA и использовать Office Scripts (TypeScript, в песочнице, без доступа к файловой системе). Office Scripts - современный, одобренный Microsoft путь.
Как это сравнить с подходом Google Apps Script?
Apps Script ближе к режиму поставил-и-забыл: работает в облаке Google, встроенные триггеры по расписанию, не нужны лицензии Excel/Power Automate. VBA выигрывает для офлайн-работы и ручной синхронизации в один клик из книги, которую команда и так держит открытой. Office Scripts + Power Automate - облачный/планируемый эквивалент на стороне Microsoft, но Power Automate часто требует платный premium-коннектор для исходящего HTTP.
Начните бесплатно с Brevo