docs @dimzhub.my.id Inbox

API dimzmail

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.
Base URLhttps://mailler.dimzhub.my.id
Content-Type APIapplication/ld+json; charset=utf-8
Skema URLhttps://<host>/<resource>
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.

Langkah 1: daftar akun, masuk, baca inbox
BASE=https://mailler.dimzhub.my.id
ADDR=contoh-$(date +%s)@dimzhub.my.id
PASS=rahasia123

# 1. Lihat domain yang tersedia (tanpa auth)
curl -s $BASE/domains

# 2. Daftar akun berpassword -> HTTP 201
curl -s -X POST $BASE/accounts \
  -H "Content-Type: application/json" \
  -d "{\"address\":\"$ADDR\",\"password\":\"$PASS\"}"

# 3. Ambil token -> { "id": "...", "token": "..." }
TOKEN=$(curl -s -X POST $BASE/token \
  -H "Content-Type: application/json" \
  -d "{\"address\":\"$ADDR\",\"password\":\"$PASS\"}" \
  | sed -E 's/.*"token":"([^"]+)".*/\1/')

# 4. Profil + daftar pesan
curl -s $BASE/me -H "Authorization: Bearer $TOKEN"
curl -s "$BASE/messages?page=1" -H "Authorization: Bearer $TOKEN"

# 5. Bersih-bersih -> HTTP 204
curl -s -X DELETE $BASE/accounts/ID_AKUN -H "Authorization: Bearer $TOKEN" -o /dev/null -w "%{http_code}"
Jalur cepat tanpa daftar (inbox terbuka)
# 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

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.

Header permintaan
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.contoh.payload
Query setara (untuk klien tanpa header)
https://mailler.dimzhub.my.id/messages?authorization=eyJhbGciOiJIUzI1NiJ9.contoh.payload
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.

Contoh 404
{
  "@context": "/contexts/Error",
  "@type": "hydra:Error",
  "hydra:title": "An error occurred",
  "hydra:description": "Not Found"
}

2. Validasi: ConstraintViolationList

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 500 hydra: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.

StatusBodyKapan 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.

AturanNilaiPerilaku saat dilanggar
Ukuran email masuk5 MB (5.242.880 byte)Ditolak di tingkat SMTP lewat setReject; pesan tidak pernah tersimpan.
Panjang body500 KB (512.000 byte)text dan html dipotong, bukan ditolak. Raw RFC822 tetap utuh di R2.
Lampiran10 berkasHanya 10 lampiran pertama yang disimpan; sisanya diabaikan tanpa error.
Retensi pesan24 jamCron tiap jam menghapus pesan yang lebih tua, termasuk lampiran dan raw di R2.
Retensi akun7 hariAkun yang lebih tua dihapus beserta seluruh isinya, termasuk akun berpassword.
Pagination30 per halaman?page= di luar rentang mengembalikan koleksi kosong, bukan error.
Batas jalur publik50 pesan/inbox/{address} tidak punya pagination; hanya 50 pesan terbaru yang dikirim.
Masa berlaku token7 hariSetelah lewat, semua permintaan ber-token dibalas 401.
Passwordminimal 6 karakter422 saat daftar. Login tidak memeriksa panjang, hanya kecocokan.
Nama alamat3-64 karakterPola a-z 0-9 . _ -. Di luar itu 422 untuk jalur publik atau 422 address saat daftar.
Kuota akun40.000.000 byteNilai 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 responsNilai
Access-Control-Allow-Origin*
Access-Control-Allow-MethodsGET,POST,PATCH,DELETE,OPTIONS
Access-Control-Allow-HeadersContent-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 /domains tanpa auth

Daftar domain yang dilayani layanan ini. Dipakai klien untuk memilih bagian domain sebelum membentuk alamat. Hanya menerima GET; method lain dibalas 405.

QueryTipeWajibKeterangan
pageintegeropsionalNomor halaman, minimal 1. Nilai tidak valid dianggap 1. Isi 30 domain per halaman.
Request
curl -s https://mailler.dimzhub.my.id/domains?page=1
Response 200
{
  "@context": "/contexts/Domain",
  "@id": "/domains",
  "@type": "hydra:Collection",
  "hydra:member": [
    {
      "@id": "/domains/1",
      "@type": "Domain",
      "@context": "/contexts/Domain",
      "id": "1",
      "domain": "dimzhub.my.id",
      "isActive": true,
      "isPrivate": false,
      "createdAt": "2026-01-01T00:00:00+00:00",
      "updatedAt": "2026-01-01T00:00:00+00:00"
    }
  ],
  "hydra:totalItems": 1,
  "hydra:view": {
    "@id": "/domains?page=1",
    "@type": "hydra:PartialCollectionView",
    "hydra:first": "/domains?page=1",
    "hydra:last": "/domains?page=1"
  }
}
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.

