Brevo API: praktyczny przewodnik dla programistów
Przewodnik po Brevo API dla programistów: uwierzytelnianie, adres bazowy, kontakty, email transakcyjny, kampanie, obiekty CRM, webhooki, limity zapytań i realne ograniczenia.
Brevo udostępnia jedno API REST, które obejmuje wiadomości transakcyjne, kampanie marketingowe, dane kontaktów i rekordy CRM. Doprowadzenie pierwszego zapytania do odpowiedzi 201 zajmuje jakieś dwie minuty. Zbudowanie produkcyjnej integracji, która nie gubi po cichu danych, zajmuje znacznie dłużej, bo kilka najważniejszych ograniczeń jest albo nieudokumentowanych, albo sprzecznych z tym, co samo API o sobie mówi.
Ten przewodnik obejmuje obie połowy: endpointy, SDK i uwierzytelnianie, których potrzebujesz pierwszego dnia, oraz limity platformy, które musisz uwzględnić w projekcie, zanim wdrożysz rozwiązanie.
Co obejmuje Brevo API
Wszystko żyje pod jednym hostem i jedną ścieżką wersji. Dokumentacja dla programistów dzieli tę powierzchnię na cztery obszary produktowe:
- Wiadomości: email transakcyjny, SMS i WhatsApp, w tym wysyłki wsadowe, harmonogramowanie i aktywność wiadomości.
- Platforma marketingowa: kontakty, listy, segmenty i kampanie emailowe.
- eCommerce: produkty, zamówienia i śledzenie zdarzeń klienta.
- Conversations: widżet czatu i programowe zarządzanie rozmowami.
Te obszary dzielą jedno konto, jedną bazę kontaktów i jeden klucz API. To wygodne i czasami niebezpieczne: skrypt napisany z myślą o testowej wersji danych rozmawia z tymi samymi kontaktami, do których wysyłasz kampanie.
Transakcyjny kontra marketingowy
Te dwie rodziny zachowują się na tyle różnie, że pomylenie ich to najczęstszy błąd projektowy.
| Transakcyjny | Marketingowy | |
|---|---|---|
| Główny endpoint | POST /v3/smtp/email | POST /v3/emailCampaigns |
| Adresowanie | Jawni odbiorcy w zapytaniu | listIds lub segmentIds |
| Wyzwalacz | Twoja aplikacja, w czasie rzeczywistym | Zaplanowany lub wysłany na żądanie |
| Typowy kształt wolumenu | Ciągły, jedna wiadomość naraz | Skokowy, jedna duża wysyłka |
| Postawa wobec limitów | Bardzo wysoka, 1000 zapytań na sekundę na planach standardowych | Niska, endpointy kampanii podlegają ogólnemu pułapowi |
Jeśli wciąż zastanawiasz się, czy Brevo to w ogóle właściwa platforma, przegląd platformy opisuje ten temat.
Uwierzytelnianie i zarządzanie kluczami
Brevo używa zwykłego klucza API w niestandardowym nagłówku. Nagłówek nazywa się api-key, a nie Authorization, i nie ma prefiksu Bearer. Potyka się o to niemal każdy, kto wcześniej korzystał z innego API do wysyłki wiadomości.
curl https://api.brevo.com/v3/account \ -H "api-key: $BREVO_API_KEY"Klucze generujesz w aplikacji Brevo, w ustawieniach konta, w sekcji SMTP and API, na karcie API keys. Nadaj każdemu kluczowi opisową nazwę powiązaną z systemem, który go używa. Wartość klucza wyświetla się dokładnie raz, w momencie wygenerowania, więc jeśli ją zgubisz, generujesz nowy klucz, a nie odzyskujesz stary.
Kilka praktycznych zasad:
- Wydawaj osobny klucz na każde środowisko wdrożeniowe i każdą usługę. Unieważnienie skompromitowanego klucza nigdy nie powinno wyłączać trzech niepowiązanych systemów.
- Standardowe klucze API działają na całym koncie. Traktuj każdy klucz jak pełny dostęp do kontaktów, wysyłki i danych CRM.
- Brevo obsługuje też OAuth 2.0 dla aplikacji działających w imieniu innych kont Brevo, co opisano obok przepływu z kluczami w schematach uwierzytelniania.
- Serwer MCP używany przez asystentów AI przyjmuje osobny token i faktycznie korzysta z nagłówka bearer. Ten token generujesz na tym samym ekranie API keys, ale nie jest on wymienny z kluczem REST.
Adres bazowy, wersjonowanie i pierwszy zapis
Adres bazowy to https://api.brevo.com/v3/. Wersja jest w ścieżce, a nie w nagłówku, a v3 to aktualna generacja. Każda ścieżka w tym przewodniku jest podana względem tego adresu.
Pierwszy zapis mówi więcej niż pierwszy odczyt, bo uruchamia te elementy konta, które zwykle są źle skonfigurowane, w szczególności zweryfikowanych nadawców:
curl -X POST https://api.brevo.com/v3/smtp/email \ -H "api-key: $BREVO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "sender": { "name": "Ops", "email": "[email protected]" }, "to": [{ "email": "[email protected]", "name": "Dev" }], "subject": "Pierwsza wysyłka transakcyjna", "htmlContent": "<html><body><p>Działa.</p></body></html>", "tags": ["smoke-test"] }'Udana wysyłka zwraca 201 z polem messageId. Wysyłka zaplanowana zwraca 202.
Endpointy, których naprawdę użyjesz
Kontakty
POST /v3/contacts tworzy kontakt. Ciało zapytania przyjmuje email, mapę attributes na pola niestandardowe, listIds, ext_id na Twój własny klucz zewnętrzny oraz dwie flagi, które w praktyce liczą się najbardziej: updateEnabled, która zamienia wywołanie w upsert, i getId, która sprawia, że odpowiedź zwraca identyfikator kontaktu.
Odczyty idą przez GET /v3/contacts, które stronicuje przez limit (domyślnie 50, maksymalnie 1000) i offset oraz obsługuje modifiedSince i createdSince w UTC. Synchronizacje przyrostowe powinny opierać się na modifiedSince, zamiast przechodzić całą listę. Zwróć uwagę, że parametr filter obsługuje wyłącznie operator równości, więc wszystko bardziej wyraziste należy do segmentu.
Do masowego ładowania POST /v3/contacts/import przyjmuje fileUrl, fileBody lub jsonBody, celuje w listIds, działa asynchronicznie i zwraca processId. Brevo dokumentuje maksymalny rozmiar ciała 10 MB i zaleca trzymanie się blisko 8 MB, bo parsowanie zwiększa ładunek. Podaj notifyUrl, żeby poznać wynik zamiast odpytywać.
Email transakcyjny
POST /v3/smtp/email to koń roboczy. Poza sender, to, subject i htmlContent warto znać te pola:
templateIdwraz zparams, które zastępuje treść wpisaną wprost szablonem Brevo i podstawieniami zmiennych. Parametry pojedynczej wersji mają pułap 100 KB, a łącznie 1000 KB.messageVersions, które wysyła spersonalizowane warianty w jednym wywołaniu, po maksymalnie 99 odbiorców na wersję.tags, które zawsze warto ustawić. Tagi wracają w zdarzeniach webhooków i są jedynym tanim sposobem, żeby powiązać zdarzenie dostarczenia ze ścieżką kodu, która je wywołała.scheduledAtrazem zbatchId, dla przyszłych wysyłek, które możesz chcieć anulować grupowo.headers, zapisane w Title-Case, dla niestandardowych nagłówków SMTP.
Pojedyncze zapytanie przyjmuje maksymalnie 2000 odbiorców. Różnicę między tym endpointem a wysyłką kampanii z perspektywy strategii komunikacji opisuje przewodnik po emailu transakcyjnym.
Kampanie emailowe
POST /v3/emailCampaigns wymaga name i sender oraz dokładnie jednego źródła treści: htmlContent (minimum 10 znaków, poniżej 1 MB), htmlUrl lub templateId. Odbiorcy trafiają do recipients jako listIds lub segmentIds, a scheduledAt używa formatu UTC YYYY-MM-DDTHH:mm:ss.SSSZ. Trasy towarzyszące obsługują natychmiastową wysyłkę, wysyłkę testu, aktualizację statusu i pobranie raportu kampanii.
Firmy, szanse sprzedaży i obiekty
CRM w Brevo ma dwie nakładające się ścieżki zapisu i poprawny wybór ma znaczenie.
Trasy CRM to POST /v3/companies, PATCH /v3/companies/{id}, DELETE /v3/companies/{id} oraz odpowiedni zestaw dla szans sprzedaży. Są synchroniczne. PATCH zwraca 204, gdy zmiana zostanie zastosowana.
API obiektów to ścieżka masowa: POST /v3/objects/{object_type}/batch/upsert przyjmuje do 1000 rekordów i 1 MB na zapytanie, do 500 atrybutów na rekord i do 10 rekordów powiązań na typ obiektu na rekord. Zwraca 202 z polem processId, co znaczy „przyjęte”, a nie „zastosowane”.
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" } } ] }'Przewodnik po Brevo CRM opisuje model obiektowy od strony operatora.
Oficjalne SDK
Brevo utrzymuje klienty w organizacji GitHub getbrevo:
| Język | Repozytorium |
|---|---|
| Node.js | github.com/getbrevo/brevo-node |
| Python | github.com/getbrevo/brevo-python |
| PHP | github.com/getbrevo/brevo-php |
| Java | github.com/getbrevo/brevo-java |
| C# | github.com/getbrevo/brevo-csharp |
| Go | github.com/getbrevo/brevo-go |
| Ruby | github.com/getbrevo/brevo-ruby |
Klient Node instaluje się jako @getbrevo/brevo:
npm install @getbrevo/brevoimport { BrevoClient } from "@getbrevo/brevo";
const brevo = new BrevoClient({ apiKey: process.env.BREVO_API_KEY });
const result = await brevo.transactionalEmails.sendTransacEmail({ subject: "Zamówienie potwierdzone", htmlContent: "<html><body><p>Dziękujemy za zamówienie.</p></body></html>", tags: ["order-confirmation"],});
console.log("Message ID:", result.messageId);Klient Pythona instaluje się przez pip install brevo-python. Jeśli wolisz nie ciągnąć zależności SDK dla dwóch endpointów, surowa powierzchnia HTTP jest na tyle mała, że można wołać ją bezpośrednio, co dodatkowo uodparnia Cię na zmiany wersji SDK:
import osimport requests
BASE = "https://api.brevo.com/v3"HEADERS = { "api-key": os.environ["BREVO_API_KEY"], "Content-Type": "application/json",}
def upsert_contact(email, attributes, list_ids): response = requests.post( f"{BASE}/contacts", headers=HEADERS, json={ "email": email, "attributes": attributes, "listIds": list_ids, "updateEnabled": True, }, timeout=30, ) response.raise_for_status() return responseIstnieje też serwer MCP pod adresem https://mcp.brevo.com/v1/brevo/mcp dla asystentów AI, uwierzytelniany tokenem bearer generowanym na tym samym ekranie ustawień. Przydaje się do eksploracji i pytań o konto, a nie do produkcyjnych ścieżek danych.
Webhooki
Webhooki to sposób, w jaki dowiadujesz się, co stało się po wysyłce. POST /v3/webhooks tworzy webhook z polami url, events, type oraz opcjonalnie channel (email lub sms), batched, niestandardowymi headers i obiektem auth.
Istnieją trzy typy webhooków o odrębnych słownikach zdarzeń:
- Transakcyjne:
sent,request,delivered,hardBounce,softBounce,blocked,spam,invalid,deferred,click,opened,uniqueOpened,unsubscribed. - Marketingowe:
spam,opened,click,hardBounce,softBounce,unsubscribed,listAddition,delivered,contactUpdated,contactDeleted. - Przychodzące:
inboundEmailProcessedireply, które dodatkowo wymagają poladomain.
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": "Sygnały dostarczalności" }'Trzy rzeczy trzeba zrobić dobrze. Po pierwsze, konto może przechowywać maksymalnie 40 webhooków wszystkich typów, więc kieruj ruch według zdarzenia wewnątrz swojego handlera, zamiast rejestrować osobny endpoint na każde zdarzenie. Po drugie, używaj flagi batched, gdy spodziewasz się dużego wolumenu, bo jedno zapytanie niosące wiele zdarzeń jest znacznie tańsze w przetwarzaniu niż wiele zapytań. Po trzecie, chroń odbiornik: Brevo publikuje swoje zakresy adresów IP nadawczych, a ograniczenie endpointu do tych zakresów to udokumentowane podejście. Dodaj własny współdzielony sekret przez pole headers jako drugą warstwę.
Handlery muszą być idempotentne. Traktuj identyfikator wiadomości plus typ zdarzenia plus znacznik czasu jako klucz deduplikacji.
Limity zapytań i obsługa błędów
Limity zapytań w Brevo są ustalane per endpoint i per poziom planu, a rozrzut między endpointami jest ogromny.
| Endpoint | Standard | Professional i Enterprise |
|---|---|---|
POST /v3/smtp/email | 1000 RPS | 2000 RPS |
POST /v3/transactionalSMS/send | 150 RPS | 200 RPS |
/v3/contacts/... | 10 RPS, 36 000 RPH | 20 RPS, 72 000 RPH |
POST /v3/events | 10 RPS, 36 000 RPH | wyżej na Enterprise |
GET /v3/smtp/emails | 2 RPS, 7200 RPH | 3 RPS, 10 800 RPH |
| Cała reszta | 100 RPH | 200 RPH |
Ten ostatni wiersz boli najbardziej. Wysyłka jest praktycznie nielimitowana, podczas gdy zarządzanie kampaniami, odczyty z CRM i większość wywołań administracyjnych dzielą budżet 100 zapytań na godzinę na planach standardowych. Naiwne uzupełnianie danych, które przed każdym zapisem czyta rekord firmy, wyczerpie godzinny limit w niecałe dwie minuty.
Każda odpowiedź niesie x-sib-ratelimit-limit, x-sib-ratelimit-remaining i x-sib-ratelimit-reset. Czytaj je przy sukcesie, nie tylko przy porażce. Przekroczenie limitu zwraca 429, a właściwa reakcja to odczekanie interwału z nagłówka reset, a następnie wykładnicze wycofanie z losowym rozrzutem.
async function callBrevo(path, init, attempt = 0) { const response = await fetch(`https://api.brevo.com/v3${path}`, { ...init, headers: { "api-key": process.env.BREVO_API_KEY, "Content-Type": "application/json", ...init.headers }, });
if (response.status === 429 && attempt < 5) { const reset = Number(response.headers.get("x-sib-ratelimit-reset") || 1); const backoff = Math.pow(2, attempt) * 250 + Math.random() * 250; await new Promise((r) => setTimeout(r, reset * 1000 + backoff)); return callBrevo(path, init, attempt + 1); }
return response;}Ponawiaj 429 i 5xx. Nigdy nie ponawiaj ślepo 400 ani 409, bo oba zwykle znaczą, że zapytanie jest błędne, a nie przedwczesne, a 409 w szczególności wymaga innego działania, a nie powtórki.
Testowanie bez wysyłania poczty
Dodaj do wysyłki transakcyjnej nagłówek X-Sib-Sandbox z wartością drop. Brevo zweryfikuje zapytanie, zwróci 201 z polem messageId, niczego nie dostarczy i nie zapisze logu emaila.
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>hej</p>" }'Zrozum, co to dowodzi, a czego nie. Tryb sandbox weryfikuje wyłącznie format zapytania. Nie mówi nic o uwierzytelnieniu nadawcy, renderowaniu szablonu ani dostarczalności. Trzymaj osobne konto Brevo do testów integracyjnych wszystkiego, co dotyka kontaktów lub danych CRM, bo tryb sandbox obejmuje wysyłkę, a nie resztę API.
Limity, które kształtują projekt Twojej integracji
To są ograniczenia, które ujawniają się dopiero wtedy, gdy integracja działa na prawdziwym koncie przy realnym wolumenie. Kilka z nich przeczy temu, co API mówi samo o sobie. Żadne nie podlega negocjacji, więc jedyna sensowna odpowiedź to zaprojektować rozwiązanie wokół nich.
Firmy wymagają domeny, a na domenę przypada tylko jedna firma
GET /v3/crm/attributes/companies raportuje każdy atrybut jako niewymagany, a dokumentacja tworzenia firmy wymienia jako obowiązkowe wyłącznie name. W praktyce POST /v3/companies bez niepustego atrybutu domain zwraca 400 z komunikatem o brakujących obowiązkowych atrybutach domyślnych. Pusty ciąg znaków zawodzi tak samo jak jego pominięcie.
Gorzej, unikalność domeny jest wymuszana. Druga firma na domenie już używanej zwraca 409. Dla handlu B2B to problem strukturalny: spółki zależne dzielące jedną domenę emailową kupującego nie mogą istnieć w Brevo jako osobne firmy. Sama synchronizacja kontaktu wystarczy, żeby firma pojawiła się na domenie emailowej tego kontaktu, więc tworzenie może zderzyć się z firmą, której nikt świadomie nie utworzył. Właściwy handler przy 409 przejmuje istniejącą firmę, zamiast zawodzić albo ponawiać.
Niezadeklarowane atrybuty są po cichu odrzucane
To najgroźniejsze zachowanie na tej platformie i Brevo dokumentuje je wprost: jeśli atrybut pojawia się w zapytaniu, ale nie został wcześniej zdefiniowany w schemacie obiektu, nic się nie dzieje. Żadnego błędu, żadnego utworzenia atrybutu, żadnego ostrzeżenia.
Odpowiedź 2xx nie jest więc dowodem, że Twoje dane wylądowały. Odczytaj schemat przed zapisem, odrzucaj wszystko niezadeklarowane po swojej stronie klienta i odmawiaj uruchomienia synchronizacji, której atrybuty nie istnieją, zamiast przez miesiąc zapisywać połowę rekordu, zanim ktokolwiek to zauważy.
Filtry atrybutów są przyjmowane i ignorowane
GET /v3/companies?filters[attributes.domain]=... zwraca 200 i ignoruje filtr. Dwa zupełnie różne filtry zwracają te same rekordy. Nie ma działającego sposobu, żeby wyszukać firmę po atrybucie tą trasą.
W połączeniu z faktem, że niefiltrowana lista na dużych kontach kończy się timeoutem 504 przy dowolnym rozmiarze strony, istniejąca firma bywa naprawdę nie do znalezienia udokumentowaną ścieżką. Obejście to skanowanie GET /v3/objects/company/records z sort=desc, które jest szybkie, stronicowane i zwraca atrybuty, ograniczone do rozsądnej liczby stron. Firma, która przed chwilą wywołała 409, prawie zawsze powstała chwilę wcześniej, więc skanowanie od najnowszych znajduje ją szybko.
Milion rekordów na typ obiektu i brak masowego usuwania
POST /v3/objects/{type}/batch/upsert zwraca 400, gdy typ obiektu osiągnie milion rekordów. Blokuje to zarówno aktualizacje, jak i tworzenie: adresowanie istniejącego rekordu jego własnym identyfikatorem numerycznym zawodzi identycznie. Cała ścieżka zapisu obiektów zamyka się naraz.
Zejście z powrotem poniżej pułapu jest powolne, bo POST /v3/objects/{type}/batch/delete zwraca 403 dla standardowych typów obiektów Brevo, takich jak company. Jedyna trasa to DELETE /v3/companies/{id}, po jednym rekordzie na wywołanie, przy mniej więcej 156 ms. Wyczyszczenie 124 000 rekordów tą metodą zajęło godziny przy 20 równoległych workerach. Monitoruj liczbę rekordów zgodnie z harmonogramem, zamiast odkrywać pułap przez nieudaną synchronizację, i kieruj aktualizacje o dużym wolumenie przez PATCH /v3/companies/{id}, który takiego limitu nie ma.
ext_id to identyfikator Brevo, nie Twój
W rekordach obiektów identifiers.ext_id przechowuje własny identyfikator firmy w CRM Brevo, ciąg w stylu Mongo. To nie jest dowolny klucz zewnętrzny. Kluczowanie upsertu po ext_id ustawionym na identyfikator z Twojej platformy tworzy duplikaty zamiast dopasowań. Twój identyfikator zewnętrzny należy do osobnego, zadeklarowanego atrybutu.
Upserty obiektów są asynchroniczne, zapisy CRM nie
batch/upsert zwraca 202 i processId, a stosuje zmiany później. Nieistniejący identyfikator zawodzi asynchronicznie i mimo to zwraca 202 do Twojego wywołującego. PATCH /v3/companies/{id} zwraca 204 i jest stosowany synchronicznie. Jeśli Twoja synchronizacja raportuje sukces, tylko ścieżka synchroniczna zasługuje na to słowo bez dodatkowego odczytu kontrolnego.
Krótka lista kontrolna integracji
- Osobne klucze API na usługę i na środowisko, rotowane przy zmianach kadrowych.
- Wszystkie zapisy idą przez jednego klienta, który czyta nagłówki limitów i wycofuje się przy 429.
- Schemat atrybutów jest weryfikowany przy starcie, a synchronizacja odmawia uruchomienia, jeśli brakuje jej atrybutów.
- 409 przy tworzeniu firmy oznacza przejęcie, a nie ponowienie.
- Ścieżki masowe używają API obiektów dla przepustowości, a tras CRM dla wszystkiego, co musi być potwierdzone.
- Webhooki są idempotentne, wsadowe, ograniczone po IP i niosą nagłówek ze współdzielonym sekretem.
- Przyrostowe synchronizacje kontaktów używają
modifiedSince, a nie przechodzenia całej listy.
Zbudowanie i utrzymanie tej warstwy to prawdziwa praca inżynierska: weryfikacja schematu, wycofywanie, logika przejmowania, uzgadnianie danych. Tajo istnieje po to, żeby to wchłonąć, utrzymując dane z Shopify i handlu w synchronizacji z kontaktami, firmami i zdarzeniami w Brevo, bez ręcznego pisania logiki ponowień i deduplikacji. Jeśli mimo wszystko podłączasz to sam, przewodnik po integracji z Brevo omawia decyzje dotyczące modelu danych, które poprzedzają kod.
Kluczowe wnioski
- API to jedna powierzchnia REST pod
https://api.brevo.com/v3/, uwierzytelniana nagłówkiemapi-key, a nie tokenem bearer. - Limity zapytań są skrajnie nierówne: wysyłka jest praktycznie nielimitowana, podczas gdy większość pozostałych endpointów dzieli 100 zapytań na godzinę na planach standardowych.
- Oficjalne SDK istnieją dla siedmiu języków, ale powierzchnia HTTP jest na tyle prosta, że można wołać ją bezpośrednio, gdy potrzebujesz tylko kilku endpointów.
- Tryb sandbox weryfikuje wyłącznie format zapytania, więc trzymaj osobne konto do testowania czegokolwiek poza wysyłką.
- Odpowiedź 2xx nie dowodzi, że zapis został zastosowany. Niezadeklarowane atrybuty są po cichu odrzucane, a upserty obiektów są asynchroniczne.
- Projektuj wokół stałych ograniczeń: jedna firma na domenę, milion rekordów na typ obiektu, brak masowego usuwania dla obiektów standardowych i filtry atrybutów, które po cichu nic nie robią.