Cómo enviar contactos de Excel a Brevo con una macro VBA (y la alternativa con Office Scripts)

Una macro VBA que funciona y publica los contactos de una hoja de Excel en la API de Brevo de un clic. Además, cuándo usar Office Scripts + Power Automate, y los pros y contras frente a un setup con Google Apps Script.

Featured image for article: Cómo enviar contactos de Excel a Brevo con una macro VBA (y la alternativa con Office Scripts)

Tienes contactos en Excel y los quieres en Brevo. La respuesta rápida y sucia es guardar el archivo como .csv e importarlo, lo cual está bien una vez. Para cualquier cosa que hagas de forma repetida (entrega semanal de ventas, un libro que tu equipo actualiza a diario, una lista de partners que se refresca cada poco), quieres un botón dentro de Excel que haga la sincronización.

Esta guía cubre las dos rutas que de verdad tienen sentido para eso:

  1. Una macro VBA incrustada en el libro, sin licencias, sin nube, funciona offline, corre en el momento en que un usuario hace clic en un botón. La respuesta correcta para alrededor del 80 % de los casos de “Excel a Brevo”.
  2. Office Scripts + Power Automate, TypeScript en lugar de VBA, corre en la nube, soporta disparadores programados. La respuesta correcta si el libro vive en OneDrive/SharePoint y quieres sincronización desatendida, pero ten presente la licencia de Power Automate.

Si buscas el equivalente para Google Sheets, mira el artículo complementario sobre Apps Script. Y si solo quieres una importación CSV puntual desde un script en tu portátil, la guía de importación CSV tiene versiones en Python, Node.js y cURL.

Lo que hace la macro

Cuando el usuario hace clic en un botón “Sync to Brevo” en la hoja:

  1. Lee cada fila de la hoja activa (cabecera primero, un contacto por fila).
  2. Construye un array JSON con la forma del parámetro jsonBody de Brevo.
  3. Lo envía con POST a https://api.brevo.com/v3/contacts/import con la API key guardada en el libro.
  4. Muestra un cuadro de mensaje con el resultado.

Eso es todo. Unas 120 líneas de VBA. Abajo tienes el módulo completo y funcional.

Estructura de hoja que espera la macro

emailfirstNamelastNamecompanycity
[email protected]JaneDoeAcmeBerlin
[email protected]JohnSmithGlobexParis

email es obligatorio. Cualquier otra columna se convierte en un atributo de contacto en Brevo, mapeado por la cabecera de la columna (en mayúsculas) al nombre del atributo. Así firstNameFIRSTNAME, companyCOMPANY. Los atributos personalizados (cualquier cosa más allá del conjunto estándar) tienen que existir antes en tu cuenta de Brevo, defínelos en Contactos → Configuración → Atributos de contacto.

Paso 1: abrir el editor VBA

En Excel: pulsa Alt + F11. Se abre el editor VBA. En el panel Proyecto de la izquierda, haz clic derecho sobre tu libro y elige Insertar → Módulo. Aparece un Module1 en blanco.

Paso 2: pegar la macro completa

Reemplaza el contenido de Module1 por esto:

' ===========================================================================
' 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

Paso 3: guardar el libro como .xlsm

Las macros VBA solo persisten en libros habilitados para macros. Guardar como → elige Libro de Excel habilitado para macros (.xlsm). El formato .xlsx plano se carga las macros en silencio, mucha gente pierde código así la primera vez.

Paso 4: configurar tu API key

Ejecuta ConfigureApiKey una vez. Bien:

  • En el editor VBA, haz clic dentro del sub ConfigureApiKey y pulsa F5, o
  • En Excel, Programador → Macros, elige ConfigureApiKey, Ejecutar.

Pega tu clave xkeysib-.... La macro la guarda como propiedad personalizada del documento dentro del propio libro, no está en el código fuente, no está en el registro y viaja con el archivo (así que cuidado: si mandas el .xlsm a alguien, la API key se va con él).

Si prefieres guardar la clave fuera del libro, cambia el almacenamiento al registro de Windows:

