Brevo API: Panduan Praktis untuk Developer
Panduan Brevo API untuk developer: autentikasi, base URL, kontak, email transaksional, kampanye, objek CRM, webhook, rate limit, dan batasan nyata di lapangan.
Brevo menyediakan satu REST API yang mencakup pesan transaksional, kampanye pemasaran, data kontak, dan record CRM. Membuat permintaan pertama Anda mengembalikan 201 hanya butuh sekitar dua menit. Membangun integrasi produksi yang tidak diam-diam kehilangan data butuh waktu jauh lebih lama, karena beberapa batasan yang paling penting tidak terdokumentasi atau justru bertentangan dengan apa yang dilaporkan API tentang dirinya sendiri.
Panduan ini membahas kedua sisi tersebut: endpoint, SDK, dan autentikasi yang Anda butuhkan pada hari pertama, serta batasan platform yang harus Anda antisipasi dalam desain sebelum rilis.
Apa saja yang dicakup Brevo API
Semuanya berada di bawah satu host dan satu path versi. Dokumentasi developer mengelompokkan permukaan API ini ke dalam empat area produk:
- Messaging: email transaksional, SMS, dan WhatsApp, termasuk pengiriman batch, penjadwalan, dan aktivitas pesan.
- Marketing platform: kontak, daftar, segmen, dan kampanye email.
- eCommerce: produk, pesanan, dan pelacakan event pelanggan.
- Conversations: widget chat dan pengelolaan percakapan secara programatik.
Area-area tersebut berbagi satu akun, satu basis data kontak, dan satu API key. Itu praktis sekaligus kadang berbahaya: skrip yang ditulis dengan asumsi data staging sebenarnya berbicara dengan kontak yang sama seperti yang dikirimi kampanye Anda.
Transaksional versus marketing
Kedua keluarga ini berperilaku cukup berbeda sehingga tertukar di antara keduanya adalah kesalahan desain yang paling umum.
| Transaksional | Marketing | |
|---|---|---|
| Endpoint utama | POST /v3/smtp/email | POST /v3/emailCampaigns |
| Penentuan penerima | Penerima eksplisit di dalam permintaan | listIds atau segmentIds |
| Pemicu | Aplikasi Anda, secara real-time | Terjadwal atau dikirim sesuai permintaan |
| Bentuk volume tipikal | Terus-menerus, satu pesan sekali kirim | Melonjak, satu pengiriman besar |
| Posisi rate limit | Sangat tinggi, 1.000 permintaan per detik pada paket standar | Rendah, endpoint kampanye masuk batas umum |
Jika Anda masih mempertimbangkan apakah Brevo memang platform yang tepat, ikhtisar platform membahas hal tersebut.
Autentikasi dan pengelolaan kunci
Brevo memakai API key biasa di dalam header khusus. Nama headernya adalah api-key, bukan Authorization, dan tidak ada awalan Bearer. Hal ini menjebak hampir semua orang yang lebih dulu memakai API pesan lain.
curl https://api.brevo.com/v3/account \ -H "api-key: $BREVO_API_KEY"Kunci dibuat di aplikasi Brevo pada pengaturan akun, di bagian SMTP dan API, pada tab API keys. Beri setiap kunci nama deskriptif yang terkait dengan sistem yang memakainya. Nilai kunci ditampilkan tepat satu kali saat dibuat, jadi jika Anda kehilangannya, Anda membuat kunci baru dan bukan memulihkan yang lama.
Beberapa aturan praktis:
- Terbitkan kunci terpisah per target deployment dan per layanan. Mencabut kunci yang bocor tidak boleh sampai mematikan tiga sistem yang tidak berhubungan.
- API key standar berlaku untuk seluruh akun. Perlakukan kunci apa pun sebagai akses penuh ke kontak, pengiriman, dan data CRM.
- Brevo juga mendukung OAuth 2.0 untuk aplikasi yang bertindak atas nama akun Brevo lain, dijelaskan bersama alur kunci di authentication schemes.
- Server MCP yang dipakai asisten AI memakai token terpisah dan memang menggunakan header bearer. Token itu dibuat di layar API keys yang sama, tetapi tidak dapat dipertukarkan dengan kunci REST.
Base URL, versi, dan penulisan pertama Anda
Base URL-nya adalah https://api.brevo.com/v3/. Versi berada di path, bukan di header, dan v3 adalah generasi yang berlaku saat ini. Setiap path dalam panduan ini bersifat relatif terhadap base tersebut.
Penulisan pertama lebih informatif daripada pembacaan pertama, karena penulisan menguji bagian akun yang biasanya salah konfigurasi (khususnya pengirim terverifikasi):
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"] }'Pengiriman yang berhasil mengembalikan 201 beserta messageId. Pengiriman terjadwal mengembalikan 202.
Endpoint yang benar-benar akan Anda pakai
Kontak
POST /v3/contacts membuat sebuah kontak. Body menerima email, map attributes untuk field kustom, listIds, ext_id untuk kunci eksternal Anda sendiri, dan dua flag yang paling berpengaruh dalam praktik: updateEnabled, yang mengubah panggilan menjadi upsert, dan getId, yang membuat respons mengembalikan id kontak.
Pembacaan dilakukan melalui GET /v3/contacts, yang melakukan paginasi dengan limit (default 50, maksimum 1000) dan offset, serta mendukung modifiedSince dan createdSince dalam UTC. Sinkronisasi inkremental sebaiknya bertumpu pada modifiedSince, bukan menelusuri seluruh daftar. Perhatikan bahwa parameter filter hanya mendukung operator kesamaan, jadi apa pun yang lebih ekspresif sebaiknya diletakkan dalam segmen.
Untuk pemuatan massal, POST /v3/contacts/import menerima fileUrl, fileBody, atau jsonBody, menargetkan listIds, dan berjalan secara asinkron sambil mengembalikan processId. Brevo mendokumentasikan batas body maksimum 10 MB dan menyarankan tetap di sekitar 8 MB karena proses parsing menggelembungkan payload. Sertakan notifyUrl supaya Anda tahu hasilnya tanpa perlu polling.
Email transaksional
POST /v3/smtp/email adalah tulang punggungnya. Selain sender, to, subject, dan htmlContent, field yang perlu Anda ketahui adalah:
templateIdbersamaparams, yang menggantikan konten inline dengan template Brevo beserta substitusi variabelnya. Params per versi dibatasi 100 KB, dan params kumulatif 1000 KB.messageVersions, yang mengirim varian personal dalam satu panggilan, dengan maksimum 99 penerima per versi.tags, yang sebaiknya selalu Anda isi. Tag ikut kembali pada event webhook, dan itulah satu-satunya cara murah untuk mengaitkan event pengiriman dengan jalur kode yang menghasilkannya.scheduledAtbersamabatchId, untuk pengiriman terjadwal yang mungkin ingin Anda batalkan sebagai satu kelompok.headers, dalam Title-Case, untuk header SMTP kustom.
Satu permintaan menerima maksimum 2.000 penerima. Untuk memahami perbedaan endpoint ini dengan pengiriman kampanye, panduan email transaksional menyajikan sudut pandang strategi pesan.
Kampanye email
POST /v3/emailCampaigns memerlukan name dan sender, ditambah tepat satu sumber konten: htmlContent (minimum 10 karakter, di bawah 1 MB), htmlUrl, atau templateId. Audiens masuk ke recipients sebagai listIds atau segmentIds, dan scheduledAt memakai format UTC YYYY-MM-DDTHH:mm:ss.SSSZ. Rute pendamping mencakup pengiriman langsung, pengiriman uji, pembaruan status, dan penarikan laporan kampanye.
Perusahaan, deal, dan objek
CRM Brevo punya dua jalur penulisan yang saling tumpang tindih, dan memilih dengan benar itu penting.
Rute CRM adalah POST /v3/companies, PATCH /v3/companies/{id}, DELETE /v3/companies/{id}, serta set yang setara untuk deal. Rute ini sinkron. Sebuah PATCH mengembalikan 204 begitu perubahan diterapkan.
API objek adalah jalur massalnya: POST /v3/objects/{object_type}/batch/upsert menerima hingga 1.000 record dan 1 MB per permintaan, hingga 500 atribut per record, dan hingga 10 record asosiasi per tipe objek per record. Rute ini mengembalikan 202 beserta processId, yang berarti diterima, bukan diterapkan.
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" } } ] }'Panduan Brevo CRM membahas model objek ini dari sisi operator.
SDK resmi
Brevo memelihara klien di bawah organisasi GitHub getbrevo:
| Bahasa | Repositori |
|---|---|
| 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 |
Klien Node dipasang sebagai @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);Klien Python dipasang dengan pip install brevo-python. Jika Anda lebih suka tidak membawa dependensi SDK hanya untuk dua endpoint, permukaan HTTP mentahnya cukup kecil untuk dipanggil langsung, sekaligus melindungi Anda dari pergantian versi 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 responseTersedia juga server MCP di https://mcp.brevo.com/v1/brevo/mcp untuk asisten AI, diautentikasi dengan bearer token yang dibuat di layar pengaturan yang sama. Server ini berguna untuk eksplorasi dan pertanyaan seputar akun, bukan untuk jalur data produksi.
Webhook
Webhook adalah cara Anda mengetahui apa yang terjadi setelah pengiriman. POST /v3/webhooks membuat satu webhook, dengan url, events, type, dan secara opsional channel (email atau sms), batched, headers kustom, serta objek auth.
Ada tiga tipe webhook dengan kosakata event yang berbeda:
- Transaksional:
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:
inboundEmailProcesseddanreply, yang selain itu juga memerlukandomain.
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" }'Ada tiga hal yang harus benar. Pertama, satu akun hanya dapat menampung maksimum 40 webhook untuk semua tipe, jadi lakukan perutean berdasarkan event di dalam handler Anda, bukan mendaftarkan satu endpoint per event. Kedua, gunakan flag batched ketika Anda mengharapkan volume tinggi, karena satu permintaan yang membawa banyak event jauh lebih murah diproses daripada banyak permintaan. Ketiga, lindungi penerimanya: Brevo memublikasikan rentang IP pengirimnya, dan membatasi endpoint Anda ke rentang tersebut adalah pendekatan yang terdokumentasi. Tambahkan secret bersama Anda sendiri melalui field headers sebagai lapisan kedua.
Handler harus idempoten. Perlakukan kombinasi id pesan, tipe event, dan timestamp sebagai kunci deduplikasi.
Rate limit dan penanganan error
Rate limit Brevo ditetapkan per endpoint dan per tingkat paket, dan rentang antar endpoint sangat lebar.
| Endpoint | Standard | Professional dan 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 | lebih tinggi pada Enterprise |
GET /v3/smtp/emails | 2 RPS, 7.200 RPH | 3 RPS, 10.800 RPH |
| Selebihnya | 100 RPH | 200 RPH |
Baris terakhir itulah yang menyakitkan. Pengiriman praktis tidak dibatasi, sementara pengelolaan kampanye, pembacaan CRM, dan sebagian besar panggilan administratif berbagi anggaran 100 permintaan per jam pada paket standar. Backfill naif yang membaca record perusahaan sebelum setiap penulisan akan menghabiskan kuota satu jam dalam waktu kurang dari dua menit.
Setiap respons membawa x-sib-ratelimit-limit, x-sib-ratelimit-remaining, dan x-sib-ratelimit-reset. Bacalah saat berhasil, bukan hanya saat gagal. Melampaui batas menghasilkan 429, dan respons yang benar adalah menunggu selama interval pada header reset lalu menerapkan exponential backoff dengan 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;}Ulangi permintaan untuk 429 dan 5xx. Jangan pernah mengulang 400 atau 409 secara membabi buta, karena keduanya biasanya berarti permintaannya salah, bukan terlalu cepat, dan 409 khususnya memerlukan tindakan berbeda, bukan pengulangan.
Menguji tanpa mengirim email
Tambahkan header X-Sib-Sandbox dengan nilai drop pada pengiriman transaksional. Brevo memvalidasi permintaan, mengembalikan 201 beserta messageId, tidak mengirimkan apa pun, dan tidak menulis log email.
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>" }'Pahami apa yang dibuktikan dan tidak dibuktikan langkah ini. Mode sandbox hanya memvalidasi format permintaan. Mode ini tidak mengatakan apa pun tentang autentikasi pengirim, rendering template, atau deliverability. Simpan akun Brevo terpisah untuk pengujian integrasi apa pun yang menyentuh kontak atau data CRM, karena mode sandbox mencakup pengiriman dan bukan bagian API lainnya.
Batasan yang membentuk desain integrasi Anda
Berikut adalah batasan yang baru muncul setelah integrasi berjalan terhadap akun nyata dengan volume nyata. Beberapa di antaranya bertentangan dengan apa yang dikatakan API tentang dirinya sendiri. Tidak satu pun bisa dinegosiasikan, jadi satu-satunya respons yang masuk akal adalah merancang di sekitarnya.
Perusahaan wajib punya domain, dan hanya satu perusahaan per domain
GET /v3/crm/attributes/companies melaporkan setiap atribut sebagai tidak wajib, dan referensi create-a-company hanya mencantumkan name sebagai wajib. Pada praktiknya, POST /v3/companies tanpa atribut domain yang tidak kosong mengembalikan 400 dengan pesan tentang atribut default wajib yang hilang. String kosong gagal dengan cara yang sama seperti tidak menyertakannya sama sekali.
Lebih parah lagi, keunikan domain ditegakkan. Perusahaan kedua pada domain yang sudah dipakai mengembalikan 409. Untuk commerce B2B ini bersifat struktural: anak-anak perusahaan yang berbagi satu domain email pembeli tidak bisa semuanya ada sebagai perusahaan terpisah di Brevo. Menyinkronkan sebuah kontak saja sudah cukup untuk memunculkan perusahaan pada domain email kontak tersebut, sehingga sebuah create bisa bentrok dengan perusahaan yang tidak pernah dibuat siapa pun secara eksplisit. Handler yang benar mengadopsi perusahaan yang sudah ada saat menerima 409, bukan gagal atau mengulang.
Atribut yang tidak dideklarasikan dibuang diam-diam
Ini perilaku paling berbahaya di platform tersebut, dan Brevo mendokumentasikannya secara terus terang: jika sebuah atribut muncul dalam permintaan tetapi sebelumnya tidak didefinisikan dalam skema objek, tidak terjadi apa-apa. Tidak ada error, tidak ada pembuatan atribut, tidak ada peringatan.
Karena itu, respons 2xx bukan bukti bahwa data Anda benar-benar masuk. Baca skema sebelum menulis, buang apa pun yang tidak dideklarasikan di klien Anda sendiri, dan tolak menjalankan sinkronisasi yang atributnya tidak ada, alih-alih menulis setengah record selama sebulan sebelum ada yang menyadarinya.
Filter atribut diterima lalu diabaikan
GET /v3/companies?filters[attributes.domain]=... mengembalikan 200 dan mengabaikan filternya. Dua filter yang sepenuhnya berbeda mengembalikan record yang sama. Tidak ada cara yang berfungsi untuk mencari perusahaan berdasarkan atribut melalui rute tersebut.
Dikombinasikan dengan fakta bahwa daftar tanpa filter mengalami timeout 504 pada akun besar di ukuran halaman berapa pun, perusahaan yang sudah ada bisa benar-benar tidak dapat ditemukan lewat jalur yang terdokumentasi. Solusinya adalah memindai GET /v3/objects/company/records dengan sort=desc, yang cepat, terpaginasi, dan mengembalikan atribut, dibatasi pada jumlah halaman yang wajar. Perusahaan yang baru saja memicu 409 hampir selalu dibuat beberapa saat sebelumnya, jadi pemindaian dari yang terbaru akan menemukannya dengan cepat.
Satu juta record per tipe objek, dan tanpa bulk delete
POST /v3/objects/{type}/batch/upsert mengembalikan 400 begitu sebuah tipe objek menampung satu juta record. Ini memblokir pembaruan sekaligus pembuatan: mengalamati record yang sudah ada lewat id numeriknya sendiri pun gagal dengan cara yang sama. Seluruh jalur penulisan objek tertutup sekaligus.
Kembali ke bawah batas itu berjalan lambat, karena POST /v3/objects/{type}/batch/delete mengembalikan 403 untuk tipe objek standar Brevo seperti company. Satu-satunya rute adalah DELETE /v3/companies/{id}, satu record per panggilan pada kisaran 156 ms. Membersihkan 124.000 record dengan cara itu memakan waktu berjam-jam dengan 20 worker paralel. Pantau jumlah record secara terjadwal alih-alih menemukan batasnya lewat sinkronisasi yang gagal, dan arahkan pembaruan bervolume tinggi melalui PATCH /v3/companies/{id}, yang tidak memiliki batas semacam itu.
ext_id adalah id milik Brevo, bukan milik Anda
Pada record objek, identifiers.ext_id menyimpan id perusahaan CRM milik Brevo sendiri, berupa string bergaya Mongo. Ini bukan kunci eksternal bebas. Melakukan upsert dengan kunci ext_id yang diisi identifier platform Anda akan menciptakan duplikat, bukan mencocokkan record. Id eksternal Anda seharusnya diletakkan di atribut tersendiri yang dideklarasikan.
Upsert objek bersifat asinkron, penulisan CRM tidak
batch/upsert mengembalikan 202 dan sebuah processId, lalu menerapkannya kemudian. Id yang tidak ada akan gagal secara asinkron dan tetap mengembalikan 202 ke pemanggil Anda. PATCH /v3/companies/{id} mengembalikan 204 dan diterapkan secara sinkron. Jika sinkronisasi Anda melaporkan sukses, hanya jalur sinkron yang layak menyandang kata itu tanpa pembacaan lanjutan.
Daftar periksa integrasi singkat
- API key terpisah per layanan dan per environment, dirotasi saat ada pergantian staf.
- Semua penulisan melalui satu klien yang membaca header rate limit dan mundur saat menerima 429.
- Skema atribut diverifikasi saat startup, dan sinkronisasi menolak berjalan jika atributnya tidak ada.
- 409 pada create perusahaan berarti adopsi, bukan ulangi.
- Jalur massal memakai API objek untuk throughput dan rute CRM untuk apa pun yang harus dikonfirmasi.
- Webhook bersifat idempoten, batched, dibatasi IP, dan membawa header secret bersama.
- Sinkronisasi kontak inkremental memakai
modifiedSince, bukan penelusuran seluruh daftar.
Membangun dan memelihara lapisan ini adalah pekerjaan rekayasa yang nyata: verifikasi skema, backoff, logika adopsi, rekonsiliasi. Tajo hadir untuk menyerap pekerjaan tersebut, menjaga data Shopify dan commerce tetap sinkron dengan kontak, perusahaan, dan event Brevo tanpa ada yang perlu menulis logika retry dan dedupe secara manual. Jika Anda memilih merangkainya sendiri, panduan integrasi Brevo menelusuri pilihan model data yang harus dibuat sebelum menulis kode.
Poin-poin penting
- API ini adalah satu permukaan REST di
https://api.brevo.com/v3/, diautentikasi dengan headerapi-key, bukan bearer token. - Rate limit sangat tidak merata: pengiriman praktis tidak dibatasi, sementara sebagian besar endpoint lain berbagi 100 permintaan per jam pada paket standar.
- SDK resmi tersedia untuk tujuh bahasa, tetapi permukaan HTTP-nya cukup sederhana untuk dipanggil langsung bila Anda hanya butuh beberapa endpoint.
- Mode sandbox hanya memvalidasi format permintaan, jadi siapkan akun terpisah untuk menguji apa pun di luar pengiriman.
- Respons 2xx tidak membuktikan sebuah penulisan diterapkan. Atribut yang tidak dideklarasikan dibuang diam-diam, dan upsert objek bersifat asinkron.
- Rancang di sekitar batasan tetapnya: satu perusahaan per domain, satu juta record per tipe objek, tidak ada bulk delete untuk objek standar, dan filter atribut yang diam-diam tidak melakukan apa pun.