PathTipeWajibKeterangan
idstringWAJIBPosisi domain dalam daftar, mulai dari "1". Tidak ditemukan → 404.
Request
curl -s https://mailler.dimzhub.my.id/domains/1
Response 200
{
  "@id": "/domains/1",
  "@type": "Domain",
  "@context": "/contexts/Domain",
  "id": "1",
  "domain": "dimzhub.my.id",
  "isActive": true,
  "isPrivate": false,
  "createdAt": "2026-01-01T00:00:00+00:00",
  "updatedAt": "2026-01-01T00:00:00+00:00"
}
Timestamp tetap. createdAt dan updatedAt selalu bernilai 2026-01-01T00:00:00+00:00 untuk semua domain; ini nilai statis, bukan waktu sungguhan.
POST /accounts tanpa auth

Mendaftarkan alamat dengan password, sehingga inbox-nya privat. Alamat harus memakai salah satu domain layanan dan belum pernah dipakai. Berhasil → 201.

Body JSONTipeWajibKeterangan
addressstringWAJIBAlamat lengkap, mis. nama@dimzhub.my.id. Diubah ke huruf kecil sebelum disimpan.
passwordstringWAJIBMinimal 6 karakter. Disimpan sebagai PBKDF2-SHA256 dengan salt acak.

Kemungkinan 422

propertyPathmessagePemicu
addressThis value should not be blank.Field tidak ada atau string kosong.
passwordThis value should not be blank.Field tidak ada atau string kosong.
addressThis value is not valid.Domain tidak dikenali, ada lebih dari satu @, atau nama gagal pola a-z 0-9 . _ - sepanjang 3-64 karakter.
passwordThis value is too short. It should have 6 characters or more.Panjang kurang dari 6.
addressThis value is already used.Alamat sudah terdaftar.
Request
curl -s -X POST https://mailler.dimzhub.my.id/accounts \
  -H "Content-Type: application/json" \
  -d '{"address":"contoh@dimzhub.my.id","password":"rahasia123"}'
Response 201
{
  "@context": "/contexts/Account",
  "@id": "/accounts/3f7c1a92-8d44-4c1e-9b02-77ac55de1f30",
  "@type": "Account",
  "id": "3f7c1a92-8d44-4c1e-9b02-77ac55de1f30",
  "address": "contoh@dimzhub.my.id",
  "quota": 40000000,
  "used": 0,
  "isDisabled": false,
  "isDeleted": false,
  "createdAt": "2026-10-01T10:00:00.000Z",
  "updatedAt": "2026-10-01T10:00:00.000Z"
}
Body bukan JSON objek (mis. teks kosong atau array) dibalas 400 hydra:Error dengan deskripsi "Invalid JSON body.", bukan 422.
POST /token tanpa auth

Menukar alamat dan password menjadi token. Hanya menerima POST; GET /token dibalas 405.

Body JSONTipeWajibKeterangan
addressstringWAJIBAlamat yang terdaftar. Dibandingkan setelah diubah ke huruf kecil.
passwordstringWAJIBKosong atau salah → 401, dengan pesan yang sama seperti alamat tak dikenal.
Request
curl -s -X POST https://mailler.dimzhub.my.id/token \
  -H "Content-Type: application/json" \
  -d '{"address":"contoh@dimzhub.my.id","password":"rahasia123"}'
Response 200
{
  "id": "3f7c1a92-8d44-4c1e-9b02-77ac55de1f30",
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIzZjdjMWE5MiIsImV4cCI6MTc5MTM4MDAwMH0.contoh-tanda-tangan"
}
Response 401: kredensial salah
{
  "code": 401,
  "message": "Invalid credentials."
}
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.

PathTipeWajibKeterangan
addressstringWAJIBAlamat lengkap. Boleh mengandung karakter khusus, karena di-percent-encode di URL.

Urutan pemeriksaan

  1. Alamat divalidasi: harus memakai domain layanan dan nama sepanjang 3-64 karakter pola a-z 0-9 . _ -. Gagal → 422 {"error":"Alamat tidak valid"}.
  2. Kalau belum ada dan AUTO_CREATE=true, alamat dibuat otomatis dengan password kosong. Kalau AUTO_CREATE=false → 404 {"error":"Mailbox tidak ada"}.
  3. Kalau alamat punya password → 403 {"error":"Mailbox ini privat. Masuk lewat /token."}.