' Replace the body of ConfigureApiKey with:
SaveSetting "Brevo", "Sync", "ApiKey", key
' And GetApiKey with:
GetApiKey = GetSetting("Brevo", "Sync", "ApiKey", "")

SaveSetting/GetSetting escribe en HKCU\Software\VB and VBA Program Settings\Brevo\Sync, por usuario, no por libro. Úsalo si varios libros deben compartir la misma clave o si no quieres la clave en el archivo.

Paso 5: añadir un botón en la hoja

Esto es lo que lo convierte en una experiencia de un clic para usuarios no técnicos.

Insertar → Formas → Rectángulo, suelta uno en la hoja, etiquétalo “Sync to Brevo.” Clic derecho sobre la forma → Asignar macro → elige SyncSheetToBrevo. Listo.

O, para una interfaz más pulida, añade una pestaña personalizada de cinta vía el Office Custom UI Editor, pero para la mayoría de las herramientas internas, el botón hecho de forma sobra.

Paso 6: ejecutarlo

Haz clic en el botón. La macro lee las filas, las divide en lotes, hace POST de cada lote a Brevo y muestra un cuadro de mensaje resumen. La importación de Brevo es asíncrona, así que el mensaje de éxito significa “Brevo aceptó el lote”, la creación real de los contactos sucede en el servidor en los siguientes segundos. Recibirás un email de resumen de Brevo cuando termine (a no ser que pongas disableNotification: true).

Trampas habituales

El botón no hace nada y no hay error. Las macros están desactivadas. Mira la barra amarilla de seguridad en la parte superior de la hoja, haz clic en Habilitar contenido. Si tu organización bloquea macros, mira la ruta de centro de confianza/firma de código más abajo.

Compile error: User-defined type not defined. Estás en Mac Excel, que no tiene MSXML2.XMLHTTP. El VBA de Mac no puede hacer peticiones HTTPS directamente; usa la ruta de Office Scripts más abajo.

400 Bad Request de Brevo sin causa obvia. Casi siempre es una de estas: (a) un atributo personalizado de tu hoja todavía no existe en Brevo, créalo primero; (b) un bug de escape de JSON, comillas o barras invertidas en valores de celda que no se escaparon. La función EscapeJson del código maneja los casos estándar; si tus datos tienen caracteres raros, registra payload en la ventana inmediata (Debug.Print payload) y revísalo.

401 Unauthorized. Header equivocado. Es api-key (en minúsculas, con guion), no Authorization. La macro usa el correcto, pero si has copiado un snippet de otro sitio, vuelve a comprobarlo.

Excel se congela en importaciones grandes. La macro corre de forma síncrona en el hilo de la interfaz. Para más de 50.000 filas, verás Excel colgado durante 10 a 30 segundos mientras construye el JSON y espera a Brevo. O lo asumes, o cambias MSXML2.XMLHTTP por su variante asíncrona, pero a esa escala te conviene más Power Automate (siguiente sección).

Cuando VBA no basta: Office Scripts + Power Automate

VBA no puede hacer sincronización en la nube programada. Si necesitas:

  • El libro sincronizando a Brevo cada hora sin que nadie lo abra
  • El libro en OneDrive/SharePoint, editado desde la web
  • Un departamento de IT que prohíbe las macros de escritorio

…entonces quieres Office Scripts (el equivalente cloud de Apps Script de Microsoft) más Power Automate (su capa de programación y HTTP).

El reparto: Office Scripts lee la hoja y devuelve los datos de los contactos. Power Automate toma esos datos y los envía con POST a Brevo cuando se dispara.

El Office Script (Excel para la web → Automatizar → Nuevo 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;
}

El flujo de Power Automate:

  1. Disparador: Periodicidad (cada 1 hora), o botón manual, o “Cuando se modifica una fila” si quieres sincronización por cambios.
  2. Acción: Excel Online → Ejecutar script, apúntalo a tu libro y al script de arriba. Guarda su valor de retorno como contacts.
  3. Acción: HTTP (este es el conector Premium, mira la nota de licencias más abajo).
    • Método: 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. Acción: Condición, si el código de estado ≠ 202, envía una alerta por Teams o email.

