Brevo API: 실무 중심 개발자 가이드

Brevo API 가이드: 인증, 연락처 갱신, 웹훅, 요청 제한, 안전한 재시도를 설명합니다.

Brevo API

핵심 답은 무엇인가요?

일반 API 키에는 https://api.brevo.com/v3/와 api-key 헤더를 사용합니다. 연락처 동기화 시 안정적인 원본 ID, 정의된 속성, 명시적인 목록을 지정하고 수신 거부 상태를 유지하세요. 저장 결과와 제한 헤더를 확인하고 429 재시도에 한도를 두세요. 웹훅은 인증된 변경 신호로 처리하고 중복 제거와 대조를 수행해야 합니다.

자세히 알아보기
이 페이지에서

연락처 생성이 201을 반환해도 결과가 틀릴 수 있습니다. 원본 ID가 빠졌거나 목록을 잘못 골랐거나 기존 수신 거부를 놓친 경우입니다. 이는 HTTP 세부사항보다 데이터 처리 결정의 문제입니다.

먼저 실행 가능한 연락처 저장 요청을 보여주고, 작은 규모로 반복할 동기화의 검사를 설명합니다.

Brevo API가 다루는 범위

모든 것이 하나의 호스트와 하나의 버전 경로 아래에 있습니다. 개발자 문서는 이 인터페이스를 네 개의 제품 영역으로 묶습니다.

  • 메시징: 트랜잭션 이메일, SMS, WhatsApp. 일괄 발송, 예약 발송, 메시지 활동 내역을 포함합니다.
  • 마케팅 플랫폼: 연락처, 리스트, 세그먼트, 이메일 캠페인.
  • eCommerce: 상품, 주문, 고객 이벤트 추적.
  • Conversations: 채팅 위젯과 프로그래밍 방식의 대화 관리.

이 영역들은 하나의 계정, 하나의 연락처 데이터베이스, 하나의 API 키를 공유합니다. 편리하지만 때로는 위험합니다. 스테이징 데이터를 가정하고 작성한 스크립트가 실제 캠페인을 발송하는 바로 그 연락처를 건드리기 때문입니다.

트랜잭션과 마케팅의 차이

두 계열은 충분히 다르게 동작하며, 이 둘을 혼동하는 것이 가장 흔한 설계 오류입니다.

트랜잭션마케팅
주요 엔드포인트POST /v3/smtp/emailPOST /v3/emailCampaigns
수신자 지정요청에 수신자를 명시listIds 또는 segmentIds
트리거애플리케이션에서 실시간으로예약 발송 또는 수동 발송
일반적인 볼륨 형태지속적으로 한 통씩한 번에 몰리는 대량 발송
요청 제한 여력매우 높음, general 등급에서 초당 1,000회낮음, 캠페인 엔드포인트는 일반 상한 적용

Brevo가 애초에 적합한 플랫폼인지 아직 판단 중이라면, 플랫폼 개요가 그 부분을 다룹니다.

인증과 키 관리

Brevo는 커스텀 헤더에 담긴 단순한 API 키를 사용합니다. 헤더 이름은 Authorization이 아니라 api-key이며, Bearer 접두사도 없습니다. 다른 메시징 API를 먼저 써 본 사람이라면 거의 예외 없이 여기서 걸립니다.

지원되는 작업에서는 OAuth Bearer 토큰을 사용할 수도 있습니다. 일반 API 키는 계속 api-key 헤더에 넣습니다.

Terminal window
curl https://api.brevo.com/v3/account \
-H "api-key: $BREVO_API_KEY"

키는 Brevo 앱의 계정 설정에서 SMTP and API 섹션의 API keys 탭에서 생성합니다. 각 키에는 그 키를 사용하는 시스템과 연결되는 설명적인 이름을 붙이시기 바랍니다. 키 값은 생성 시점에 정확히 한 번만 표시되므로, 분실하면 기존 키를 복구하는 대신 새 키를 생성해야 합니다.

