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.
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:
- 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”.
- 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:
- Lee cada fila de la hoja activa (cabecera primero, un contacto por fila).
- Construye un array JSON con la forma del parámetro
jsonBodyde Brevo. - Lo envía con POST a
https://api.brevo.com/v3/contacts/importcon la API key guardada en el libro. - 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
| firstName | lastName | company | city | |
|---|---|---|---|---|
| [email protected] | Jane | Doe | Acme | Berlin |
| [email protected] | John | Smith | Globex | Paris |
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í firstName → FIRSTNAME, company → COMPANY. 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 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 FunctionPaso 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
ConfigureApiKeyy pulsaF5, 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:
- Disparador: Periodicidad (cada 1 hora), o botón manual, o “Cuando se modifica una fila” si quieres sincronización por cambios.
- Acción: Excel Online → Ejecutar script, apúntalo a tu libro y al script de arriba. Guarda su valor de retorno como
contacts. - 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}
- Método:
- 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
| Necesidad | Mejor opción |
|---|---|
| Botón de un clic en un libro que tu equipo ya abre a diario | Macro VBA (esta guía, mitad superior) |
| Libro en OneDrive/SharePoint, sincronización automática horaria | Office Scripts + Power Automate (necesita Premium para HTTP) |
| Solo Mac Excel, no puede usar VBA | Office Scripts + Power Automate |
| Los datos viven en Google Sheets, no en Excel | Apps Script (gratis, disparadores programados integrados) |
| Importación puntual, nunca volverá a hacer falta | Guardar como → CSV y usar el script de importación CSV |
| Importación masiva desde un archivo de más de 10 MB | CSV 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.