Realidad de las licencias: la acción HTTP es un conector Premium de Power Automate. En los planes Microsoft 365 Business Basic/Standard tienes los conectores estándar pero no los Premium. La salida más barata es el complemento Power Automate Premium (unos 15 USD por usuario al mes en el momento de escribir esto), o mover el HTTP a una pequeña Azure Function que el flujo estándar pueda llamar. Si ya estás en E3/E5 con Premium incluido, ya está.

Esta es la razón principal por la que la historia de Apps Script es más limpia: el UrlFetchApp de Apps Script es gratis y sin restricciones, mientras que el equivalente de Microsoft mete la llamada de red detrás de un nivel de conector de pago.

VBA frente a Office Scripts frente a Apps Script: cuándo elegir cada uno

NecesidadMejor opción
Botón de un clic en un libro que tu equipo ya abre a diarioMacro VBA (esta guía, mitad superior)
Libro en OneDrive/SharePoint, sincronización automática horariaOffice Scripts + Power Automate (necesita Premium para HTTP)
Solo Mac Excel, no puede usar VBAOffice Scripts + Power Automate
Los datos viven en Google Sheets, no en ExcelApps Script (gratis, disparadores programados integrados)
Importación puntual, nunca volverá a hacer faltaGuardar como → CSV y usar el script de importación CSV
Importación masiva desde un archivo de más de 10 MBCSV con fileUrl, mira la guía CSV

Por qué esto le gana a Zapier y a las plataformas no-code

Para un trabajo recurrente de Excel a Brevo, las herramientas de automatización de terceros (Zapier, Make, n8n) cobran por tarea y meten un tercero entre tus datos y Brevo. El enfoque VBA tiene cero coste recurrente, ningún flujo de datos por terceros y vive dentro del archivo: cuando el libro se mueve, la integración se mueve con él. Office Scripts + Power Automate es parecido pero con Microsoft de tercero (que ya está en tu stack si estás en M365).

El sentido completo del endpoint POST /v3/contacts/import de Brevo es que no necesitas una plataforma pegamento, tus herramientas ya saben hacer peticiones HTTP.

Lecturas adicionales

Frequently Asked Questions

¿Una macro VBA puede llamar de verdad a la API de Brevo desde dentro de Excel?
Sí. VBA puede hacer peticiones HTTP a través de MSXML2.XMLHTTP, que viene con cualquier instalación moderna de Windows. La macro hace POST de JSON a api.brevo.com/v3/contacts/import, el mismo endpoint al que pegaría un script de Python o de Node. Mac Excel también puede hacerlo vía Office Scripts + Power Automate, pero el VBA de Mac escritorio no (no tiene MSXML).
¿Puedo ejecutar esto de forma programada como con los disparadores de tiempo de Apps Script?
Desde VBA solo, no: Excel tiene que estar abierto para que Application.OnTime dispare. Para sincronización programada y desatendida tienes dos opciones: (1) el Programador de tareas de Windows que abre el libro y ejecuta la macro, o (2) Office Scripts disparado por un flujo programado de Power Automate (cubierto más adelante en esta guía).
¿VBA es seguro? Mi empresa bloquea las macros.
Las macros VBA corren con acceso al sistema de archivos y al registro, así que bloquearlas por defecto es una política sensata. Dos rutas para esquivarlo: (1) firma la macro con un certificado de firma de código y mete el libro en una Trusted Location, o (2) sáltate VBA por completo y usa Office Scripts (TypeScript, en sandbox, sin acceso al sistema de archivos). Office Scripts es la ruta moderna bendecida por Microsoft.
¿Cómo se compara esto con la opción de Google Apps Script?
Apps Script está más cerca del modelo set-and-forget: corre en la nube de Google, los disparadores programados están integrados, no hace falta licencia de Excel ni de Power Automate. VBA gana para trabajo offline y sincronización manual de un clic desde un libro que tu equipo ya tiene abierto. Office Scripts + Power Automate es el equivalente cloud y programado en el lado de Microsoft, pero Power Automate suele necesitar un conector premium de pago para hacer HTTP saliente.
Empieza gratis con Brevo