몇 가지 실무 원칙이 있습니다.

  • 배포 대상별, 서비스별로 키를 분리해서 발급하십시오. 유출된 키 하나를 폐기하는 일이 서로 무관한 세 개의 시스템을 중단시켜서는 안 됩니다.
  • 표준 API 키는 계정 전체 권한을 가집니다. 모든 키를 연락처, 발송, CRM 데이터에 대한 완전한 접근 권한으로 취급하십시오.
  • Brevo는 다른 Brevo 계정을 대신해 동작하는 애플리케이션을 위한 OAuth 2.0도 지원하며, 인증 방식 문서에 키 방식과 함께 설명되어 있습니다.
  • AI 어시스턴트가 사용하는 MCP 서버는 별도의 토큰을 사용하며 bearer 헤더를 씁니다. 이 토큰은 같은 API keys 화면에서 생성하지만 REST 키와 서로 바꿔 쓸 수 없습니다.

기본 URL, 버전, 첫 쓰기 요청

기본 URL은 https://api.brevo.com/v3/ 입니다. 버전은 헤더가 아니라 경로에 들어가며, 현재 세대는 v3입니다. 이 가이드의 모든 경로는 이 기본 URL을 기준으로 합니다.

첫 저장에는 직접 관리하는 테스트 주소, 유효한 목록 ID, 안정적인 원본 ID를 사용하세요. 먼저 Brevo에 FNAME 속성을 만드세요. 실제 키를 사용하면 이 예제는 계정에 바로 기록합니다.

Terminal window
curl -X POST https://api.brevo.com/v3/contacts \
-H "api-key: $BREVO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"ext_id": "test-source-contact-1",
"attributes": { "FNAME": "Dev" },
"listIds": [42],
"updateEnabled": true,
"getId": true
}'

42를 테스트 목록의 ID로 바꾸세요. 연락처를 다시 읽어 ID, 이름, 목록, 차단 상태를 확인하세요. 2xx만으로 예상 결과를 증명할 수 없습니다.

실제로 사용하게 될 엔드포인트

연락처

POST /v3/contacts는 연락처를 생성합니다. 본문에는 email, 정의된 attributes, listIds, 안정적인 원본 키 ext_id를 넣습니다. updateEnabled: true는 기존 연락처를 갱신하고 getId: true는 최종 ID를 요청합니다. Brevo가 이 필드를 문서화했습니다. 매 실행에서 같은 ext_id를 사용하고 원본 ID와 Brevo ID의 대응을 저장하세요.

저장 전에는 각 속성을 어느 시스템이 관리하는지, 동의한 연락처가 어느 목록에 들어가는지 결정하세요. listIds를 명시하세요. 일반 upsert에서 emailBlacklisted: false를 보내지 마세요. Brevo는 차단된 연락처의 이메일을 바꾸면 차단이 풀릴 수 있다고 경고합니다. 주소 변경은 별도로 처리하세요. 마케팅 목록에 넣기 전에 차단과 수신 거부를 확인하세요. 읽기와 쓰기 사이에 상태가 달라질 수 있으므로 엄격한 보호가 필요하면 발송 직전 다시 확인해야 합니다.

읽기는 GET /v3/contacts 로 처리하며, limit(기본값 50, 최대 1000)과 offset으로 페이지를 나누고 UTC 기준의 modifiedSince, createdSince를 지원합니다. 증분 동기화는 전체 리스트를 훑는 대신 modifiedSince에 의존해야 합니다. filter 파라미터는 동등 비교 연산자만 지원하므로, 그보다 표현력이 필요한 조건은 세그먼트로 옮기시기 바랍니다.

대량 적재에는 POST /v3/contacts/import 를 사용합니다. fileUrl, fileBody, jsonBody를 받고 listIds를 대상으로 하며 비동기로 실행되어 processId를 반환합니다. Brevo는 본문 최대 10 MB를 문서화하고 있으며, 파싱 과정에서 페이로드가 부풀기 때문에 8 MB 근처를 유지하도록 권장합니다. 결과를 폴링하지 않고 통보받으려면 notifyUrl을 지정하십시오.

트랜잭션 이메일

