VBA 매크로로 Excel 연락처를 Brevo로 푸시하는 방법 (그리고 Office Scripts 대안)
한 번의 클릭으로 Excel 시트의 연락처를 Brevo의 API로 게시하는 작동하는 VBA 매크로, 그리고 언제 대신 Office Scripts와 Power Automate를 사용해야 하는지, Google Apps Script 설정 대비 트레이드오프를 다룹니다.
Excel에 연락처가 있고 Brevo로 가져오고 싶습니다. 빠르고 간단한 답변은 파일을 .csv로 저장하고 가져오는 것이며, 한 번이라면 괜찮습니다. 반복적으로 하는 작업의 경우(주간 영업 인계, 팀이 매일 업데이트하는 워크북, 새로 고쳐지는 파트너 리스트) Excel 내부에서 동기화를 수행하는 버튼이 필요합니다.
이 가이드는 그것에 실제로 의미가 있는 두 가지 경로를 다룹니다.
- 워크북에 내장된 VBA 매크로: 라이선스 없음, 클라우드 없음, 오프라인에서 작동, 사용자가 버튼을 클릭하는 순간 실행. “Excel-to-Brevo” 사례의 약 80%에 대한 정답입니다.
- Office Scripts와 Power Automate: VBA 대신 TypeScript, 클라우드에서 실행, 스케줄 트리거 지원. 워크북이 OneDrive/SharePoint에 있고 무인 동기화를 원할 때 정답이지만, Power Automate 라이선스에 주의하세요.
Google Sheets에 해당하는 것을 찾고 있다면 Apps Script에 관한 동반 글을 보세요. 그리고 노트북에서 스크립트로 일회성 CSV 가져오기만 원한다면, CSV 가져오기 가이드에 Python, Node.js, cURL 버전이 있습니다.
매크로가 하는 일
사용자가 시트의 “Sync to Brevo” 버튼을 클릭할 때.
- 활성 워크시트의 모든 행을 읽습니다(헤더 행이 먼저, 행당 연락처 하나).
- Brevo의
jsonBody매개변수에 맞춰진 JSON 배열을 만듭니다. - 워크북에 저장된 API 키와 함께
https://api.brevo.com/v3/contacts/import로 POST합니다. - 결과와 함께 메시지 박스를 표시합니다.
그게 전부입니다. 약 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 Function3단계: 워크북을 .xlsm으로 저장
VBA 매크로는 매크로 사용 워크북에서만 유지됩니다. Save As → Excel Macro-Enabled Workbook (.xlsm) 을 선택합니다. 일반 .xlsx 형식은 매크로를 자동으로 제거합니다. 많은 사람들이 처음 이렇게 코드를 잃습니다.
4단계: API 키 구성
ConfigureApiKey를 한 번 실행합니다. 다음 중 하나를 선택하세요.
- VBA 편집기에서
ConfigureApiKey서브 내부의 아무 곳이나 클릭하고F5를 누르거나, - Excel에서 Developer → Macros,
ConfigureApiKey선택, Run.
xkeysib-... 키를 붙여넣습니다. 매크로는 그것을 워크북 내부의 사용자 정의 문서 속성으로 저장합니다. 소스 코드에 없고, 레지스트리에 없고, 파일과 함께 이동합니다(따라서 주의: .xlsm을 누군가에게 이메일로 보내면 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 선택. 완료.
또는 더 세련된 UI를 위해 Office Custom UI Editor를 통해 사용자 정의 리본 탭을 추가할 수 있지만, 대부분의 내부 도구의 경우 도형-버튼 방식으로 충분합니다.
6단계: 실행
버튼을 클릭합니다. 매크로가 행을 읽고, 일괄 처리하고, 각 배치를 Brevo에 게시하고, 요약 메시지 박스를 표시합니다. Brevo의 가져오기는 비동기적이므로 성공 메시지는 “Brevo가 배치를 수락했다”는 의미입니다. 실제 연락처 생성은 다음 몇 초 동안 서버 측에서 발생합니다. 완료되면 Brevo로부터 이메일 요약을 받습니다(disableNotification: true로 설정하지 않은 경우).
일반적인 함정
버튼이 아무것도 하지 않고 오류가 없습니다. 매크로가 비활성화되어 있습니다. 시트 상단의 노란색 보안 막대를 보고 Enable Content 를 클릭합니다. 조직이 매크로를 차단하는 경우, 아래의 신뢰 센터/코드 서명 경로를 보세요.
Compile error: User-defined type not defined. Mac Excel을 사용 중이며, MSXML2.XMLHTTP가 없습니다. Mac VBA는 HTTPS 요청을 직접 보낼 수 없습니다. 대신 아래의 Office Scripts 경로를 사용하세요.
명백한 원인 없이 Brevo로부터 400 Bad Request. 거의 항상 다음 중 하나입니다. (a) 시트의 사용자 정의 속성이 아직 Brevo에 존재하지 않음(먼저 만드세요); (b) JSON 이스케이프 버그(셀 값의 따옴표나 백슬래시가 이스케이프되지 않음). 코드의 EscapeJson 함수가 표준 케이스를 처리합니다. 데이터에 이상한 문자가 있다면, payload를 즉시 창에 로깅(Debug.Print payload)하고 검사하세요.
401 Unauthorized. 잘못된 헤더. Authorization이 아니라 api-key(소문자, 하이픈)입니다. 매크로는 올바른 것을 사용하지만, 다른 곳에서 스니펫을 복사한 경우 다시 확인하세요.
Excel이 큰 가져오기에서 멈춥니다. 매크로는 UI 스레드에서 동기적으로 실행됩니다. 50,000+ 행의 경우 JSON을 빌드하고 Brevo를 기다리는 동안 Excel이 10-30초 동안 멈추는 것을 보게 됩니다. 그것을 받아들이거나 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가 그 데이터를 받아 트리거에서 Brevo에 POST합니다.
Office Script (웹용 Excel → 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 (1시간마다), 또는 수동 버튼, 또는 변경 기반 동기화를 원한다면 “When a row is modified”.
- 액션: Excel Online → Run script, 워크북과 위 스크립트를 가리키게 합니다. 반환 값을
contacts로 저장합니다. - 액션: HTTP (이것은 프리미엄 커넥터입니다, 아래 라이선스 노트 참조).
- 메서드:
POST - URI:
https://api.brevo.com/v3/contacts/import - 헤더:
api-key: xkeysib-...,Content-Type: application/json - 본문:
{"jsonBody": @{outputs('Run_script')?['body/result']},"listIds": [42],"updateExistingContacts": true}
- 메서드:
- 액션: Condition → 상태 코드가 202가 아니면 Teams/이메일 알림을 보냅니다.
라이선스 현실 확인: HTTP 액션은 Power Automate Premium 커넥터입니다. Microsoft 365 Business Basic/Standard 플랜에서는 표준 커넥터를 받지만 프리미엄은 받지 않습니다. 가장 저렴한 우회 방법은 Power Automate Premium 추가 기능(작성 시점 기준 사용자당 월 약 $15) 또는 표준 플로우가 호출할 수 있는 작은 Azure Function으로 HTTP를 옮기는 것입니다. 이미 프리미엄이 포함된 E3/E5에 있다면 준비된 것입니다.
이것이 Apps Script 스토리가 더 깔끔한 주요 이유입니다. Apps Script의 UrlFetchApp은 무료이고 제한이 없는 반면, Microsoft 동등 솔루션은 네트워크 호출을 유료 커넥터 계층 뒤에 둡니다.
VBA vs Office Scripts vs Apps Script: 언제 어떤 것을 선택할까
| 필요 | 최선의 옵션 |
|---|---|
| 팀이 이미 매일 여는 워크북의 원클릭 버튼 | VBA 매크로 (이 가이드, 상단 절반) |
| OneDrive/SharePoint의 워크북, 시간별 자동 동기화 | Office Scripts와 Power Automate (HTTP를 위해 프리미엄 필요) |
| Mac Excel만, VBA를 사용할 수 없음 | Office Scripts와 Power Automate |
| 데이터가 Excel이 아닌 Google Sheets에 있음 | Apps Script (무료, 스케줄 트리거 내장) |
| 일회성 가져오기, 다시 필요하지 않음 | Save As → CSV 그리고 CSV 가져오기 스크립트 사용 |
| 10MB 이상 파일에서 일괄 가져오기 | fileUrl이 있는 CSV, CSV 가이드 참조 |
이것이 Zapier / 노코드 플랫폼을 이기는 이유
반복적인 Excel-to-Brevo 작업의 경우, 서드파티 자동화 도구(Zapier, Make, n8n)는 작업당 요금을 부과하고 데이터와 Brevo 사이에 제3자를 둡니다. VBA 접근 방식은 진행 비용이 없고, 서드파티 데이터 흐름이 없으며, 파일 내부에 살아 있습니다. 워크북이 이동하면 통합도 함께 이동합니다. Office Scripts와 Power Automate는 비슷하지만 Microsoft가 제3자입니다(M365에 있다면 이미 스택에 있음).
Brevo POST /v3/contacts/import 엔드포인트의 핵심은 글루 플랫폼이 필요 없다는 것입니다. 도구가 이미 HTTP 요청을 보내는 방법을 알고 있습니다.