Brevo API: ghid practic pentru dezvoltatori
Ghid Brevo API pentru dezvoltatori: autentificare, URL de bază, contacte, e-mail tranzacțional, campanii, obiecte CRM, webhook-uri, limite de rată și limitele reale.
Brevo expune un singur API REST care acoperă mesageria tranzacțională, campaniile de marketing, datele de contact și înregistrările CRM. Ca prima cerere să returneze 201 îți ia cam două minute. Ca să obții o integrare de producție care nu pierde date în tăcere îți ia considerabil mai mult, pentru că mai multe dintre constrângerile care contează cel mai mult sunt fie nedocumentate, fie contrazic ceea ce raportează API-ul despre sine.
Acest ghid acoperă ambele jumătăți: endpointurile, SDK-urile și autentificarea de care ai nevoie din prima zi, plus limitele platformei în jurul cărora trebuie să proiectezi înainte de a lansa.
Ce acoperă Brevo API
Totul stă sub o singură gazdă și o singură cale de versiune. Documentația pentru dezvoltatori grupează suprafața în patru zone de produs:
- Mesagerie: e-mail tranzacțional, SMS și WhatsApp, inclusiv trimiteri în lot, programare și activitatea mesajelor.
- Platformă de marketing: contacte, liste, segmente și campanii de e-mail.
- eCommerce: produse, comenzi și urmărirea evenimentelor clienților.
- Conversations: widgetul de chat și gestionarea programatică a conversațiilor.
Aceste zone împart un singur cont, o singură bază de date de contacte și o singură cheie API. Este convenabil și, ocazional, periculos: un script scris pornind de la o idee de staging despre date vorbește cu aceleași contacte către care trimit campaniile tale.
Tranzacțional față de marketing
Cele două familii se comportă suficient de diferit încât confundarea lor este cea mai frecventă eroare de proiectare.
| Tranzacțional | Marketing | |
|---|---|---|
| Endpoint principal | POST /v3/smtp/email | POST /v3/emailCampaigns |
| Adresare | Destinatari expliciți în cerere | listIds sau segmentIds |
| Declanșator | Aplicația ta, în timp real | Programat sau trimis la cerere |
| Forma tipică a volumului | Continuu, câte un mesaj o dată | În valuri, o singură trimitere mare |
| Poziția față de limita de rată | Foarte ridicată, 1.000 de cereri pe secundă pe planurile standard | Scăzută, endpointurile de campanii intră sub plafonul general |
Dacă încă decizi dacă Brevo este platforma potrivită, prezentarea generală a platformei acoperă acel teren.
Autentificare și gestionarea cheilor
Brevo folosește o cheie API simplă într-un antet propriu. Antetul se numește api-key, nu Authorization, și nu există niciun prefix Bearer. Asta încurcă aproape pe oricine a folosit mai întâi un alt API de mesagerie.
curl https://api.brevo.com/v3/account \ -H "api-key: $BREVO_API_KEY"Cheile se generează în aplicația Brevo, în setările contului, în secțiunea SMTP and API, pe fila API keys. Dă fiecărei chei un nume descriptiv legat de sistemul care o folosește. Valoarea cheii este afișată exact o singură dată, la generare, deci dacă o pierzi generezi una nouă în loc să o recuperezi pe cea veche.
Câteva reguli practice:
- Emite o cheie separată pentru fiecare mediu de implementare și pentru fiecare serviciu. Revocarea unei chei compromise nu ar trebui niciodată să pice trei sisteme fără legătură între ele.
- Cheile API standard sunt valabile la nivel de cont. Tratează orice cheie ca acces complet la contacte, trimitere și date CRM.
- Brevo acceptă și OAuth 2.0 pentru aplicațiile care acționează în numele altor conturi Brevo, descris alături de fluxul cu chei în schemele de autentificare.
- Serverul MCP folosit de asistenții AI ia un token separat și chiar folosește un antet bearer. Tokenul se generează din același ecran de chei API, dar nu este interschimbabil cu o cheie REST.
URL de bază, versionare și prima ta scriere
URL-ul de bază este https://api.brevo.com/v3/. Versiunea stă în cale, nu într-un antet, iar v3 este generația curentă. Fiecare cale din acest ghid este relativă la acea bază.
O primă scriere este mai informativă decât o primă citire, pentru că pune la treabă părțile din cont care sunt de obicei configurate greșit, în special expeditorii verificați:
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": "First transactional send", "htmlContent": "<html><body><p>It works.</p></body></html>", "tags": ["smoke-test"] }'O trimitere reușită returnează 201 cu un messageId. O trimitere programată returnează 202.
Endpointurile pe care chiar le vei folosi
Contacte
POST /v3/contacts creează un contact. Corpul acceptă email, o hartă attributes pentru câmpuri personalizate, listIds, ext_id pentru cheia ta externă și cele două opțiuni care contează cel mai mult în practică: updateEnabled, care transformă apelul într-un upsert, și getId, care face ca răspunsul să returneze id-ul contactului.
Citirile trec prin GET /v3/contacts, care paginează cu limit (implicit 50, maximum 1000) și offset și acceptă modifiedSince și createdSince în UTC. Sincronizările incrementale ar trebui să se sprijine pe modifiedSince, nu pe parcurgerea listei complete. Reține că parametrul filter acceptă doar un operator de egalitate, așa că orice lucru mai expresiv aparține unui segment.
Pentru încărcare în masă, POST /v3/contacts/import acceptă fileUrl, fileBody sau jsonBody, țintește listIds și rulează asincron, returnând un processId. Brevo documentează un corp maxim de 10 MB și recomandă să rămâi în jur de 8 MB, pentru că parsarea umflă payloadul. Furnizează notifyUrl ca să afli rezultatul în loc să interoghezi repetat.
E-mail tranzacțional
POST /v3/smtp/email este calul de povară. Dincolo de sender, to, subject și htmlContent, câmpurile pe care merită să le cunoști sunt:
templateIdîmpreună cuparams, care înlocuiește conținutul inline cu un șablon Brevo și substituțiile lui de variabile. Parametrii unei versiuni individuale sunt plafonați la 100 KB, iar parametrii cumulați la 1000 KB.messageVersions, care trimite variante personalizate într-un singur apel, cu până la 99 de destinatari per versiune.tags, pe care ar trebui să le setezi întotdeauna. Etichetele revin în evenimentele webhook și sunt singura cale ieftină de a corela un eveniment de livrare cu ramura de cod care l-a produs.scheduledAtplusbatchId, pentru trimiteri viitoare pe care ai putea vrea să le anulezi ca grup.headers, scrise în Title-Case, pentru anteturi SMTP personalizate.
O singură cerere acceptă cel mult 2.000 de destinatari. Pentru diferența dintre acest endpoint și trimiterea de campanii, ghidul de e-mail tranzacțional oferă perspectiva de strategie a mesajelor.
Campanii de e-mail
POST /v3/emailCampaigns cere name și sender, plus exact o sursă de conținut: htmlContent (minimum 10 caractere, sub 1 MB), htmlUrl sau templateId. Publicul se pune în recipients ca listIds sau segmentIds, iar scheduledAt folosește formatul UTC YYYY-MM-DDTHH:mm:ss.SSSZ. Rutele însoțitoare acoperă trimiterea imediată, trimiterea unui test, actualizarea statusului și extragerea raportului campaniei.
Companii, tranzacții și obiecte
CRM-ul Brevo are două căi de scriere care se suprapun, iar alegerea corectă contează.
Rutele CRM sunt POST /v3/companies, PATCH /v3/companies/{id}, DELETE /v3/companies/{id} și setul echivalent pentru tranzacții. Acestea sunt sincrone. Un PATCH returnează 204 imediat ce modificarea este aplicată.
API-ul de obiecte este calea de masă: POST /v3/objects/{object_type}/batch/upsert acceptă până la 1000 de înregistrări și 1 MB per cerere, până la 500 de atribute per înregistrare și până la 10 înregistrări de asociere per tip de obiect per înregistrare. Returnează 202 cu un processId, adică acceptat, nu aplicat.
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" } } ] }'Ghidul Brevo CRM acoperă modelul de obiecte din perspectiva operatorului.
SDK-uri oficiale
Brevo întreține clienți în organizația GitHub getbrevo:
| Limbaj | Repository |
|---|---|
| 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 |
Clientul Node se instalează ca @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: "Order confirmed", htmlContent: "<html><body><p>Thanks for your order.</p></body></html>", tags: ["order-confirmation"],});
console.log("Message ID:", result.messageId);Clientul Python se instalează cu pip install brevo-python. Dacă preferi să nu cari o dependență de SDK pentru două endpointuri, suprafața HTTP brută este suficient de mică încât să o apelezi direct, ceea ce te ține și izolat de schimbările de versiune ale SDK-ului:
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 responseExistă și un server MCP la https://mcp.brevo.com/v1/brevo/mcp pentru asistenții AI, autentificat cu un token bearer generat în același ecran de setări. Este util pentru explorare și pentru întrebări despre cont, nu pentru fluxuri de date de producție.
Webhook-uri
Webhook-urile sunt modul în care afli ce s-a întâmplat după o trimitere. POST /v3/webhooks creează unul, cu url, events, type și, opțional, channel (email sau sms), batched, headers personalizate și un obiect auth.
Există trei tipuri de webhook, cu vocabulare de evenimente distincte:
- Tranzacțional:
sent,request,delivered,hardBounce,softBounce,blocked,spam,invalid,deferred,click,opened,uniqueOpened,unsubscribed. - Marketing:
spam,opened,click,hardBounce,softBounce,unsubscribed,listAddition,delivered,contactUpdated,contactDeleted. - Inbound:
inboundEmailProcessedșireply, care cer în plus undomain.
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" }'Trei lucruri de nimerit. Întâi, un cont poate avea cel mult 40 de webhook-uri în total, pe toate tipurile, așa că rutează după eveniment în interiorul handlerului, în loc să înregistrezi un endpoint pentru fiecare eveniment. Al doilea, folosește opțiunea batched când te aștepți la volum, pentru că o cerere care poartă multe evenimente este mult mai ieftin de procesat decât multe cereri. Al treilea, protejează receptorul: Brevo publică intervalele IP de la care trimite, iar restricționarea endpointului tău la acele intervale este abordarea documentată. Adaugă propriul tău secret partajat prin câmpul headers, ca al doilea strat.
Handlerele trebuie să fie idempotente. Tratează id-ul mesajului plus tipul evenimentului plus marca de timp drept cheie de deduplicare.
Limite de rată și tratarea erorilor
Limitele de rată ale Brevo sunt per endpoint și per nivel de plan, iar distanța dintre endpointuri este enormă.
| Endpoint | Standard | Professional și Enterprise |
|---|---|---|
POST /v3/smtp/email | 1.000 RPS | 2.000 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 | mai mult pe Enterprise |
GET /v3/smtp/emails | 2 RPS, 7.200 RPH | 3 RPS, 10.800 RPH |
| Tot restul | 100 RPH | 200 RPH |
Ultimul rând este cel care doare. Trimiterea este practic nemăsurată, în timp ce gestionarea campaniilor, citirile CRM și majoritatea apelurilor administrative împart un buget de 100 de cereri pe oră pe planurile standard. Un backfill naiv care citește o înregistrare de companie înainte de fiecare scriere epuizează cota unei ore în mai puțin de două minute.
Fiecare răspuns poartă x-sib-ratelimit-limit, x-sib-ratelimit-remaining și x-sib-ratelimit-reset. Citește-le la succes, nu doar la eșec. Depășirea unei limite returnează 429, iar reacția corectă este să aștepți intervalul din antetul de reset și apoi să aplici backoff exponențial cu jitter.
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;}Reîncearcă la 429 și 5xx. Nu reîncerca niciodată orbește la 400 sau 409, pentru că amândouă înseamnă de obicei că cererea este greșită, nu că a venit prea devreme, iar un 409 în special cere o altă acțiune, nu o repetare.
Testare fără a trimite e-mailuri
Adaugă antetul X-Sib-Sandbox cu valoarea drop la o trimitere tranzacțională. Brevo validează cererea, returnează 201 cu un messageId, nu livrează nimic și nu scrie niciun jurnal de e-mail.
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>" }'Înțelege ce dovedește și ce nu dovedește asta. Modul sandbox validează doar formatul cererii. Nu spune nimic despre autentificarea expeditorului, randarea șabloanelor sau livrabilitate. Ține un cont Brevo separat pentru testarea integrării a orice atinge contacte sau date CRM, pentru că modul sandbox acoperă trimiterea, nu restul API-ului.
Limite care îți modelează designul integrării
Acestea sunt constrângerile care apar abia după ce o integrare rulează pe un cont real, la volum. Câteva contrazic ceea ce spune API-ul despre sine. Niciuna nu este negociabilă, așa că singura reacție rezonabilă este să proiectezi în jurul lor.
Companiile cer un domeniu, și o singură companie per domeniu
GET /v3/crm/attributes/companies raportează fiecare atribut ca fiind neobligatoriu, iar referința de creare a unei companii listează doar name ca obligatoriu. În practică, POST /v3/companies fără un atribut domain nevid returnează 400, cu un mesaj despre atribute implicite obligatorii lipsă. Un șir gol eșuează la fel ca omiterea lui.
Mai rău, unicitatea domeniului este impusă. O a doua companie pe un domeniu deja folosit returnează 409. Pentru comerțul B2B asta este structural: filialele care împart un singur domeniu de e-mail al cumpărătorului nu pot exista toate ca firme separate în Brevo. Sincronizarea unui contact este de asemenea suficientă ca să apară o companie pe domeniul de e-mail al acelui contact, deci o creare poate intra în coliziune cu o companie pe care nu a creat-o nimeni explicit. Handlerul corect adoptă compania existentă la 409, în loc să eșueze sau să reîncerce.
Atributele nedeclarate sunt eliminate în tăcere
Acesta este cel mai periculos comportament din platformă, iar Brevo îl documentează pe față: dacă un atribut apare într-o cerere, dar nu a fost definit anterior în schema obiectului, nu se întâmplă nimic. Nicio eroare, nicio creare de atribut, niciun avertisment.
Un răspuns 2xx nu este deci o dovadă că datele tale au ajuns unde trebuie. Citește schema înainte de a scrie, elimină în propriul client tot ce nu este declarat și refuză să rulezi o sincronizare ale cărei atribute nu există, în loc să scrii o jumătate de înregistrare timp de o lună până observă cineva.
Filtrele pe atribute sunt acceptate și ignorate
GET /v3/companies?filters[attributes.domain]=... returnează 200 și ignoră filtrul. Două filtre complet diferite returnează aceleași înregistrări. Nu există nicio cale funcțională de a căuta o companie după atribut prin acea rută.
Combinat cu faptul că lista nefiltrată expiră cu 504 pe conturile mari, la orice dimensiune de pagină, o companie existentă poate fi cu adevărat imposibil de găsit pe calea documentată. Soluția de ocolire este să scanezi GET /v3/objects/company/records cu sort=desc, care este rapid, paginat și returnează atribute, limitat la un număr rezonabil de pagini. O companie care tocmai a declanșat un 409 a fost aproape întotdeauna creată cu câteva momente înainte, așa că scanarea de la cea mai nouă o găsește repede.
Un milion de înregistrări per tip de obiect, și nicio ștergere în masă
POST /v3/objects/{type}/batch/upsert returnează 400 odată ce un tip de obiect deține un milion de înregistrări. Blochează și actualizările, nu doar creările: adresarea unei înregistrări existente după propriul id numeric eșuează identic. Toată calea de scriere pe obiecte se închide deodată.
Revenirea sub plafon este lentă, pentru că POST /v3/objects/{type}/batch/delete returnează 403 pentru tipurile de obiecte standard Brevo, cum ar fi company. Singura rută este DELETE /v3/companies/{id}, câte o înregistrare per apel, la aproximativ 156 ms. Golirea a 124.000 de înregistrări în acest fel a durat ore, cu 20 de workeri în paralel. Monitorizează numărul de înregistrări periodic, în loc să descoperi plafonul printr-o sincronizare eșuată, și rutează actualizările de volum mare prin PATCH /v3/companies/{id}, care nu are o astfel de limită.
ext_id este id-ul Brevo, nu al tău
Pe înregistrările de obiecte, identifiers.ext_id conține propriul id CRM de companie al Brevo, un șir în stil Mongo. Nu este o cheie externă liberă. Dacă faci un upsert cheiat pe ext_id setat la identificatorul platformei tale, creezi duplicate în loc să potrivești. Id-ul tău extern aparține unui atribut declarat propriu.
Upsert-urile pe obiecte sunt asincrone, scrierile CRM nu sunt
batch/upsert returnează 202 și un processId, apoi aplică mai târziu. Un id inexistent eșuează asincron și tot returnează 202 către apelantul tău. PATCH /v3/companies/{id} returnează 204 și se aplică sincron. Dacă sincronizarea ta raportează succes, doar calea sincronă merită cuvântul fără o citire de confirmare.
O scurtă listă de verificare pentru integrare
- Chei API separate per serviciu și per mediu, rotite la schimbări de personal.
- Toate scrierile trec printr-un singur client care citește anteturile de limită de rată și face backoff la 429.
- Schema de atribute este verificată la pornire, iar sincronizarea refuză să ruleze dacă atributele ei lipsesc.
- 409 la crearea unei companii înseamnă adoptare, nu reîncercare.
- Căile de masă folosesc API-ul de obiecte pentru debit și rutele CRM pentru orice trebuie confirmat.
- Webhook-urile sunt idempotente, în lot, restricționate pe IP și poartă un antet cu secret partajat.
- Sincronizările incrementale de contacte folosesc
modifiedSince, nu parcurgeri ale listei complete.
Construirea și întreținerea acestui strat este muncă reală de inginerie: verificarea schemei, backoff, logica de adopție, reconcilierea. Tajo există ca să o absoarbă, ținând datele Shopify și de comerț sincronizate cu contactele, companiile și evenimentele din Brevo, fără ca cineva să scrie de mână logica de reîncercare și de deduplicare. Dacă totuși le conectezi singur, ghidul de integrare Brevo parcurge alegerile de model de date care vin înaintea codului.
Concluzii cheie
- API-ul este o singură suprafață REST la
https://api.brevo.com/v3/, autentificată cu un antetapi-key, nu cu un token bearer. - Limitele de rată sunt extrem de inegale: trimiterea este practic nemăsurată, în timp ce majoritatea celorlalte endpointuri împart 100 de cereri pe oră pe planurile standard.
- Există SDK-uri oficiale pentru șapte limbaje, dar suprafața HTTP este suficient de simplă încât să o apelezi direct când ai nevoie doar de câteva endpointuri.
- Modul sandbox validează doar formatul cererii, deci ține un cont separat pentru testarea a orice depășește trimiterile.
- Un răspuns 2xx nu dovedește că o scriere s-a aplicat. Atributele nedeclarate sunt eliminate în tăcere, iar upsert-urile pe obiecte sunt asincrone.
- Proiectează în jurul limitelor fixe: o singură companie per domeniu, un milion de înregistrări per tip de obiect, nicio ștergere în masă pentru obiectele standard și filtre pe atribute care nu fac nimic în liniște.