POST /v3/smtp/email 이 핵심입니다. sender, to, subject, htmlContent 외에 알아 둘 만한 필드는 다음과 같습니다.

  • templateId와 params. 인라인 콘텐츠 대신 Brevo 템플릿과 변수 치환을 사용합니다. 개별 버전 파라미터는 100 KB, 누적 파라미터는 1000 KB로 제한됩니다.
  • messageVersions. 한 번의 호출로 개인화된 변형을 발송하며, 버전당 최대 99명의 수신자를 지원합니다.
  • tags는 전달 이벤트를 이벤트를 만든 애플리케이션 흐름과 연결하는 데 도움이 될 수 있습니다.
  • scheduledAt과 batchId. 나중에 그룹 단위로 취소할 수도 있는 예약 발송에 사용합니다.
  • headers. 커스텀 SMTP 헤더를 Title-Case로 지정합니다.

한 요청은 최대 2,000명의 수신자를 받습니다. 이 엔드포인트와 캠페인 발송의 차이는 트랜잭션 이메일 가이드에서 메시징 전략 관점으로 다룹니다.

이메일 캠페인

POST /v3/emailCampaigns 는 name과 sender를 요구하며, 여기에 정확히 하나의 콘텐츠 소스가 필요합니다. htmlContent(최소 10자, 1 MB 미만), htmlUrl, templateId 중 하나입니다. 대상은 recipients 안에 listIds 또는 segmentIds로 넣고, scheduledAt은 YYYY-MM-DDTHH:mm:ss.SSSZ UTC 형식을 사용합니다. 즉시 발송, 테스트 발송, 상태 변경, 캠페인 리포트 조회는 별도의 관련 경로가 담당합니다.

회사, 거래, 객체

Brevo CRM에는 겹치는 쓰기 경로가 두 가지 있으며, 어느 쪽을 고르느냐가 중요합니다.

CRM 경로는 POST /v3/companies, PATCH /v3/companies/{id}, DELETE /v3/companies/{id} 와 거래에 대한 동일한 세트입니다. 이들은 동기 방식입니다. PATCH 는 변경이 적용되면 204를 반환합니다.

객체 API는 대량 처리 경로입니다. POST /v3/objects/{object_type}/batch/upsert 는 요청당 최대 1000개 레코드와 1 MB, 레코드당 최대 500개 속성, 레코드당 객체 타입별 최대 10개의 연관 레코드를 받습니다. 반환값은 processId와 함께 202이며, 이는 적용이 아니라 접수를 뜻합니다.

Terminal window
curl -X POST https://api.brevo.com/v3/objects/company/batch/upsert \
-H "api-key: $BREVO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"identifiers": { "id": 12345 },
"attributes": { "domain": "acme.example", "industry": "retail" }
}
]
}'

Brevo CRM 가이드는 같은 객체 모델을 운영자 관점에서 설명합니다.

공식 SDK

Brevo는 getbrevo GitHub 조직에서 클라이언트를 관리합니다.

언어저장소
Node.jsgithub.com/getbrevo/brevo-node
Pythongithub.com/getbrevo/brevo-python
PHPgithub.com/getbrevo/brevo-php
Javagithub.com/getbrevo/brevo-java
C#github.com/getbrevo/brevo-csharp
Gogithub.com/getbrevo/brevo-go
Rubygithub.com/getbrevo/brevo-ruby

Node 클라이언트는 @getbrevo/brevo로 설치합니다.

Terminal window
npm install @getbrevo/brevo
import { BrevoClient } from "@getbrevo/brevo";
// Reconcile uncertain send outcomes before retrying.
const brevo = new BrevoClient({ apiKey: process.env.BREVO_API_KEY, maxRetries: 0 });
const result = await brevo.transactionalEmails.sendTransacEmail({
subject: "Order confirmed",
htmlContent: "<html><body><p>Thanks for your order.</p></body></html>",
sender: { name: "Acme", email: "[email protected]" },
to: [{ email: "[email protected]", name: "Customer" }],
tags: ["order-confirmation"],
});
console.log("Message ID:", result.messageId);