Request
curl -s "https://mailler.dimzhub.my.id/inbox/contoh@dimzhub.my.id"
Response 200
{
  "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.

PathTipeWajibKeterangan
addressstringWAJIBAlamat pemilik pesan. Pesan milik alamat lain → 404.
idstringWAJIBUUID pesan dari daftar inbox.
Request
curl -s "https://mailler.dimzhub.my.id/inbox/contoh@dimzhub.my.id/b1f0c4e8-2a6d-4f83-9c71-05e2ab7d4c19"
Response 200
{
  "id": "b1f0c4e8-2a6d-4f83-9c71-05e2ab7d4c19",
  "from": {
    "address": "noreply@layanan.com",
    "name": "Layanan Verifikasi"
  },
  "subject": "Kode verifikasi Anda",
  "text": "Kode verifikasi Anda adalah 482913.\nJangan bagikan kode ini kepada siapa pun.",
  "html": [
    "<div style=\"font-family:sans-serif\"><p>Kode verifikasi Anda: <b>482913</b></p></div>"
  ],
  "createdAt": "2026-10-01T09:58:12.000Z",
  "attachments": [
    {
      "id": "7d3a9e51-0c66-4b8a-9f12-3ac4e87b0d55",
      "filename": "syarat.pdf",
      "contentType": "application/pdf",
      "disposition": "attachment",
      "transferEncoding": "base64",
      "related": false,
      "size": 102400,
      "contentId": "",
      "downloadUrl": "/inbox/contoh%40dimzhub.my.id/b1f0c4e8-2a6d-4f83-9c71-05e2ab7d4c19/attachment/7d3a9e51-0c66-4b8a-9f12-3ac4e87b0d55"
    }
  ]
}
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.
GET /inbox/{address}/{id}/attachment/{attId} tanpa auth

Mengunduh satu lampiran. Response-nya biner, bukan JSON.

BagianTipeWajibKeterangan
address pathstringWAJIBPemilik pesan.
id pathstringWAJIBUUID pesan.
attId pathstringWAJIBUUID lampiran, diambil dari attachments[].id.
inlinequeryopsionalDiisi apa pun (mis. ?inline=1) untuk menampilkan di tempat. Hanya berlaku untuk gambar yang aman.
Request
curl -sL "https://mailler.dimzhub.my.id/inbox/contoh%40dimzhub.my.id/b1f0c4e8-2a6d-4f83-9c71-05e2ab7d4c19/attachment/7d3a9e51-0c66-4b8a-9f12-3ac4e87b0d55" \
  -o syarat.pdf
Header respons
Content-Type: application/pdf
Content-Disposition: attachment; filename*=UTF-8''syarat.pdf
X-Content-Type-Options: nosniff
Access-Control-Allow-Origin: *
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.

PathTipeWajibKeterangan
addressstringWAJIBPemilik pesan.
idstringWAJIBUUID pesan yang dihapus.
Request
curl -s -o /dev/null -w "%{http_code}\n" -X DELETE \
  "https://mailler.dimzhub.my.id/inbox/contoh@dimzhub.my.id/b1f0c4e8-2a6d-4f83-9c71-05e2ab7d4c19"
Response 204
(tanpa body)
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 /me perlu token

Profil akun pemilik token, termasuk pemakaian penyimpanan. Ini endpoint pertama yang dipanggil setelah login untuk memastikan token masih berlaku.

Request
curl -s https://mailler.dimzhub.my.id/me \
  -H "Authorization: Bearer $TOKEN"
Response 200
{
  "@context": "/contexts/Account",
  "@id": "/accounts/3f7c1a92-8d44-4c1e-9b02-77ac55de1f30",
  "@type": "Account",
  "id": "3f7c1a92-8d44-4c1e-9b02-77ac55de1f30",
  "address": "contoh@dimzhub.my.id",
  "quota": 40000000,
  "used": 24192,
  "isDisabled": false,
  "isDeleted": false,
  "createdAt": "2026-10-01T10:00:00.000Z",
  "updatedAt": "2026-10-01T10:00:00.000Z"
}
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.

PathTipeWajibKeterangan
idstringWAJIBHarus sama dengan id pemilik token. Id lain dibalas 404, bukan 403, sehingga keberadaan akun lain tidak bocor.
Request
curl -s https://mailler.dimzhub.my.id/accounts/3f7c1a92-8d44-4c1e-9b02-77ac55de1f30 \
  -H "Authorization: Bearer $TOKEN"
Response 200
{
  "@context": "/contexts/Account",
  "@id": "/accounts/3f7c1a92-8d44-4c1e-9b02-77ac55de1f30",
  "@type": "Account",
  "id": "3f7c1a92-8d44-4c1e-9b02-77ac55de1f30",
  "address": "contoh@dimzhub.my.id",
  "quota": 40000000,
  "used": 24192,
  "isDisabled": false,
  "isDeleted": false,
  "createdAt": "2026-10-01T10:00:00.000Z",
  "updatedAt": "2026-10-01T10:00:00.000Z"
}
DELETE /accounts/{id} perlu token

Menghapus akun beserta seluruh pesan, lampiran, dan berkas raw-nya. Token yang masih beredar otomatis tidak berguna karena akunnya sudah tidak ada.

PathTipeWajibKeterangan
idstringWAJIBHarus sama dengan id pemilik token.
Request
curl -s -o /dev/null -w "%{http_code}\n" -X DELETE \
  https://mailler.dimzhub.my.id/accounts/3f7c1a92-8d44-4c1e-9b02-77ac55de1f30 \
  -H "Authorization: Bearer $TOKEN"
Response 204
(tanpa body)
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 /messages perlu token

Daftar pesan akun dengan pagination, bentuk koleksi Hydra seperti mail.tm.

QueryTipeWajibKeterangan
pageintegeropsionalNomor halaman, minimal 1. Nilai tidak valid dianggap 1. Isi 30 pesan per halaman.
Request
curl -s "https://mailler.dimzhub.my.id/messages?page=1" \
  -H "Authorization: Bearer $TOKEN"
Response 200
{
  "@context": "/contexts/Message",
  "@id": "/messages",
  "@type": "hydra:Collection",
  "hydra:member": [
    {
      "@id": "/messages/b1f0c4e8-2a6d-4f83-9c71-05e2ab7d4c19",
      "@type": "Message",
      "@context": "/contexts/Message",
      "id": "b1f0c4e8-2a6d-4f83-9c71-05e2ab7d4c19",
      "accountId": "3f7c1a92-8d44-4c1e-9b02-77ac55de1f30",
      "msgid": "<b1f0c4e8-2a6d-4f83-9c71-05e2ab7d4c19@dimzhub.my.id>",
      "from": {
        "name": "Layanan Verifikasi",
        "address": "noreply@layanan.com"
      },
      "to": [
        {
          "name": "",
          "address": "contoh@dimzhub.my.id"
        }
      ],
      "subject": "Kode verifikasi Anda",
      "seen": false,
      "isDeleted": false,
      "hasAttachments": false,
      "size": 24192,
      "downloadUrl": "/messages/b1f0c4e8-2a6d-4f83-9c71-05e2ab7d4c19/download",
      "createdAt": "2026-10-01T09:58:12.000Z",
      "updatedAt": "2026-10-01T09:58:12.000Z",
      "intro": "Kode verifikasi Anda adalah 482913. Jangan bagikan kode ini kepada siapa pun."
    }
  ],
  "hydra:totalItems": 1,
  "hydra:view": {
    "@id": "/messages?page=1",
    "@type": "hydra:PartialCollectionView",
    "hydra:first": "/messages?page=1",
    "hydra:last": "/messages?page=1"
  }
}
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.

PathTipeWajibKeterangan
idstringWAJIBPesan milik akun lain dibalas 404 karena query selalu difilter account_id.
Request
curl -s https://mailler.dimzhub.my.id/messages/b1f0c4e8-2a6d-4f83-9c71-05e2ab7d4c19 \
  -H "Authorization: Bearer $TOKEN"
Response 200
{
  "@id": "/messages/b1f0c4e8-2a6d-4f83-9c71-05e2ab7d4c19",
  "@type": "Message",
  "@context": "/contexts/Message",
  "id": "b1f0c4e8-2a6d-4f83-9c71-05e2ab7d4c19",
  "accountId": "3f7c1a92-8d44-4c1e-9b02-77ac55de1f30",
  "msgid": "<b1f0c4e8-2a6d-4f83-9c71-05e2ab7d4c19@dimzhub.my.id>",
  "from": {
    "name": "Layanan Verifikasi",
    "address": "noreply@layanan.com"
  },
  "to": [
    {
      "name": "",
      "address": "contoh@dimzhub.my.id"
    }
  ],
  "subject": "Kode verifikasi Anda",
  "seen": true,
  "isDeleted": false,
  "hasAttachments": false,
  "size": 24192,
  "downloadUrl": "/messages/b1f0c4e8-2a6d-4f83-9c71-05e2ab7d4c19/download",
  "createdAt": "2026-10-01T09:58:12.000Z",
  "updatedAt": "2026-10-01T09:58:12.000Z",
  "cc": [],
  "bcc": [],
  "flagged": false,
  "verifications": [],
  "retention": true,
  "retentionDate": "2026-10-02T09:58:12.000Z",
  "text": "Kode verifikasi Anda adalah 482913.\nJangan bagikan kode ini kepada siapa pun.",
  "html": [
    "<div style=\"font-family:sans-serif\"><p>Kode verifikasi Anda: <b>482913</b></p></div>"
  ],
  "attachments": []
}
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.

PathTipeWajibKeterangan
idstringWAJIBPesan yang ditandai. Pesan tidak dikenal → 404.
Request
curl -s -X PATCH https://mailler.dimzhub.my.id/messages/ID_PESAN \
  -H "Authorization: Bearer $TOKEN"
Response 200
{ "seen": true }
Hanya satu arah. Tidak ada cara menandai pesan kembali belum dibaca; tidak ada nilai false yang diterima.
DELETE /messages/{id} perlu token

Menghapus satu pesan beserta lampiran dan berkas raw-nya di R2.

PathTipeWajibKeterangan
idstringWAJIBPesan yang dihapus. Pesan tidak dikenal → 404.
Request
curl -s -o /dev/null -w "%{http_code}\n" -X DELETE \
  https://mailler.dimzhub.my.id/messages/ID_PESAN \
  -H "Authorization: Bearer $TOKEN"
Response 204
(tanpa body)
GET /messages/{id}/attachment/{attId} perlu token

Mengunduh lampiran lewat jalur ber-token. Aturan inline sama persis dengan versi publik.

BagianTipeWajibKeterangan
id pathstringWAJIBUUID pesan milik akun pemilik token.
attId pathstringWAJIBUUID lampiran dari attachments[].id.
inlinequeryopsionalHanya dihormati untuk gambar png, jpeg, jpg, gif, webp.
Request
curl -sL https://mailler.dimzhub.my.id/messages/ID_PESAN/attachment/ID_LAMPIRAN \
  -H "Authorization: Bearer $TOKEN" \
  -o lampiran.bin
Header respons
Content-Type: image/png
Content-Disposition: inline; filename*=UTF-8''logo.png
X-Content-Type-Options: nosniff
Access-Control-Allow-Origin: *
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}/download perlu token

