#Gambaran umum
API ini mengirim email transaksional: kode masuk, tanda terima, dan pemberitahuan tentang akun seseorang. Ini API JSON lewat HTTPS. Setiap permintaan membawa API key Anda; setiap jawaban berupa JSON.
| Alamat dasar | https://api.nexdeliv.com |
|---|---|
| Format | JSON masuk, JSON keluar (UTF-8) |
| Autentikasi | Authorization: Bearer <key Anda> |
| Tanggal dan hari | UTC |
Email massal atau tidak diminta tidak diizinkan; lihat Terms.
#Mulai cepat
- Buat akun di halaman Get started dan pilih nama untuk akun pengiriman Anda. Anda langsung mulai di paket Free: tanpa kartu, sampai 100 email per hari.
- Di dashboard, tambahkan domain tempat Anda mengirim. Pasang tiga catatan DNS yang ditampilkan, lalu pilih “Check again” sampai statusnya Verified.
- Di dashboard, buat API key. Key hanya ditampilkan sekali: simpan sebagai rahasia di aplikasi Anda (misalnya variabel lingkungan NEXDELIV_KEY).
- Kirim email dengan permintaan di bawah. Jawaban 202 berarti email diterima.
#Autentikasi
Kirim key Anda di header Authorization: Authorization: Bearer nxd_… . Key yang hilang, tidak dikenal, atau dinonaktifkan mendapat jawaban yang sama, 401 unauthorized, sehingga tidak ada yang terungkap tentang key mana yang ada.
- Key hanya ditampilkan sekali, saat dibuat. Kami hanya menyimpan hash satu arah darinya.
- Jika key mungkin bocor, ganti di dashboard (lihat API key): yang lama berhenti berfungsi seketika. Batas dan penghitung Anda tetap.
#Mengirim email
POST/v1/email
POST /v1/email mengirim satu pesan. Pesan bisa ke beberapa orang lewat to, cc, dan bcc, paling banyak 10 alamat berbeda; setiap alamat dihitung satu email dari paket Anda.
| Kolom | Wajib | Arti |
|---|---|---|
| to | ya | Sebuah alamat email, atau daftar alamat (paling banyak 10 bersama cc dan bcc). Masing-masing maksimal 254 karakter. |
| cc | tidak | Sebuah alamat atau daftar alamat, terlihat oleh semua penerima. |
| bcc | tidak | Sebuah alamat atau daftar alamat, tersembunyi dari yang lain. |
| subject | ya | Maksimal 200 karakter, satu baris (tanpa pemisah baris). |
| text | ya | Isi teks biasa, maksimal 20.000 karakter. Setiap pesan memerlukannya, juga saat Anda mengirim html. |
| html | tidak | Versi HTML, maksimal 100.000 karakter. |
| from | tidak | Pengirim, sebagai address@domain atau Nama <address@domain>. Harus di domain akun Anda yang sudah terverifikasi. Bila dikosongkan, pengirim bawaan akun dipakai. Nama tampilan maksimal 80 karakter. |
| replyTo | tidak | Sebuah alamat, atau daftar hingga 3, tujuan balasan. |
| headers | tidak | Objek berisi header tambahan, misalnya {"List-Unsubscribe": "<mailto:[email protected]>"}. Hingga 10, hanya ASCII, nilai maksimal 500 karakter. Header yang diatur pesan itu sendiri (From, To, Cc, Bcc, Reply-To, Subject, Date, Message-ID, Content-Type, MIME-Version, Return-Path, dan beberapa lainnya) ditolak. |
| attachments | tidak | File yang dikirim bersama email, di paket yang memilikinya (ketiganya, setelah Anda membayar): daftar hingga 5 objek {"filename": "struk.pdf", "content": "<base64>", "contentType": "application/pdf"}; contentType boleh dikosongkan. Lihat Lampiran di bawah. |
| template | tidak | Nama template tersimpan, sebagai pengganti subject, text, dan html; vars memberi nilai untuk variabelnya. Lihat Template email di bawah. |
| unsubscribe | tidak | true menjadikannya pesan yang bisa dihentikan: ia membawa tautan yang dipakai penerimanya (satu orang) untuk berhenti menerima pesan semacam itu. Lihat Berhenti berlangganan di bawah. |
| track | tidak | {"opens": true, "clicks": true} meminta pembukaan dan klik pada pesan ini diberitahukan ke webhook Anda. Mati kecuali Anda memintanya. Perlu html dan satu penerima. Lihat Pembukaan dan klik di bawah. |
Alamat yang tertulis dua kali (huruf besar atau kecil) dihitung sekali. Jika satu alamat di daftar pernah bounce atau mengeluh, seluruh pesan ditolak dengan 422 recipient_suppressed, jadi hapus alamat itu lalu kirim ulang. Semua kolom juga bisa dicoba dari dashboard, di menu Kirim uji.
curl -X POST https://api.nexdeliv.com/v1/email \
-H "Authorization: Bearer $NEXDELIV_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "Your App <[email protected]>",
"to": ["[email protected]", "[email protected]"],
"cc": "[email protected]",
"bcc": ["[email protected]"],
"replyTo": ["[email protected]"],
"subject": "Your invoice",
"text": "Your invoice is attached to your account.",
"headers": {
"List-Unsubscribe": "<mailto:[email protected]>",
"X-Campaign": "invoices"
}
}'curl -X POST https://api.nexdeliv.com/v1/email \
-H "Authorization: Bearer $NEXDELIV_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "Your App <[email protected]>",
"to": "[email protected]",
"subject": "Your sign-in code",
"text": "Your code is 482913.",
"html": "<p>Your code is <strong>482913</strong>.</p>",
"replyTo": "[email protected]"
}'HTTP/1.1 202 Accepted
Content-Type: application/json
{ "id": "010e01a17b5ad26e-10055656-9b2f-4b83-8369-48e48a22c42c-000000" }id adalah ID pesan. Simpan bila Anda ingin mencocokkan kejadian webhook dengan catatan Anda: setiap kejadian webhook membawa messageId yang sama.
Idempotency-Key (header opsional): jika Anda tidak menerima jawabannya (timeout, koneksi terputus), kirim permintaan yang sama lagi dengan Idempotency-Key yang sama, 1 sampai 255 karakter ASCII yang dapat dicetak (UUID boleh), dan pesan tidak akan terkirim dua kali. Dalam 24 jam, kunci yang sama dengan isi yang sama mengembalikan 202 yang asli dengan id yang sama dan header Idempotent-Replayed: true, dan tidak ada yang dikirim atau dihitung lagi. Kunci yang sama dengan isi berbeda dijawab 422 idempotency_key_reused; kunci yang sama saat permintaan pertama masih berjalan dijawab 409 idempotency_in_progress (tunggu satu detik). Jika sebuah permintaan gagal (misalnya 502 send_failed), kunci itu bebas lagi dan percobaan ulang akan mengirim. Pakai kunci baru untuk setiap pesan baru.
#Template email
Simpan subjek dan isi email yang sering Anda kirim dengan sebuah nama, dengan {{variabel}} di tempat nilai akan masuk, lalu kirim dengan template dan vars tanpa menulis pesan setiap kali. Template dibuat dan diubah di dashboard, di bagian Template (atau dengan route di bawah, masuk dengan akun Anda, bukan dengan API key).
curl -X POST https://api.nexdeliv.com/v1/email \
-H "Authorization: Bearer $NEXDELIV_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "Your App <[email protected]>",
"to": "[email protected]",
"template": "welcome",
"vars": { "name": "Arif", "code": "482913" }
}'| Field | Arti |
|---|---|
| template | Nama template di akun Anda: huruf kecil, angka, dan tanda hubung, 1 sampai 64 karakter. Tidak boleh digabung dengan subject, text, atau html (400 invalid_template_use). to, cc, bcc, from, replyTo, headers, attachments, unsubscribe, dan track bekerja seperti biasa. |
| vars | Objek berisi nilai untuk variabel: {"name": "Arif"}. Nilainya teks, angka, atau true/false. Hingga 50 variabel, nilai hingga 2.000 karakter. Nama variabel berupa huruf, angka, dan garis bawah. |
- Variabel ditulis {{name}}, persis seperti itu (tanpa spasi di dalamnya). Ia diganti dengan nilainya, satu kali: tidak ada perulangan, kondisi, atau include, dan nilai yang sendirinya memuat {{x}} disisipkan apa adanya.
- Di html nilai di-escape (< menjadi <), jadi nama atau teks dari pengguna Anda tidak bisa menambah markup. Di subjek dan teks nilai disisipkan apa adanya, dan nilai dengan baris baru di sana ditolak (400 invalid_vars), karena baris baru bisa menambah header atau menulis ulang pesan.
- Setiap variabel yang dipakai template harus ada di vars, kalau tidak jawabannya 400 missing_variable. Variabel di vars yang tidak dipakai template diabaikan. Template yang tidak dikenal dijawab 404 template_not_found.
- Hanya {{name}} yang ditulis persis seperti itu yang menjadi variabel. Tulisan yang mirip tetapi bukan ({{ name }} dengan spasi, {{first-name}} dengan tanda hubung) akan terkirim apa adanya, jadi template yang memuatnya ditolak saat disimpan (400 invalid_placeholder). Pesan yang dikirim tanpa template juga ditolak bila subjek, teks, atau html-nya memuat {{...}} (400 unfilled_placeholder), karena variabel hanya diisi untuk template; kirim "raw": true bila kurung kurawal itu memang dimaksudkan.
- Satu template satu bahasa. Untuk pesan dalam beberapa bahasa, simpan satu template untuk tiap bahasa dengan bahasa di akhir nama (welcome-en, welcome-id), dan biarkan aplikasi Anda memilih namanya.
- {{unsubscribe_url}} bukan variabel. Tulis di template seperti biasa dan kirim dengan "unsubscribe": true: kami mengisinya dengan tautan, seperti pada pesan lain. Ia tidak bisa diisi lewat vars (400 invalid_vars).
- Pesan hasil template diperiksa seperti pesan lain: subjek hingga 200 karakter satu baris, teks hingga 20.000, dan html hingga 100.000, setelah nilai dimasukkan. Batas paket Anda dan daftar supresi berlaku seperti biasa.
- Jumlah template yang bisa Anda simpan mengikuti paket: Starter 10, Basic 50, Pro 200, Free 3. Template punya subjek (hingga 200 karakter), teks, dan html bila Anda mau, dengan hingga 50 variabel berbeda.
Route di balik dashboard, untuk alat Anda sendiri (token masuk akun, bukan API key; akun yang dimatikan mendapat 403 account_disabled):
| Route | Fungsi |
|---|---|
| GET /v1/account/templates | Nama dan subjek template Anda, dan berapa banyak yang diizinkan paket Anda. |
| GET /v1/account/templates/{name} | Satu template dengan teks, html, dan variabel yang dipakainya. |
| PUT /v1/account/templates/{name} | Membuat atau mengganti template. Body: {"subject": "...", "text": "...", "html": "..."} (html boleh dihilangkan). 409 templates_limit bila akan melebihi jumlah yang diizinkan paket Anda. |
| DELETE /v1/account/templates/{name} | Menghapus template. Pesan yang menyebut namanya setelah itu dijawab 404 template_not_found. |
#Lampiran
Setiap paket berbayar bisa mengirim file bersama email (paket Free tidak bisa: belum ada yang dibayar, dan file adalah jalur phishing dan malware). Taruh di attachments, masing-masing berupa nama dan isi dalam base64. Isi email (text, dan html bila ada) tetap sama; file dikirim sebagai lampiran biasa.
| Kolom | Wajib | Arti |
|---|---|---|
| filename | ya | Nama yang dilihat penerima, dengan ekstensinya (struk.pdf). Maksimal 255 karakter, tanpa / \ : * ? " < > | dan tanpa pemisah baris. |
| content | ya | File dalam base64 (alfabet standar). Tidak boleh kosong. |
| contentType | tidak | Jenis media, misalnya application/pdf. Bila dikosongkan, mengikuti ekstensi. |
- Maksimal 5 file, dan 4 MB file seluruhnya untuk satu permintaan (ukuran file, sebelum base64).
- Jenis file yang ditolak sistem pengiriman kami (program dan skrip seperti .exe, .bat, .js, .vbs, .msi, .ps1) dijawab 400 attachment_type_not_allowed.
- Setiap paket punya volume file per bulan (UTC), dihitung untuk tiap penerima, karena setiap salinan yang terkirim ada biayanya: Starter 1 GB, Basic 3 GB, Pro 10 GB. Lewat dari itu, 429 attachments_limit; email tanpa file tidak terhenti olehnya. Selama trial gratis tidak ada lampiran: 403 attachments_not_allowed.
- Kami tidak menyimpan file-nya, dan riwayat hanya menunjukkan alamat, waktu, dan status.
curl -X POST https://api.nexdeliv.com/v1/email \
-H "Authorization: Bearer $NEXDELIV_KEY" \
-H "Content-Type: application/json" \
-d "{
"from": "Your App <[email protected]>",
"to": "[email protected]",
"subject": "Your receipt",
"text": "Your receipt is attached.",
"attachments": [{ "filename": "receipt.pdf", "content": "$(base64 < receipt.pdf | tr -d '\n')" }]
}"#Berhenti berlangganan
Untuk pesan yang mungkin ingin dihentikan orang (kabar, pengingat, pemberitahuan yang bukan tentang akun orang itu sendiri), kirim "unsubscribe": true. Kami menambahkan header List-Unsubscribe (dengan List-Unsubscribe-Post, supaya Gmail dan lainnya menawarkan satu klik) berisi tautan kami, dan menaruh tautan yang sama di isi pesan di mana pun Anda menulis {{unsubscribe_url}}, di teks maupun html.
curl -X POST https://api.nexdeliv.com/v1/email \
-H "Authorization: Bearer $NEXDELIV_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "Your App <[email protected]>",
"to": "[email protected]",
"subject": "What is new this month",
"text": "Hello! Here is what is new.\n\nStop getting these emails: {{unsubscribe_url}}",
"html": "<p>Hello! Here is what is new.</p><p><a href=\"{{unsubscribe_url}}\">Stop getting these emails</a></p>",
"unsubscribe": true
}'- Satu penerima: pesan seperti ini hanya punya satu alamat di to, tanpa cc dan bcc, karena tautannya milik orang itu. Beberapa orang, beberapa permintaan.
- Tautan membuka halaman kecil yang meminta satu konfirmasi (pemindai email yang membuka tautan tidak bisa membuat siapa pun berhenti). Orang itu lalu masuk daftar suppression Anda dengan alasan unsubscribe, dan tidak keluar dengan sendirinya. Anda melihatnya di menu Supresi, tempat alamat dengan alasan itu bisa dihapus manual di paket yang mengelola daftarnya.
- Yang dihentikan hanya pesan yang dikirim dengan "unsubscribe": true. Kode masuk, tanda terima, dan semua pesan lain tanpa kolom itu tetap sampai ke alamat yang sama. Pesan seperti itu ke orang yang sudah berhenti dijawab 422 recipient_unsubscribed dan tidak dihitung. Alamat yang bounce ditahan untuk semua pesan, dengan 422 recipient_suppressed.
- Jangan mengisi List-Unsubscribe atau List-Unsubscribe-Post sendiri di headers saat memakai ini: dijawab 400 invalid_unsubscribe. Tanpa kolom itu, headers bekerja seperti biasa, untuk tautan milik Anda sendiri.
- Untuk tahu saat itu terjadi, pilih kejadian email.unsubscribed di menu Webhook di dashboard (lihat Webhook). Dikirim sekali, pada saat pertama.
#Pembukaan dan klik
Untuk pesan yang perlu diketahui apakah dibaca atau tautan mana yang diikuti (kabar, tanda terima dengan tautan bayar), kirim "track": {"opens": true, "clicks": true}, atau salah satunya. Lalu pilih kejadian email.opened dan email.clicked di menu Webhook di dashboard, dan masing-masing diberitahukan ke webhook Anda.
curl -X POST https://api.nexdeliv.com/v1/email \
-H "Authorization: Bearer $NEXDELIV_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "Your App <[email protected]>",
"to": "[email protected]",
"subject": "Your receipt",
"text": "Your receipt: https://your-domain.com/receipts/123",
"html": "<p>Your receipt is <a href=\"https://your-domain.com/receipts/123\">here</a>.</p>",
"track": { "opens": true, "clicks": true }
}'- Mati untuk setiap pesan yang tidak memintanya, dan sebaiknya dibiarkan mati untuk kode masuk, reset kata sandi, dan semua pesan yang tautannya harus persis seperti yang Anda tulis: dengan klik menyala, sistem pengiriman kami mengirim setiap tautan di html lewat servernya sendiri sebelum sampai ke server Anda, yang menambah satu langkah dan bisa membuat tautan tampak kurang tepercaya.
- Perlu html (pembukaan adalah gambar satu piksel di html, klik adalah tautan di html; teks biasa tidak dilacak) dan satu penerima (tautan sebuah pesan sama untuk semua yang menerimanya, jadi klik tidak bisa mengatakan siapa yang mengklik). Selain itu dijawab 400 invalid_track.
- Pembukaan hanyalah petunjuk. Banyak program email memuat gambar untuk orangnya sebelum dibaca, atau menyembunyikan kapan dibaca, dan sebagian tidak memuatnya sama sekali. Klik lebih baik, tetapi pemindai email mengikuti setiap tautan: bot bernilai true bila kami mengira sebuah program yang melakukannya.
- Anda menerima alamat, waktu, dan untuk klik, tautan seperti yang Anda tulis. Alamat IP dan perangkat orangnya tidak pernah kami teruskan, dan tidak ada yang kami simpan.
- Tautan yang tidak ditulis sebagai alamat yang ter-encode dengan benar (misalnya spasi di query) tidak dilacak. Tautan berhenti berlangganan yang kami tambahkan (lihat Berhenti berlangganan) dikecualikan dari tracking.
#Jawaban dan error
Masalah selalu berupa status HTTP dan isi JSON dengan kode error, misalnya {"error":"invalid_to"}. Hanya 202 yang berarti pesan diterima untuk dikirim.
| Status | error | Yang dilakukan |
|---|---|---|
| 202 | (tidak ada; isinya id) | Diterima. Pengiriman atau bounce dilaporkan kemudian lewat webhook. |
| 400 | invalid_body, invalid_to, invalid_cc, invalid_bcc, too_many_recipients, invalid_subject, invalid_text, invalid_html, invalid_reply_to, invalid_headers, invalid_from, invalid_attachments, attachment_type_not_allowed, attachments_too_large, invalid_unsubscribe, invalid_track, invalid_template_use, invalid_vars, missing_variable, unfilled_placeholder | Permintaan salah. Perbaiki; jangan kirim ulang apa adanya. |
| 404 | template_not_found | Template yang disebut di template tidak ada di akun Anda. Kode 400 terkait: invalid_template_use (template bersama subject, text, atau html), missing_variable (template memakai variabel yang tidak ada di vars), dan invalid_vars (nilai yang tidak diizinkan; lihat Template email). |
| 401 | unauthorized | Key hilang, salah, atau akun dimatikan. |
| 403 | from_not_allowed, attachments_not_allowed | from_not_allowed: pengirim tidak berada di domain akun Anda. attachments_not_allowed: akun Anda di paket Free, atau tidak punya paket yang berlampiran. Starter, Basic, dan Pro punya setelah dibayar. |
| 422 | recipient_suppressed | Sebuah alamat di to, cc, atau bcc pernah bounce permanen atau mengajukan keluhan, jadi tidak dikirimi lagi. Tidak ada yang dihitung ke batas Anda. |
| 423 | sending_held | Pengiriman dijeda di akun Anda karena bounce atau keluhan 7 hari terakhir tinggi (5 persen atau lebih, atau 0,1 persen atau lebih, dengan minimal 50 jawaban). Tidak ada yang dikirim atau dihitung. Kami mengirim email saat ini terjadi. Bersihkan daftar Anda (daftar suppression menunjukkan yang bounce), lalu tulis ke [email protected]; jeda juga lepas sendiri, paling cepat setelah sehari, begitu angkanya di bawah batas lagi. Jangan coba ulang terus-menerus. |
| 422 | recipient_typo, recipient_invalid_domain | Domain sebuah alamat di to, cc, atau bcc adalah salah ketik yang dikenal dari penyedia umum (recipient_typo, misalnya gmial.com), atau tidak ada / tidak menerima email (recipient_invalid_domain: tanpa record MX, tanpa record alamat, atau null MX). Isi jawaban juga memuat address dan, bila bisa ditebak, suggestion (alamat yang kemungkinan dimaksud). Tidak ada yang dikirim atau dihitung. Kami hanya memeriksa domainnya; alamat yang tidak ada di domain yang nyata tetap diterima dan bisa bounce. Jika pemeriksaan DNS kami tidak mendapat jawaban, pesan tetap diteruskan. |
| 422 | recipient_unsubscribed | Satu-satunya penerima pesan yang dikirim dengan "unsubscribe": true sudah meminta berhenti. Tidak ada yang dihitung ke batas Anda. Pesan tanpa kolom itu tetap sampai ke alamat tersebut. |
| 402 | credit_empty | Paket sudah berakhir dan email prabayar tidak cukup untuk permintaan ini (sisanya lebih sedikit dari jumlah penerima, atau saldo habis masa berlakunya saat permintaan diproses). Beli lagi di dashboard. |
| 402 | plan_expired | Bulan yang Anda bayar berakhir lebih dari tiga hari lalu. Pengiriman mati sampai Anda membayar lagi di dashboard. |
| 402 | plan_canceled | Langganan dibatalkan. Tidak ada yang terkirim sampai paket diaktifkan lagi di dashboard. |
| 429 | rate_limited, busy, monthly_limit, daily_limit, free_capacity, attachments_limit | rate_limited: Anda mengirim lebih banyak permintaan daripada yang diizinkan paket: rata-rata sekian permintaan per detik, dengan lonjakan 5 kali lipatnya sekaligus. Tunggu (header Retry-After berisi berapa detik sampai jendela berikutnya) lalu kirim lagi. busy: semua pelanggan bersama-sama sedang di kecepatan yang diizinkan penyedia email kami; tunggu satu detik lalu kirim lagi, tidak ada yang terhitung. monthly_limit: Anda menghabiskan jatah email paket untuk bulan ini (UTC); beli paket tambahan atau pindah ke paket lebih besar di bagian Paket dashboard, atau coba lagi tanggal 1 (lihat “Kehabisan email”). daily_limit: hanya pada paket Free, yang punya batas per hari UTC sebagai ganti batas bulanan. free_capacity: semua akun gratis berbagi kuota email per hari UTC dan kuota itu penuh; header Retry-After berisi berapa detik sampai pukul 00:00 UTC, saat kuota dibuka lagi. Email prabayar dan semua paket berbayar tidak termasuk kuota itu. attachments_limit: file bulan ini (UTC) akan melewati volume paket Anda; tidak ada yang dihitung atau dikirim. |
| 503 | unsubscribe_not_set_up | Pesan dengan "unsubscribe": true dikirim saat tautannya belum disiapkan di sisi kami. Tulis kepada kami. |
| 400 | invalid_idempotency_key | Header Idempotency-Key kosong, lebih dari 255 karakter, atau berisi karakter yang bukan ASCII yang dapat dicetak. |
| 409 | idempotency_in_progress | Permintaan pertama dengan Idempotency-Key ini masih berjalan. Tunggu satu detik (Retry-After: 1) lalu kirim permintaan yang sama lagi. |
| 422 | idempotency_key_reused | Idempotency-Key ini sudah dipakai dalam 24 jam terakhir dengan isi yang berbeda. Pakai kunci baru untuk pesan yang berbeda. |
| 502 | send_failed | Penyedia email menolak atau gagal. Mencoba lagi nanti masuk akal; domain yang belum terverifikasi juga menyebabkannya. |
| 504 | send_unconfirmed | Penyedia email mungkin sudah menerima pesan dan jawabannya hilang (timeout, atau gangguan di sisi mereka). Jangan kirim ulang tanpa memeriksa penerimanya: percobaan ulang bisa mengirimnya dua kali. Pengiriman tidak dikembalikan ke paket Anda. Idempotency-Key yang sama dijawab 504 yang sama dan tidak mengirim apa pun. |
| 500 | internal | Masalah di sisi kami. Coba lagi nanti. |
Kirim ulang hanya 429 (setelah batas direset), 500, dan 502. Jangan pernah kirim ulang 4xx apa adanya.
#Domain pengirim
Anda mengirim dari domain sendiri, sehingga email membawa nama Anda dan reputasi tetap milik Anda. Di dashboard, tambahkan domain; kami menampilkan tiga catatan CNAME untuk DNS domain itu (hanya DNS, tanpa proxy). Setelah ditemukan, domain berstatus Verified; ini bisa memakan beberapa menit sampai sehari.
Setelah domain terverifikasi, Anda bisa mengirim dari alamat apa pun di domain itu, misalnya [email protected]. Paket Anda menentukan berapa banyak domain yang bisa ditambahkan.
#Dashboard
Masuk untuk mengelola akun Anda. Menu di sebelah kiri membuka satu bagian dalam satu waktu. Alamatnya mengingat bagian yang terbuka (misalnya #history), jadi tautan atau muat ulang membawa Anda kembali ke tempat yang sama.
| Bagian | Gunanya |
|---|---|
| Ringkasan | Paket Anda dan sampai kapan dibayar, email terkirim hari ini dan bulan ini terhadap batas Anda, dan permintaan pertama untuk disalin. |
| Paket | Daftar paket, serta membayar atau memperpanjang. |
| Domain | Tambah domain pengirim dan lihat catatan DNS yang perlu dipasang. |
| API key | Ganti key utama, serta tambah atau hapus key untuk aplikasi Anda. |
| Riwayat | Pesan yang Anda kirim dan hasil untuk setiap penerima. |
| Webhook | Tempat kami memberi tahu server Anda apa yang terjadi pada setiap pesan. |
| Template | Simpan subjek dan isi email dengan {{variabel}}, lihat pratinjaunya, dan kirim uji. |
| Kirim uji | Tulis dan kirim email sungguhan dari dashboard. |
| Keamanan | Aktifkan masuk dua langkah dengan aplikasi authenticator. |
Jika Anda lupa kata sandi, pilih “Lupa kata sandi?” di halaman masuk: kami mengirim kode ke alamat email Anda, lalu Anda memilih kata sandi baru dengannya.
#Masuk dua langkah
Masuk dua langkah melindungi akun Anda bahkan bila ada yang tahu kata sandi Anda: setelah kata sandi, Anda memasukkan kode 6 angka yang hanya bisa ditampilkan ponsel Anda. Ini pilihan Anda; tidak ada yang diminta kode sampai mengaktifkannya.
- Buka bagian Keamanan dan pilih “Aktifkan dengan aplikasi authenticator”. Pasang aplikasi authenticator di ponsel (Google Authenticator, Authy, Microsoft Authenticator, 1Password …), pindai kode QR, masukkan kode yang tampil di aplikasi, dan selesai. Jika tidak bisa memindai, ketik kunci yang tampil di bawah kode.
- Simpan kunci itu juga di pengelola kata sandi: kunci hanya tampil sekali, dan dengannya Anda bisa memasang ponsel baru sendiri.
- Saat mengaktifkannya, Anda mendapat 10 kode pemulihan, hanya tampil sekali. Simpan di tempat Anda menyimpan kata sandi (pengelola kata sandi atau cetakan), bukan di ponsel yang sama dengan aplikasinya. Setiap kode berlaku sekali.
- Setelah itu, masuk meminta kata sandi lalu kode dari aplikasi. Kode berganti tiap 30 detik, jadi jam ponsel harus tepat (waktu otomatis).
- Untuk ganti ponsel, pilih “Pakai ponsel baru” di Keamanan dan pindai kode yang baru. Untuk berhenti memakainya, pilih “Matikan”.
- Ponsel hilang? Di halaman masuk pilih “Pakai kode pemulihan”, lalu isi email, kata sandi, dan satu kode pemulihan. Masuk dua langkah lalu dimatikan dan Anda masuk; aktifkan lagi di Keamanan dan Anda mendapat kode baru. Kode yang tersisa berhenti berlaku.
- Ponsel hilang dan tidak ada kode pemulihan tersisa? Tulis ke [email protected] dari alamat email akun Anda: kami mematikan masuk dua langkah untuk Anda, lalu Anda bisa masuk dan mengaturnya lagi.
- Di Keamanan Anda bisa melihat berapa kode yang tersisa dan membuat kode baru; kode baru menggantikan yang lama. Lima percobaan salah untuk satu alamat disusul menunggu hingga 15 menit.
Kode lewat email sebagai langkah kedua tidak tersedia. Masuk dua langkah berlaku untuk masuk di situs web, bukan untuk API: API key Anda tidak terpengaruh.
#API key
Paket Anda menentukan berapa key yang boleh ada bersamaan, key utama termasuk. Pakai satu key untuk setiap aplikasi atau lingkungan, agar Anda bisa menghapus satu tanpa menyentuh yang lain.
| Paket | API key |
|---|---|
| Starter | 3 |
| Basic | 5 |
| Pro | 10 |
| Paket Free | 1 |
- Key utama bisa diganti, tidak bisa dihapus. Yang lama berhenti berfungsi seketika.
- Di bagian API key, “Tambah key” meminta nama (misalnya production atau staging) dan menampilkan key-nya sekali. Key tambahan bisa dihapus kapan saja, dan berhenti berfungsi seketika.
- Semua key sebuah akun mengirim sebagai akun yang sama: domain, batas, dan penghitung yang sama. Dua key tidak memberi Anda dua set batas.
- Key tidak pernah ditampilkan lagi. Jika hilang, ganti atau tambah yang lain.
#Riwayat pengiriman
Bagian Riwayat mendaftar pesan yang Anda kirim, yang terbaru dulu: kapan, dari alamat mana, ke siapa (to, cc, dan bcc), dan hasil untuk setiap penerima. Ini untuk orang yang bertanya “apakah email saya sampai?”. Untuk bereaksi di program Anda sendiri, pakai webhook.
| Status | Arti |
|---|---|
| Diterima | Kami menyerahkan pesan ke penyedia email kami. Belum ada kabar lain. |
| Terkirim | Server email penerima menerima pesan. |
| Bounce | Pesan tidak bisa diantar. Bounce permanen juga memasukkan alamat ke daftar supresi Anda. |
| Keluhan | Penerima menandai pesan sebagai spam. Alamat masuk daftar supresi Anda. |
| Ditolak | Penyedia email kami menolak pesan. |
| Paket | Hari riwayat disimpan |
|---|---|
| Starter | 7 |
| Basic | 14 |
| Pro | 30 |
| Paket Free | 1 |
- Kami menyimpan waktu, alamat pengirim, alamat penerima, ID pesan, dan status tiap penerima. Untuk pesan yang dikirim dengan tracking, kami juga menyimpan apakah dan kapan pesan pertama kali dibuka dan tautannya pertama kali diikuti (ditandai terpisah bila tampaknya program yang melakukannya), tampil sebagai Dibuka dan Diklik; tidak pernah tautan atau perangkatnya. Kami tidak pernah menyimpan subjek, isi, atau header.
- Setelah hari-hari paket Anda, entri terhapus sendiri, dan dashboard hanya menampilkan hari-hari itu.
- Anda bisa menyaring menurut status dan mencari alamat, pengirim, atau ID pesan. Saringan bekerja pada pesan yang sudah dimuat di halaman; pilih “Muat pesan lebih lama” untuk mundur lebih jauh.
- Pesan yang dikirim sebelum riwayat ada tidak tercatat di dalamnya. Akun yang dijeda tetap bisa melihat riwayatnya.
#Kirim email uji
Bagian Kirim uji menulis email sungguhan dan mengirimnya sebagai akun Anda, seperti yang dilakukan aplikasi Anda: From, To, Cc, Bcc, Reply-To, subjek, isi teks, isi HTML opsional, dan header tambahan (satu per baris, berbentuk Nama: nilai). Di bawah form tampil pesan yang sama sebagai perintah curl, jadi yang berhasil di sana bisa disalin ke kode Anda.
- Ini email sungguhan. Dihitung ke paket Anda, satu email untuk setiap penerima, dan muncul di riwayat Anda.
- From harus alamat di domain terverifikasi akun Anda. To, Cc, dan Bcc bersama menampung hingga 10 alamat berbeda; pisahkan dengan koma atau spasi.
- Aturannya sama dengan API: batas, daftar supresi, dan error sama, dan jawabannya menjelaskan dengan kata-kata apa yang perlu diperbaiki.
- Untuk melihat hasil sebuah uji, kirim ke alamat Anda sendiri lalu buka bagian Riwayat: statusnya berubah dari Diterima menjadi Terkirim dalam beberapa detik.
#Membayar, memperpanjang, dan berakhir
Akun baru dimulai di paket Free, tanpa kartu, dan tetap bisa mengirim dengan batas lebih kecil daripada paket berbayar (lihat “Batas dan paket”). Untuk lebih banyak, pilih paket di bagian Paket: “Tingkatkan” untuk paket lebih tinggi, “Perpanjang” untuk paket yang Anda punya.
- Anda membayar dalam rupiah, online: QRIS, virtual account, atau e-wallet, lewat Mayar. Kami tidak pernah melihat atau menyimpan nomor kartu.
- Paket aktif sendiri dalam semenit setelah pembayaran, dan kami mengirim email konfirmasi berisi paket, jumlah, tanggal sampai kapan dibayar, dan referensi.
- Paket berlaku 30 hari sejak pembayaran. Membayar paket yang sama lagi sebelum berakhir menambah 30 hari pada tanggal berakhir. Anda juga bisa membayar setahun sekaligus: harganya sepuluh bulan, jadi dua bulan gratis, dan berlaku 365 hari. Tidak ada yang diperpanjang otomatis.
- Membayar paket lain saat hari berbayar Anda belum habis tidak membuat hari itu hilang. Sisa hari Anda bernilai sebesar yang Anda bayar untuknya, dan nilai itu ditambahkan ke 30 hari paket baru sebagai hari yang bisa dibelinya di sana: di paket yang lebih tinggi jumlahnya jadi lebih sedikit, di paket yang lebih murah lebih banyak. Anda membayar harga normal paket baru, batasnya langsung berlaku, dan bagian Paket serta email konfirmasi menyebut berapa hari yang ditambahkan. Misalnya, dengan sisa 18 hari di Basic, membayar Pro memberi 30 harinya ditambah sekitar 6 hari.
- Tiga hari setelah tanggal berakhir, pengiriman berhenti (API menjawab 402 plan_expired) sampai Anda membayar lagi. Tidak ada yang diperpanjang otomatis dan tidak ada yang ditagih tanpa Anda meminta. Domain dan key Anda tetap ada.
Ada pertanyaan soal pembayaran? Tulis ke [email protected] dengan referensi (NX-…) yang tampil di bagian Paket.
#Kehabisan email: paket tambahan
Email paket Anda dihitung per bulan UTC. Saat 80% terpakai kami menulis ke orang yang masuk ke akun, dan sekali lagi saat 100%. Jika Anda butuh lebih banyak sebelum tanggal 1, bagian Paket di dashboard menawarkan dua cara:
- Paket tambahan: email tambahan untuk sisa bulan ini, dibeli sekali saja, tanpa ganti paket. Yang dijual: +10.000 email seharga Rp89.000 dan +50.000 email seharga Rp399.000. Boleh membeli lebih dari satu; jumlahnya bertambah.
- Paket yang lebih tinggi, yang juga menyimpan sisa hari Anda (lihat “Membayar, memperpanjang, dan berakhir”).
Email prabayar, untuk saat Anda tidak punya paket: 5.000 email seharga Rp49.000, dibeli sekali dari dashboard. Ketika paket Anda sudah berakhir, API mengirim dengan saldo ini dan tidak menjawab 402 (di paket Free: melewati 100 per hari): setiap penerima mengambil satu email dari saldo, dengan fitur Starter (lampirannya, riwayat 7 hari, 10 template, 2 domain dan 3 key, dan tanpa batas harian), dan tidak termasuk kuota bersama akun Free. Saldo tidak direset tiap bulan dan berlaku 6 bulan sejak pembelian terakhir Anda; membeli lagi menambah sisa dan memajukan akhir seluruh saldo 6 bulan. Yang tidak terpakai di akhir hangus dan tidak dikembalikan. Akun dengan paket berbayar yang sedang berjalan tidak memakainya: saldo menunggu. Kami menulis kepada Anda 30 dan 7 hari sebelum berakhir.
Cara kerja paket tambahan:
- Untuk akun dengan paket berbayar yang sedang berjalan. Paket Free, paket yang sudah berakhir, dan akun tanpa paket membeli paket dulu.
- Anda membayar seperti membayar paket (QRIS, virtual account, atau e-wallet). Dalam semenit setelah pembayaran, email ditambahkan ke batas bulan ini, dan kami mengirim email konfirmasi berisi jumlah email, nominal, dan referensi.
- Email itu ditambahkan di atas batas paket Anda, dan email yang sudah Anda kirim bulan ini tetap terhitung. Jika bulan berganti saat Anda membayar, email itu berlaku untuk bulan saat pembayaran diterapkan.
- Bisa dikirim sampai hari terakhir bulan itu (UTC). Sisanya hangus setelah itu: tidak dibawa ke bulan berikutnya, dan mulai tanggal 1 hanya batas paket Anda yang berlaku. Lihat tanggalnya sebelum membayar, terutama di hari-hari terakhir bulan.
- Tidak mengubah hal lain: bukan paket Anda, tanggal sampai kapan dibayar, domain, key, atau permintaan per detik. Batas harian juga tidak berubah (hanya paket Free yang punya).
Paket tambahan lebih mahal per email daripada paket biasa, jadi kalau Anda butuh lebih banyak setiap bulan, paket yang lebih tinggi lebih murah. Ada pertanyaan? Tulis ke [email protected] dengan referensi (NX-…) yang tampil di bagian Paket.
#Batas dan paket
Yang membatasi pengiriman Anda adalah jumlah email per bulan UTC pada paket Anda, dan seberapa cepat Anda mengirim: beberapa permintaan per detik, cukup untuk kode masuk dan tanda terima sebuah aplikasi. Paket berbayar tidak punya batas per hari. Saat batas tercapai, API menjawab 429 dan tidak mengirim apa pun; tidak ada tagihan di luar paket. Dashboard menunjukkan pemakaian Anda.
| Paket | Email per bulan | Domain pengirim | API key |
|---|---|---|---|
| Starter | 10.000 | 2 | 3 |
| Basic | 50.000 | 5 | 5 |
| Pro | 130.000 | 10 | 10 |
| Paket Free | tanpa batas bulanan, 100 per hari | 1 | 1 |
| Paket | Permintaan per detik, rata-rata | Sekaligus (lonjakan 5 detik) |
|---|---|---|
| Starter | 2 | 10 |
| Basic | 5 | 25 |
| Pro | 10 | 50 |
| Paket Free | 1 | 5 |
Itu jumlah permintaan yang kami terima dari akun Anda: laju adalah rata-ratanya, dan dalam setiap jendela 5 detik Anda boleh mengirim sebanyak itu kali 5 permintaan, semuanya di detik pertama jika mau. Jadi kode masuk beberapa orang yang mendaftar bersamaan diterima sekaligus; yang melewati lonjakan harus menunggu jendela berikutnya. Semua pelanggan juga berbagi kapasitas layanan: saat kapasitas penuh sesaat, jawabannya 429 busy bagi siapa pun yang meminta berikutnya, dan tidak ada yang dihitung.
Satu permintaan adalah satu pesan. Setiap penerimanya (di to, cc, dan bcc; alamat yang tertulis dua kali dihitung sekali) dihitung satu email dari paket Anda.
Bila terlampaui, jawabannya menyebut batas yang mana (429 rate_limited, busy, monthly_limit, atau daily_limit), dan permintaan itu tidak dikirim dan tidak dihitung. Untuk rate_limited, tunggu sesuai detik di header Retry-After (paling lama 5) lalu kirim lagi; untuk busy, tunggu satu detik. Tambahkan jeda acak kecil agar banyak percobaan ulang tidak datang bersamaan. Untuk monthly_limit, beli paket tambahan atau pindah ke paket lebih besar (lihat “Kehabisan email”), atau tunggu bulan depan; mengulang lebih awal tidak akan berhasil. Satu permintaan boleh membawa sampai 10 penerima (di to, cc, dan bcc) dari pesan yang sama: itu satu permintaan untuk laju, dan satu email dari paket Anda untuk tiap penerima. Jika Anda mengirim lebih dari lonjakan sekaligus, antrikan di sisi Anda dan sebarkan permintaannya.
Akun baru dimulai di paket Free, tanpa kartu: sampai 100 email per hari, satu domain, riwayat 1 hari, 3 template, dan tanpa file. Akun Free berbagi kuota email per hari UTC, jadi pada hari kuota itu penuh jawabannya 429 free_capacity sampai pukul 00:00 UTC. Paket dibayar dalam rupiah, online (QRIS, virtual account, atau e-wallet), diaktifkan saat pembayaran masuk, dan berlaku 30 hari. Lihat “Membayar, memperpanjang, dan berakhir”. Tiga hari setelah berakhir, API menjawab 402 plan_expired sampai Anda membayar lagi; tidak ada yang diperpanjang otomatis.
#Bounce, keluhan, dan daftar supresi
Saat pesan bounce permanen (alamat tidak ada), mendapat keluhan, atau ditolak, alamat itu masuk daftar supresi Anda. Pengiriman berikutnya ke alamat itu dijawab 422 recipient_suppressed. Setiap akun punya daftar sendiri: bounce pelanggan lain tidak pernah memblokir email Anda. Masalah sementara, seperti kotak surat penuh, tidak menyupresi alamat.
Di Basic dan Pro, bagian Supresi di dashboard menampilkan daftarnya: alamat, alasannya, kapan masuk, dan kapan keluar. Anda bisa mencarinya dengan sebagian alamat dan menghapus alamat, yang juga menghapusnya dari daftar yang disimpan penyedia email kami untuk akun Anda, sehingga bisa dikirimi lagi. Pro juga bisa menambah alamat secara manual dan memilih setelah berapa hari alamat yang masuk sendiri keluar dari daftar (30, 90, 180, atau 365 hari, atau tidak pernah). Alamat yang Anda tambah manual tetap sampai Anda menghapusnya.
| Paket | Kelola daftar | Tambah manual | Hapus otomatis |
|---|---|---|---|
| Starter | Tidak | Tidak | Tidak pernah |
| Basic | Cari, hapus | Tidak | Otomatis |
| Pro | Cari, hapus | Ya | Pilihan Anda |
Alamatnya sendiri hanya disimpan untuk paket yang mengelola daftar, dan hanya selama ada di daftar; di Starter hanya hash satu arah yang disimpan. Menghapus alamat yang memang tidak berfungsi hanya membuatnya bounce lagi, yang merugikan reputasi Anda.
Jaga tingkat bounce dan keluhan tetap rendah. Terlalu banyak bisa menjeda pengiriman Anda; jeda hanya berlaku untuk akun Anda.
#Memeriksa reputasi domain Anda
Ada dua hal berbeda untuk dilihat. Pertama, bagaimana pengiriman Anda sendiri: berapa banyak pesan yang bounce atau mendapat keluhan. Kedua, bagaimana penyedia email besar (Gmail, Outlook) menilai domain Anda. Yang pertama bisa kami tunjukkan. Yang kedua hanya bisa dijawab penyedia itu sendiri, dan alatnya gratis.
Kesehatan pengiriman. Di dashboard, di bagian Riwayat, panel “Kesehatan pengiriman” menghitung nasib pesan Anda dalam hari-hari yang disimpan paket Anda: berapa yang terkirim, bounce, mendapat keluhan, atau ditolak, serta persentase bounce dan keluhan. Hasilnya Baik, Perlu diperhatikan, atau Tinggi. Panel ini baru menilai setelah ada minimal 50 penerima yang sudah ada jawabannya.
| Ukuran | Perlu diperhatikan mulai | Penyedia email bertindak mulai |
|---|---|---|
| Bounce | 2 % | 5 % |
| Keluhan | 0,05 % | 0,1 % |
Di atas kolom kanan, penyedia email kami meninjau akun dan bisa menjeda pengiriman, dan Gmail serta Outlook mulai memasukkan email Anda ke folder spam. Untuk menurunkan angkanya, kirim hanya ke alamat yang diberikan orang kepada Anda, pastikan diketik dengan benar, dan berhenti mengirim ke alamat yang bounce (alamat itu otomatis masuk daftar supresi Anda).
Gmail. Google Postmaster Tools menunjukkan bagaimana Gmail menilai domain Anda (reputasi, tingkat spam, autentikasi). Gratis: masuk di postmaster.google.com dengan akun Google, tambahkan domain pengirim Anda, lalu pasang record TXT yang diberikan Google di penyedia DNS Anda. Google hanya menampilkan data jika domain Anda mengirim cukup banyak email ke alamat Gmail, sehingga pengirim kecil mungkin tidak melihat apa-apa. Kami tidak bisa melihatnya untuk Anda: data itu milik siapa pun yang membuktikan memiliki domain tersebut.
Outlook dan Hotmail. Alat Microsoft bekerja berdasarkan alamat IP, dan alamat IP itu milik penyedia email kami dan dipakai bersama, sehingga tidak mengatakan apa-apa tentang domain Anda. Untuk Outlook, bounce di kesehatan pengiriman Anda adalah tanda terbaik.
DMARC. Domain Anda ditandatangani dengan DKIM (record yang Anda pasang saat menyiapkan domain), yang dibutuhkan DMARC untuk lolos. Memasang record DMARC memberi tahu penyedia email apa yang harus dilakukan terhadap email yang gagal, dan mengirimi Anda laporan tentang siapa yang mengirim atas nama domain Anda. Mulailah dengan hanya memantau:
_dmarc.domain-anda.com TXT "v=DMARC1; p=none; rua=mailto:[email protected]"Setelah beberapa minggu menerima laporan, jika yang muncul hanya email Anda sendiri, Anda bisa mengubah p=none menjadi p=quarantine. Pakai alamat yang Anda baca untuk laporan itu; laporan dikirim oleh penyedia email, bukan oleh kami.
#Webhook
Webhook adalah panggilan dari NexDeliv ke server Anda saat sesuatu terjadi pada email yang Anda kirim. Pakai untuk tahu bahwa pesan terkirim, berhenti mengirim ke alamat yang bounce, atau menanggapi keluhan.
Atur di dashboard, bagian Webhook: isi alamat https di server Anda lalu simpan. Pertama kali, secret penandatanganan (whsec_…) ditampilkan sekali; simpan sebagai rahasia. “Send a test event” memeriksa bahwa server Anda menjawab.
| Kejadian | Kapan | data |
|---|---|---|
| email.delivered | Server email penerima menerima pesan. | messageId, to |
| email.bounced | Pesan bounce. “Permanent” berarti alamat tidak berfungsi dan kini disupresi; “Transient” berarti mungkin berhasil lain kali. | messageId, to, bounce.type, bounce.subType |
| email.complained | Penerima menandai pesan sebagai spam. | messageId, to, complaint.type (bila ada) |
| email.rejected | Kami menolak mengirimnya (misalnya berisi virus). | messageId, to, reject.reason |
| email.unsubscribed | Seseorang membuka tautan berhenti berlangganan dari sebuah pesan. Hanya dikirim bila Anda memilihnya, di menu Webhook di dashboard. | messageId (kosong), to |
| email.opened | Pesan yang dikirim dengan tracking dibuka. Hanya dikirim bila Anda memilihnya. Banyak program email memuat gambar untuk orangnya (dan menyembunyikan kapan), jadi pembukaan adalah petunjuk, bukan hitungan. | messageId, to, open.bot |
| email.clicked | Sebuah tautan di pesan yang dikirim dengan tracking diikuti. Hanya dikirim bila Anda memilihnya. Pemindai email mengikuti setiap tautan: bot menunjukkan saat kami mengira sebuah program yang melakukannya. | messageId, to, click.link, click.bot |
Setiap panggilan adalah POST dengan isi JSON berbentuk ini, dan header ini: NexDeliv-Event (jenisnya), NexDeliv-Event-Id (id-nya), dan NexDeliv-Signature.
POST /webhooks/nexdeliv HTTP/1.1
Host: your-app.example.com
Content-Type: application/json
User-Agent: NexDeliv-Webhooks/1
NexDeliv-Event: email.bounced
NexDeliv-Event-Id: evt_9f2c1d7a4b6e8f0a1b2c3d4e
NexDeliv-Signature: t=1761559202,v1=5b1f0c6a8e...
{
"id": "evt_9f2c1d7a4b6e8f0a1b2c3d4e",
"type": "email.bounced",
"createdAt": "2026-10-27T10:00:02.000Z",
"data": {
"messageId": "010e01a17b5ad26e-10055656-9b2f-4b83-8369-48e48a22c42c-000000",
"to": "[email protected]",
"bounce": { "type": "Permanent", "subType": "NoEmail" }
}
}// email.delivered: no extra field
{ "id": "evt_...", "type": "email.delivered", "createdAt": "...", "data": { "messageId": "...", "to": "[email protected]" } }
// email.complained: "type" is what the mailbox provider reported ("abuse", "fraud", ...); it can be missing
{ "id": "evt_...", "type": "email.complained", "createdAt": "...", "data": { "messageId": "...", "to": "[email protected]", "complaint": { "type": "abuse" } } }
// email.rejected: we refused to send it
{ "id": "evt_...", "type": "email.rejected", "createdAt": "...", "data": { "messageId": "...", "to": "[email protected]", "reject": { "reason": "Bad content" } } }// email.unsubscribed: only for a webhook that chose it. messageId is empty: the person followed a link, not a message.
{ "id": "evt_...", "type": "email.unsubscribed", "createdAt": "2026-10-28T10:00:00Z", "data": { "messageId": "", "to": "[email protected]" } }// email.opened: "bot" is true when we think a program opened it (a preview, a mail scanner), not a person
{ "id": "evt_...", "type": "email.opened", "createdAt": "2026-10-28T10:05:00Z", "data": { "messageId": "...", "to": "[email protected]", "open": { "bot": false } } }
// email.clicked: "link" is the address you wrote in the message, not ours
{ "id": "evt_...", "type": "email.clicked", "createdAt": "2026-10-28T10:06:00Z", "data": { "messageId": "...", "to": "[email protected]", "click": { "link": "https://your-domain.com/receipts/123", "bot": false } } }createdAt adalah waktu kejadian. Isi panggilan tidak pernah memuat subjek atau isi pesan Anda.
#Memverifikasi tanda tangan
Siapa pun yang tahu alamat Anda bisa mengirim ke sana, jadi periksa setiap panggilan. NexDeliv-Signature berbentuk t=1761559202,v1=<hex>. Hitung HMAC-SHA256 atas teks “<t>.<isi mentah>” dengan secret Anda sebagai kunci, tulis dalam hex, lalu bandingkan dengan v1 secara constant-time. Tolak panggilan yang t-nya lebih dari lima menit dari jam Anda, supaya panggilan lama tidak bisa diputar ulang.
Pakai isi mentah persis seperti yang datang: mem-parse JSON lalu menulisnya kembali mengubah byte dan tanda tangan gagal.
import crypto from "node:crypto";
// rawBody: the body exactly as it arrived (a string or Buffer), NOT re-serialised JSON.
export function verify(rawBody, header, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(header.split(",").map((part) => part.split("=")));
const { t, v1 } = parts;
if (!t || !v1 || Math.abs(Date.now() / 1000 - Number(t)) > toleranceSeconds) return false;
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return v1.length === expected.length && crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}
// Express: app.post("/webhooks/nexdeliv", express.raw({ type: "application/json" }), (req, res) => {
// if (!verify(req.body.toString(), req.get("NexDeliv-Signature") ?? "", process.env.NEXDELIV_WEBHOOK_SECRET)) return res.sendStatus(400);
// const event = JSON.parse(req.body.toString());
// ...handle it, keeping event.id so a repeat is ignored...
// res.sendStatus(200);
// });#Menjawab, percobaan ulang, dan pengulangan
- Jawab dengan status 2xx apa pun segera setelah Anda menerima kejadian, dalam beberapa detik; kerjakan sisanya belakangan. Kami menunggu sekitar sepuluh detik.
- Jika server Anda tidak terjangkau, terlalu lambat, atau menjawab 408, 429, atau 5xx, kami mencoba lagi beberapa menit kemudian, sampai tiga percobaan. Jawaban lain (misalnya 400 atau 404) final: tidak kami coba ulang.
- Pengiriman setidaknya sekali, jadi kejadian yang sama bisa datang lebih dari sekali. Simpan id kejadian yang sudah Anda tangani dan abaikan pengulangan.
- Kejadian bisa datang tidak berurutan. Bounce bisa datang sebelum kejadian terkirim dari pesan yang lebih awal.
- Kami tidak mengikuti redirect. Alamat harus https di port 443 dan menunjuk server publik (bukan alamat IP, dan bukan nama yang mengarah ke jaringan privat).
- Dashboard menampilkan status panggilan terakhir. Buat secret baru di sana bila yang lama bocor: yang lama berhenti berfungsi seketika.
// A good handler: verify, ignore what it has seen, answer fast, work later.
const seen = new Set(); // in production: a table or cache with an expiry
app.post("/webhooks/nexdeliv", express.raw({ type: "application/json" }), (req, res) => {
const raw = req.body.toString();
if (!verify(raw, req.get("NexDeliv-Signature") ?? "", process.env.NEXDELIV_WEBHOOK_SECRET)) return res.sendStatus(400);
const event = JSON.parse(raw);
if (!seen.has(event.id)) {
seen.add(event.id);
if (event.type === "email.bounced" && event.data.bounce.type === "Permanent") markAddressInvalid(event.data.to);
}
res.sendStatus(200);
});Panggilan yang gagal. Bila server Anda tetap tidak menjawab 2xx setelah percobaan kami, kejadian itu kami simpan 7 hari. Di dashboard, bagian Webhook, “Panggilan yang gagal” mendaftarnya dengan keadaan, jumlah percobaan, dan jawaban terakhir, dan tombol Kirim ulang mengirim satu kejadian dengan id yang sama (memakai webhook yang sekarang, jadi alamat atau secret baru berlaku). Hal yang sama tersedia untuk dashboard sebagai GET /v1/account/webhook/events dan POST /v1/account/webhook/events/{ref}/replay. Kejadian yang disimpan memuat alamat orang yang dimaksud, dan hilang setelah 7 hari.
Webhook adalah kemudahan, bukan jaminan: kejadian yang tidak pernah disimpan (berhasil pada percobaan pertama) atau yang lebih tua dari 7 hari tidak bisa dikirim ulang. Daftar supresi Anda di NexDeliv tetap benar dalam kasus apa pun.