Python 클라이언트는 pip install brevo-python 으로 설치합니다. 엔드포인트 두 개 때문에 SDK 의존성을 끌어안고 싶지 않다면, 원시 HTTP 인터페이스는 직접 호출해도 될 만큼 작습니다. SDK 버전 변경의 영향에서도 자유로워집니다.

Python 클라이언트는 pip install brevo-python으로 설치합니다. 몇 개의 엔드포인트만 쓴다면 HTTP를 직접 호출할 수 있습니다.

import os
import requests
BASE = "https://api.brevo.com/v3"
HEADERS = {"api-key": os.environ["BREVO_API_KEY"]}
def upsert_consented_contact(source_id, email, first_name, list_id, consent):
if not consent:
raise ValueError("No marketing consent in the source")
if not source_id or not isinstance(list_id, int):
raise ValueError("Stable source ID and numeric list ID required")
# Configure FNAME in Brevo before using this explicit field mapping.
response = requests.post(
f"{BASE}/contacts",
headers=HEADERS,
json={
"email": email,
"ext_id": source_id,
"attributes": {"FNAME": first_name},
"listIds": [list_id],
"updateEnabled": True,
"getId": True,
},
timeout=30,
)
response.raise_for_status()
return response.json().get("id") if response.content else None

성공한 갱신도 본문이 없는 204를 반환할 수 있습니다. 이때 None은 ID를 얻기 위해 연락처를 다시 읽어야 한다는 뜻입니다.

이 예제는 호출자가 동의와 Brevo 차단 상태를 이미 확인했다고 가정합니다. 차단을 해제하지 않습니다. 실제 키는 계정에 쓰므로 먼저 모의 응답으로 시험하세요. ID 충돌은 여러 4xx 코드로 나타날 수 있습니다. 항상 409라고 가정하지 말고 응답을 읽어 ID를 대조하세요.

웹훅

웹훅은 발송 이후에 무슨 일이 일어났는지 알 수 있는 수단입니다. POST /v3/webhooks 로 생성하며, url, events, type과 선택적으로 channel(email 또는 sms), batched, 커스텀 headers, auth 객체를 지정합니다.

웹훅 타입은 세 가지이며 각각 이벤트 어휘가 다릅니다.

  • 트랜잭션: sent, request, delivered, hardBounce, softBounce, blocked, spam, invalid, deferred, click, opened, uniqueOpened, unsubscribed.
  • 마케팅: spam, opened, click, hardBounce, softBounce, unsubscribed, listAddition, delivered, contactUpdated, contactDeleted.
  • 인바운드: inboundEmailProcessed 와 reply. 여기에는 domain이 추가로 필요합니다.
Terminal window
curl -X POST https://api.brevo.com/v3/webhooks \
-H "api-key: $BREVO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.example/hooks/brevo",
"type": "transactional",
"events": ["delivered", "hardBounce", "spam", "unsubscribed"],
"description": "Deliverability signals"
}'

연락처 변경에는 contactUpdated가 포함된 마케팅 웹훅을 등록하세요. Brevo 페이로드에서는 이벤트 이름이 contact_updated입니다. 보안 가이드에 따라 auth bearer 토큰 또는 사용자 지정 헤더로 수신 요청을 인증하세요. 처리 전에 비밀값을 검증하고 로그에 남기지 마세요.

이벤트 유형, 연락처 ID, 시각으로 중복 제거 키를 만들고 재전달 기간 동안 보관하세요. 시각만으로는 고유하지 않습니다. 이벤트가 중복되거나 순서가 바뀔 수 있습니다. 현재 연락처를 다시 읽어 신뢰할 원본과 대조하세요. 재생 기간을 제한하고 복구 후 오래된 이벤트를 거부하며 정기적인 증분 검사로 누락을 보완하세요. 웹훅만으로는 양방향 동기화가 완성되지 않습니다. 커넥터 가이드를 참고하세요.

요청 제한과 오류 처리

Brevo의 요청 제한은 엔드포인트별, 요금제 등급별로 정해지며 엔드포인트 사이의 편차가 매우 큽니다.