Mengunduh email mentah dalam format RFC822 (.eml). Isinya persis seperti yang diterima Worker, tanpa pemotongan body.

PathTipeWajibKeterangan
idstringWAJIBPesan milik akun pemilik token.
Request
curl -sL https://mailler.dimzhub.my.id/messages/ID_PESAN/download \
  -H "Authorization: Bearer $TOKEN" \
  -o pesan.eml
Header respons
Content-Type: message/rfc822
Access-Control-Allow-Origin: *
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.

PathTipeWajibKeterangan
idstringWAJIBPesan milik akun pemilik token.
Request
curl -s https://mailler.dimzhub.my.id/sources/ID_PESAN \
  -H "Authorization: Bearer $TOKEN"
Response 200
{
  "@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}/download perlu token

Alias unduhan yang menempel pada resource sumber. Hasilnya sama dengan /messages/{id}/download.

PathTipeWajibKeterangan
idstringWAJIBPesan milik akun pemilik token.
Request
curl -sL https://mailler.dimzhub.my.id/sources/ID_PESAN/download \
  -H "Authorization: Bearer $TOKEN" \
  -o pesan.eml
Header respons
Content-Type: message/rfc822
Access-Control-Allow-Origin: *
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/mercure perlu 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.

QueryTipeWajibKeterangan
topicstringopsionalDapat diulang. Harus ada satu yang berakhiran /accounts/{id} milik pemilik token, jika tidak dibalas 403. Bila tidak dikirim sama sekali, stream tetap dibuka.
authorizationstringopsionalAlternatif header, karena EventSource di browser tidak bisa memasang header sendiri.
Request
curl -N "https://mailler.dimzhub.my.id/.well-known/mercure?topic=/accounts/ID_AKUN&authorization=$TOKEN"
Aliran respons
Content-Type: text/event-stream
Cache-Control: no-cache
Access-Control-Allow-Origin: *

:

id: 5c8f2b41-6d0e-4a97-8b3c-1f7ad0e29c58
data: {"@context":"/contexts/Account","@id":"/accounts/3f7c1a92-8d44-4c1e-9b02-77ac55de1f30","@type":"Account","id":"3f7c1a92-8d44-4c1e-9b02-77ac55de1f30","address":"contoh@dimzhub.my.id","quota":40000000,"used":24192,"isDisabled":false,"isDeleted":false,"createdAt":"2026-10-01T10:00:00.000Z","updatedAt":"2026-10-01T10:00:00.000Z"}

:
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.js Buka inbox