Email sekali pakai di dimzhub.my.id. Bentuk request dan response sengaja mengikuti mail.tm, jadi klien yang sudah ada bisa diarahkan ke sini tanpa mengubah apa pun.
Ikhtisar
Semua endpoint dilayani oleh satu Cloudflare Worker. Data disimpan di D1 dan berkas (raw RFC822 serta lampiran) di R2. Ada tiga cara memakai layanan ini:
Inbox terbuka: alamat tanpa password di /inbox/{address}. Siapa pun yang tahu alamatnya bisa membacanya. Cocok untuk verifikasi dan pengujian.
Akun privat: daftarkan alamat dengan password lewat POST /accounts, lalu masuk lewat POST /token. Alamat ini akan ditolak (403) di jalur publik.
Surat masuk: ditangkap Cloudflare Email Routing catch-all, diparse, lalu muncul otomatis di inbox.
GET / mengembalikan halaman inbox (text/html), dan GET /docs mengembalikan halaman yang sedang Anda baca ini.
Mulai cepat
Alur paling singkat: daftarkan akun, ambil token, lalu baca pesan yang masuk. Ganti nilai contoh dengan data Anda sendiri.
Perintah di bawah bisa disalin berurutan ke terminal. Baris sed hanya mengambil nilai token dari response; kalau tidak tersedia, ambil manual dari keluaran langkah 3.
# Alamat apa pun yang belum dipakai akan dibuat otomatis (AUTO_CREATE=true).
# Tidak perlu password dan tidak perlu token.
curl -s $BASE/inbox/coba$(date +%s)@dimzhub.my.id
Untuk PowerShell 5.1 dan 7. Invoke-RestMethod sudah mem-parse JSON otomatis, jadi hasilnya bisa langsung diakses sebagai properti.
# Alamat apa pun yang belum dipakai akan dibuat otomatis (AUTO_CREATE=true).
Invoke-RestMethod -Uri "$BASE/inbox/coba$(Get-Date -Format 'HHmmss')@dimzhub.my.id" |
ConvertTo-Json -Depth 6
Autentikasi
Hanya POST /accounts dan POST /token yang boleh dipanggil tanpa kredensial. Semua endpoint lain di bawah ini memerlukan token.
Token adalah JWT HS256 yang ditandatangani Worker, berisi sub (id akun) dan exp. Masa berlaku 7 hari; setelah lewat, token ditolak dan Anda perlu login ulang.
Token dikirim sebagai header Authorization: Bearer <token>. Alternatifnya, karena beberapa klien tidak bisa memasang header, token juga diterima lewat query ?authorization=<token> pada URL yang sama.
Password. Disimpan sebagai PBKDF2-SHA256 dengan 100.000 iterasi dan salt acak per akun. Password hanya dipakai saat daftar dan login; tidak ada endpoint untuk menggantinya.
Format error
Ada tiga bentuk error yang berbeda, tergantung dari mana error berasal. Semuanya memakai header application/ld+json kecuali bentuk 401 dari autentikasi.
Urutan pemeriksaan penting. Worker memeriksa seperti ini: preflight OPTIONS → GET / → GET /docs → /inbox → POST /accounts dan POST /token → baru gerbang token. Artinya path yang tidak dikenal sekalipun akan dibalas 401 lebih dulu bila token tidak dikirim, dan 404 hydra:Error baru muncul setelah token yang valid disertakan.
1. Error umum: hydra:Error
Dipakai untuk 400, 404, 405, dan 500. Nilai hydra:description yang benar-benar muncul di kode: "Invalid JSON body.", "Not Found", "Method Not Allowed", dan pesan pengecualian saat terjadi kegagalan internal.
Hanya dari POST /accounts, selalu berstatus 422. Objeknya selalu berisi tepat satu violations, dan code selalu null.
Contoh 422
{
"@context": "/contexts/ConstraintViolationList",
"@type": "ConstraintViolationList",
"hydra:title": "An error occurred",
"hydra:description": "password: This value is too short. It should have 6 characters or more.",
"violations": [
{
"propertyPath": "password",
"message": "This value is too short. It should have 6 characters or more.",
"code": null
}
]
}
3. Autentikasi: { code, message }
Bukan JSON-LD, dan tidak memuat @context. Dipakai untuk 401.
Contoh 401
{
"code": 401,
"message": "Invalid JWT Token"
}
4. Kegagalan internal: 500
Error yang tidak tertangkap di route() dibalas 500hydra:Error dengan hydra:description berisi pesan pengecualian aslinya. Satu kasus yang paling sering terjadi saat penyiapan: bila variabel JWT_SECRET belum diatur, penandatanganan token gagal dan POST /token membalas 500.
Contoh 500
{
"@context": "/contexts/Error",
"@type": "hydra:Error",
"hydra:title": "An error occurred",
"hydra:description": "Imported HMAC key length (0) must be a non-zero value up to 7 bits less than, and no greater than, the bit length of the raw key data (0)."
}
5. Error khas jalur publik
Jalur /inbox/... memakai bentuk paling sederhana, hanya satu field error, dan hanya mengirim header Access-Control-Allow-Origin tanpa daftar method/header.
Status
Body
Kapan muncul
422
{ "error": "Alamat tidak valid" }
Domain bukan milik layanan ini, atau bagian lokal alamat gagal validasi.
404
{ "error": "Mailbox tidak ada" }
Alamat belum terdaftar dan pembuatan otomatis dimatikan (AUTO_CREATE=false).
403
{ "error": "Mailbox ini privat. Masuk lewat /token." }
Alamat itu punya password, jadi tidak bisa dibaca lewat jalur publik.
404
{ "message": "Not found" }
Pesan atau lampiran tidak ditemukan, atau method/path tidak dikenal di dalam jalur /inbox/....
Batasan
Nilai berikut berasal langsung dari konstanta dan aturan di dalam Worker. Yang sudah dipotong atau ditolak tidak akan pernah muncul utuh di response.
Aturan
Nilai
Perilaku saat dilanggar
Ukuran email masuk
5 MB (5.242.880 byte)
Ditolak di tingkat SMTP lewat setReject; pesan tidak pernah tersimpan.
Panjang body
500 KB (512.000 byte)
text dan html dipotong, bukan ditolak. Raw RFC822 tetap utuh di R2.
Lampiran
10 berkas
Hanya 10 lampiran pertama yang disimpan; sisanya diabaikan tanpa error.
Retensi pesan
24 jam
Cron tiap jam menghapus pesan yang lebih tua, termasuk lampiran dan raw di R2.
Retensi akun
7 hari
Akun yang lebih tua dihapus beserta seluruh isinya, termasuk akun berpassword.
Pagination
30 per halaman
?page= di luar rentang mengembalikan koleksi kosong, bukan error.
Batas jalur publik
50 pesan
/inbox/{address} tidak punya pagination; hanya 50 pesan terbaru yang dikirim.
Masa berlaku token
7 hari
Setelah lewat, semua permintaan ber-token dibalas 401.
Password
minimal 6 karakter
422 saat daftar. Login tidak memeriksa panjang, hanya kecocokan.
Nama alamat
3-64 karakter
Pola a-z 0-9 . _ -. Di luar itu 422 untuk jalur publik atau 422 address saat daftar.
Kuota akun
40.000.000 byte
Nilai quota yang dilaporkan. Field used mengisi jumlah ukuran pesan yang tersimpan.
Satu domain, banyak nilai.DOMAIN boleh berisi beberapa domain dipisah koma. Yang dipakai untuk msgid dan divalidasi saat mendaftar adalah daftar itu, sedangkan GET /domains selalu mengembalikan seluruhnya dengan id berurutan mulai dari "1".
OPTIONS/*tanpa auth
Preflight CORS. Worker menangkap OPTIONS sebelum routing dijalankan, jadi berlaku untuk seluruh path.
Header respons
Nilai
Access-Control-Allow-Origin
*
Access-Control-Allow-Methods
GET,POST,PATCH,DELETE,OPTIONS
Access-Control-Allow-Headers
Content-Type,Authorization,Accept
Catatan. Tiga header di atas hanya dikirim Worker utama. Respons dari /inbox/... hanya menyertakan Access-Control-Allow-Origin: *, tanpa daftar method dan header.
GET/domainstanpa auth
Daftar domain yang dilayani layanan ini. Dipakai klien untuk memilih bagian domain sebelum membentuk alamat. Hanya menerima GET; method lain dibalas 405.
Query
Tipe
Wajib
Keterangan
page
integer
opsional
Nomor halaman, minimal 1. Nilai tidak valid dianggap 1. Isi 30 domain per halaman.
hydra:previous dan hydra:next hanya muncul bila halaman saat ini bukan halaman pertama atau terakhir. hydra:last dihitung dari totalItems dibagi 30, minimal 1.
GET/domains/{id}tanpa auth
Satu domain berdasarkan id. Perhatikan bahwa {id} adalah indeks 1-based ("1" untuk domain pertama), bukan UUID. Response-nya objek tunggal, tanpa pembungkus koleksi.
Path
Tipe
Wajib
Keterangan
id
string
WAJIB
Posisi domain dalam daftar, mulai dari "1". Tidak ditemukan → 404.
Timestamp tetap.createdAt dan updatedAt selalu bernilai 2026-01-01T00:00:00+00:00 untuk semua domain; ini nilai statis, bukan waktu sungguhan.
POST/accountstanpa auth
Mendaftarkan alamat dengan password, sehingga inbox-nya privat. Alamat harus memakai salah satu domain layanan dan belum pernah dipakai. Berhasil → 201.
Body JSON
Tipe
Wajib
Keterangan
address
string
WAJIB
Alamat lengkap, mis. nama@dimzhub.my.id. Diubah ke huruf kecil sebelum disimpan.
password
string
WAJIB
Minimal 6 karakter. Disimpan sebagai PBKDF2-SHA256 dengan salt acak.
Kemungkinan 422
propertyPath
message
Pemicu
address
This value should not be blank.
Field tidak ada atau string kosong.
password
This value should not be blank.
Field tidak ada atau string kosong.
address
This value is not valid.
Domain tidak dikenali, ada lebih dari satu @, atau nama gagal pola a-z 0-9 . _ - sepanjang 3-64 karakter.
password
This value is too short. It should have 6 characters or more.
Akun inbox terbuka tidak bisa login. Akun yang dibuat otomatis menyimpan password_hash kosong, sehingga tidak ada password yang cocok. Untuk memakainya ber-token, daftarkan ulang lewat POST /accounts dengan alamat berbeda.
GET/inbox/{address}tanpa auth
Daftar pesan di inbox terbuka. Ini endpoint yang dipakai halaman / untuk polling tiap lima detik. Bentuk response-nya berbeda dari /messages: tidak ada JSON-LD dan tidak ada pagination.
Path
Tipe
Wajib
Keterangan
address
string
WAJIB
Alamat lengkap. Boleh mengandung karakter khusus, karena di-percent-encode di URL.
Urutan pemeriksaan
Alamat divalidasi: harus memakai domain layanan dan nama sepanjang 3-64 karakter pola a-z 0-9 . _ -. Gagal → 422{"error":"Alamat tidak valid"}.
Kalau belum ada dan AUTO_CREATE=true, alamat dibuat otomatis dengan password kosong. Kalau AUTO_CREATE=false → 404{"error":"Mailbox tidak ada"}.
Kalau alamat punya password → 403{"error":"Mailbox ini privat. Masuk lewat /token."}.
{
"address": "contoh@dimzhub.my.id",
"messages": [
{
"id": "b1f0c4e8-2a6d-4f83-9c71-05e2ab7d4c19",
"from": {
"address": "noreply@layanan.com",
"name": "Layanan Verifikasi"
},
"subject": "Kode verifikasi Anda",
"intro": "Kode verifikasi Anda adalah 482913. Jangan bagikan kode ini kepada siapa pun.",
"seen": false,
"hasAttachments": false,
"createdAt": "2026-10-01T09:58:12.000Z"
}
]
}
Batas 50 pesan. Query LIMIT 50 dan urutan created_at DESC berlaku, jadi pesan terlama akan hilang dari daftar kalau sudah lebih dari 50, tanpa paging. intro adalah 120 karakter pertama body teks dengan spasi dirapatkan, atau string kosong untuk email HTML tanpa versi teks.
GET/inbox/{address}/{id}tanpa auth
Isi satu pesan dari inbox terbuka. Membaca pesan ini langsung menandainya sudah dibaca.
Path
Tipe
Wajib
Keterangan
address
string
WAJIB
Alamat pemilik pesan. Pesan milik alamat lain → 404.
Gambar inline sudah dirakit ulang. Kalau email memakai Content-ID (mis. cid:logo), semua kemunculan cid:<id> di dalam html diganti menjadi URL lampiran dengan tambahan ?inline=1, sehingga gambar langsung tampil.
Kapan inline diizinkan? Hanya bila ?inline dikirim dan MIME lampiran salah satu dari image/png, image/jpeg, image/jpg, image/gif, image/webp. Selain itu selalu attachment dengan tipe application/octet-stream. SVG sengaja tidak pernah di-inline. Lampiran yang tidak ada dibalas 404{"message":"Not found"}.
DELETE/inbox/{address}/{id}tanpa auth
Menghapus pesan dari inbox terbuka, sekaligus semua lampirannya dan berkas raw di R2.
Tidak ada cek keberadaan. Menghapus id yang tidak ada tetap dibalas 204. Penghapusan dijalankan untuk pasangan pesan dan alamat, jadi pesan milik alamat lain tidak akan tersentuh.
GET/meperlu token
Profil akun pemilik token, termasuk pemakaian penyimpanan. Ini endpoint pertama yang dipanggil setelah login untuk memastikan token masih berlaku.
Arti used. Dihitung dari SUM(size) seluruh pesan akun, yaitu ukuran raw email saat diterima. Lampiran tidak dihitung terpisah, karena ukurannya sudah termasuk di dalam raw.
GET/accounts/{id}perlu token
Profil akun berdasarkan id. Bentuk response-nya identik dengan /me.
Path
Tipe
Wajib
Keterangan
id
string
WAJIB
Harus sama dengan id pemilik token. Id lain dibalas 404, bukan 403, sehingga keberadaan akun lain tidak bocor.
Tidak bisa dibatalkan. Berkas R2 dihapus dalam batch 500 kunci, lalu baris attachments, messages, dan akhirnya accounts. Setelah itu alamat yang sama boleh didaftarkan ulang, tetapi isinya sudah hilang.
GET/messagesperlu token
Daftar pesan akun dengan pagination, bentuk koleksi Hydra seperti mail.tm.
Query
Tipe
Wajib
Keterangan
page
integer
opsional
Nomor halaman, minimal 1. Nilai tidak valid dianggap 1. Isi 30 pesan per halaman.
Catatan bentuk.msgid memakai domain pertama dari daftar DOMAIN. Pada item koleksi, intro ditambahkan ke objek pesan, sedangkan text, html, cc, dan attachments hanya ada di endpoint detail.
GET/messages/{id}perlu token
Isi lengkap satu pesan. Membaca pesan ini menandainya sudah dibaca.
Path
Tipe
Wajib
Keterangan
id
string
WAJIB
Pesan milik akun lain dibalas 404 karena query selalu difilter account_id.
Field yang selalu kosong.cc, bcc, dan verifications selalu larik kosong, flagged selalu false. retention selalu true, dan retentionDate dihitung dari createdAt ditambah 24 jam. html berbentuk larik meskipun isinya satu elemen, sedangkan teks tanpa versi HTML dikirim sebagai text saja.
PATCH/messages/{id}perlu token
Menandai pesan sudah dibaca. Body permintaan diabaikan sepenuhnya: apa pun yang dikirim, efeknya tetap menandai seen.
Nama berkas dengan karakter non-ASCII dikirim memakai filename*=UTF-8'' ber-percent-encode, jadi aman untuk pemakaian dari JavaScript maupun klien HTTP lain.
GET/messages/{id}/downloadperlu token
Mengunduh email mentah dalam format RFC822 (.eml). Isinya persis seperti yang diterima Worker, tanpa pemotongan body.
404 bila berkas raw tidak ada. Raw hanya tersimpan kalau binding R2 (BUCKET) terpasang saat email masuk. Bila penyimpanan pernah gagal, pesan tetap bisa dibaca lewat GET /messages/{id}, tetapi unduhan ini membalas 404.
GET/sources/{id}perlu token
Sumber pesan dalam bentuk JSON, isinya RFC822 mentah sebagai string. Ini padanan download untuk klien yang ingin membaca teks tanpa menangani header biner.
{
"@context": "/contexts/MessageSource",
"@id": "/sources/b1f0c4e8-2a6d-4f83-9c71-05e2ab7d4c19",
"@type": "MessageSource",
"id": "b1f0c4e8-2a6d-4f83-9c71-05e2ab7d4c19",
"downloadUrl": "/messages/b1f0c4e8-2a6d-4f83-9c71-05e2ab7d4c19/download",
"data": "Return-Path: <noreply@layanan.com>\nFrom: Layanan Verifikasi <noreply@layanan.com>\nTo: contoh@dimzhub.my.id\nSubject: Kode verifikasi Anda\nMIME-Version: 1.0\nContent-Type: text/plain; charset=utf-8\n\nKode verifikasi Anda adalah 482913."
}
Hanya /download yang diperlakukan khusus. Segmen tambahan apa pun selain download tetap mengembalikan JSON ini, jadi jangan mengandalkan 404 untuk membedakan sub-path.
GET/sources/{id}/downloadperlu token
Alias unduhan yang menempel pada resource sumber. Hasilnya sama dengan /messages/{id}/download.
Perlu diperhatikan. Endpoint ini dijalankan sebagai respons biner, sehingga header CORS tetap dikirim tetapi tanpa Content-Disposition. Gunakan /messages/{id}/attachment/... bila Anda perlu nama berkas.
GET/.well-known/mercureperlu token
Stream Server-Sent Events bergaya Mercure, untuk menerima notifikasi saat ada email masuk tanpa polling. Dikirim setiap kali jumlah pesan akun berubah, dan payload-nya objek akun terbaru.
Query
Tipe
Wajib
Keterangan
topic
string
opsional
Dapat diulang. Harus ada satu yang berakhiran /accounts/{id} milik pemilik token, jika tidak dibalas 403. Bila tidak dikirim sama sekali, stream tetap dibuka.
authorization
string
opsional
Alternatif header, karena EventSource di browser tidak bisa memasang header sendiri.
Karakter : adalah komentar SSE, dikirim tiap 3 detik sebagai penjaga koneksi. Stream menutup dirinya sendiri setelah sekitar 100 detik, sehingga klien wajib menyambung ulang; perilaku ini disengaja agar tidak melampaui batas durasi permintaan.
Contoh konsumsi di browser
const url = new URL("https://mailler.dimzhub.my.id/.well-known/mercure");
url.searchParams.set("topic", "/accounts/" + accountId);
url.searchParams.set("authorization", token);
// EventSource tidak mendukung header, jadi token dikirim lewat query.
const es = new EventSource(url);
es.onmessage = (e) => {
const account = JSON.parse(e.data);
console.log("pesan tersimpan:", account.used);
};
es.onerror = () => { /* browser menyambung ulang otomatis */ };
dimzmail · dokumentasi diturunkan dari index.js dan open.jsBuka inbox