엔드포인트General(모든 요금제)Advanced(Professional 및 Enterprise)
POST /v3/smtp/email초당 1,000회초당 2,000회
POST /v3/transactionalSMS/send초당 150회초당 200회
/v3/contacts/...초당 10회, 시간당 36,000회초당 20회, 시간당 72,000회
POST /v3/events초당 10회, 시간당 36,000회초당 20회, 시간당 72,000회
GET /v3/smtp/emails초당 2회, 시간당 7,200회초당 3회, 시간당 10,800회
자체 제한이 없는 엔드포인트시간당 100회시간당 200회

Brevo는 제한 대상 엔드포인트에 x-sib-ratelimit-limit, x-sib-ratelimit-remaining, x-sib-ratelimit-reset 헤더를 제공합니다. 성공 응답에서도 읽으세요. 429 이후에는 재설정까지 기다리고 고정된 횟수와 시간 안에서만 재시도하세요. reset은 제한 창의 단위로 표현된 기간이며 보통 초이지 Unix 시간이 아닙니다.

async function callBrevo(path, init) {
for (let attempt = 0; attempt < 3; attempt++) {
const response = await fetch(`https://api.brevo.com/v3${path}`, {
...init,
headers: { "api-key": process.env.BREVO_API_KEY, ...init.headers },
});
if (response.status !== 429 || attempt === 2) return response;
const raw = Number(response.headers.get("x-sib-ratelimit-reset"));
const seconds = Number.isFinite(raw) && raw > 0 ? raw : 2 ** attempt;
const delayMs = (seconds + 2 ** attempt) * 1000;
if (delayMs > 60_000) return response;
await new Promise(resolve => setTimeout(resolve, delayMs));
}
}

필요한 대기가 60초를 넘으면 함수는 429를 반환합니다. 다음 시도는 나중에 예약하세요.

이 도우미는 반복이 안전하다고 확인한 요청에만 사용하세요. 발송 결과가 불명확하면 자신의 메시지 ID로 먼저 대조하고 나서 재발송을 결정하세요. 상태와 제한 헤더만 기록하고 키와 연락처 데이터는 기록하지 마세요.

4xx를 무조건 재시도하지 마세요. 연락처 생성 문서에는 400과 425가 있으며 충돌이 항상 409는 아닙니다. 저장 후 5xx가 왔다면 이미 적용되었는지 확인하세요.

어떤 상태가 반환되었나요?

  1. 429

    예산 안에서 대기

    재설정 시간과 시도 횟수를 지키세요.

  2. 저장 후 5xx

    결과 확인

    서버가 오류 전에 저장했을 수 있습니다.

  3. 400

    요청 수정

    입력 오류인 경우가 많습니다.

  4. ID 충돌

    ID 대조

    고정된 충돌 코드는 보장되지 않습니다.

메일을 보내지 않고 테스트하기

트랜잭션 발송 요청에 X-Sib-Sandbox 헤더를 drop 값으로 추가하십시오. Brevo는 요청을 검증하고 messageId와 함께 201을 반환하지만 아무것도 배달하지 않고 이메일 로그도 남기지 않습니다.

Terminal window
curl -X POST https://api.brevo.com/v3/smtp/email \
-H "api-key: $BREVO_API_KEY" \
-H "X-Sib-Sandbox: drop" \
-H "Content-Type: application/json" \
-d '{ "sender": { "email": "[email protected]" }, "to": [{ "email": "[email protected]" }], "subject": "Sandbox", "htmlContent": "<p>hi</p>" }'

이 기능이 무엇을 증명하고 무엇을 증명하지 않는지 이해하는 것이 중요합니다. 샌드박스 모드는 요청 형식만 검증합니다. 발신자 인증, 템플릿 렌더링, 도달률에 대해서는 아무것도 말해 주지 않습니다. 연락처나 CRM 데이터를 건드리는 모든 것을 테스트하려면 별도의 Brevo 계정을 두시기 바랍니다. 샌드박스 모드는 발송만 다루고 나머지 API는 다루지 않기 때문입니다.

연락처 동기화 전 확인

  • ext_id에 안정적인 원본 ID를 쓰고 Brevo ID를 저장하세요.
  • 속성을 먼저 정의한 뒤 테스트 연락처를 다시 읽으세요.
  • 목록과 동의 증거를 명시하고 차단 및 수신 거부 연락처를 제외하세요.
  • 접수, 적용, 건너뜀, 실패를 따로 집계하세요. 202는 우선 접수만 뜻합니다.
  • 마케팅 웹훅을 인증하고 중복을 제거한 뒤 현재 상태와 대조하세요.
  • 제한 헤더를 확인하고 정해진 횟수 뒤 재시도를 멈추세요.

Tajo는 상거래 데이터를 Brevo와 동기화합니다. 직접 구현한다면 커넥터 가이드를 확인하세요.

자주 묻는 질문

Brevo API의 기본 URL은 무엇인가요?

모든 Brevo REST 호출은 https://api.brevo.com/v3/ 로 전송됩니다. 버전은 경로에 포함되며 현재 세대는 v3입니다. 따라서 트랜잭션 발송 같은 엔드포인트의 전체 경로는 https://api.brevo.com/v3/smtp/email 이 됩니다.

Brevo API 인증은 어떻게 하나요?

api-key라는 이름의 HTTP 헤더에 키를 담아 보냅니다. 표준 API 키에는 Authorization 헤더나 Bearer 헤더를 사용하지 않습니다. 키는 계정 설정의 SMTP and API, API keys 화면에서 생성하며, 값은 생성 시점에 단 한 번만 표시됩니다.

Brevo API의 요청 제한은 어떻게 되나요?

제한은 엔드포인트별, 요청 제한 등급별로 다릅니다. 모든 요금제에 적용되는 general 등급에서 트랜잭션 발송 엔드포인트는 초당 1,000회, 연락처 엔드포인트는 초당 10회를 허용하며, 자체 제한이 없는 엔드포인트는 시간당 100회로 제한됩니다. Professional과 Enterprise 요금제는 advanced 등급을, Enterprise는 extended 등급을 사용할 수 있습니다. 제한을 초과하면 429가 반환됩니다.

Brevo에 공식 SDK가 있나요?

있습니다. Brevo는 getbrevo GitHub 조직에서 Node.js, Python, PHP, Java, C#, Go, Ruby용 클라이언트를 배포합니다. Node 패키지는 npm의 @getbrevo/brevo이고 Python 패키지는 PyPI의 brevo-python입니다.

실제 메일을 보내지 않고 Brevo API를 테스트하려면 어떻게 하나요?

트랜잭션 발송 요청에 X-Sib-Sandbox 헤더를 drop 값으로 추가하면 됩니다. Brevo는 요청을 검증하고 messageId와 함께 201을 반환하지만 아무것도 발송하지 않고 이메일 로그도 남기지 않습니다. 요청 형식만 확인할 뿐 도달률은 검증하지 않습니다.

Brevo는 어떤 웹훅 이벤트를 지원하나요?

트랜잭션 웹훅은 전달, 반송, 수신 거부를 다룹니다. 마케팅 웹훅에는 contactUpdated와 contactDeleted가 있습니다. 수신 이벤트로는 inboundEmailProcessed가 문서화되어 있습니다.

연락처 갱신 시 기존 수신 거부는 어떻게 보호하나요?

안정적인 원본 ID와 정의된 속성을 사용하세요. 일반 갱신에서 emailBlacklisted: false를 보내지 마세요. 마케팅 목록에 추가하기 전 연락처와 동의 출처를 확인하세요.

웹훅만으로 양방향 동기화가 되나요?

아니요. 수신 측을 인증하고 중복 전달을 제거하며 누락되거나 순서가 바뀐 이벤트를 신뢰할 원본과 대조해야 합니다. 웹훅은 변경을 알릴 뿐 두 시스템의 상태 일치를 증명하지 않습니다.

Tajo 사전 이용 신청

이름과 회사 이메일 주소를 입력하세요. Tajo 이용 신청을 확인할 수 있는 링크를 이메일로 보내드립니다.

Brevo 받기