Mailkub API reference
Fetch mail, send mail, push inbound mail from a backend, and tag KTB / SCB transfer modes. No login required to read this page — share it with any developer.
Public page — no Mailkub login required. Share this URL with a developer.
Full reference for the Mailkub public API — use it to fetch mail, send mail, and pull a message's content into another system.
Overview
The Mailkub API lets an external system connect to one mailbox to fetch mail (list & read messages), send mail, push inbound mail from a backend, and pull a message's full content for display elsewhere. Put the mailbox address and API key in your backend — SMTP mail to you@yourdomain.com lands here automatically, or POST to /messages/inbound. Every API key created in the Keys tab is scoped to exactly one mailbox.
Authentication
Send the API key as a Bearer token on every request. Keys look like mk_live_… and are only ever shown once, right after you create them in the Keys tab.
Authorization: Bearer mk_live_xxxxxxxxxxxxxxxxxxxxxxxxRequests without a valid, non-revoked key for an active mailbox get 401 Unauthorized.
Quick start
Verify a key and see the mailbox it's scoped to:
curl "https://mailkub.cloud/api/v1/mailbox" \
-H "Authorization: Bearer mk_live_xxxxxxxxxxxxxxxxxxxxxxxx"{
"data": {
"id": "cmb...",
"emailAddress": "you@yourdomain.com",
"displayName": "Sales",
"domain": "acme.test",
"status": "ACTIVE",
"quotaBytes": 1073741824,
"usedStorageBytes": 52428800,
"folders": { "inbox": 12, "unread": 3, "sent": 40, "starred": 2, "drafts": 1, "trash": 0, "spam": 0 },
"transferModes": [
{ "value": "KTB_AUTO", "label": "KTBอัตโนมัติ", "description": "โอนผ่าน KTB Business API" },
{ "value": "EXPORT_KTB_BULK", "label": "ExportKTBBulk", "description": "ส่งออกไฟล์สำหรับ KTB Business" },
{ "value": "EXPORT_SCB_ANYWHERE", "label": "ExportSCBAnywhere", "description": "ส่งออกไฟล์สำหรับ SCB Anywhere" }
]
}
}1. ดึงข้อมูล — List messages
/messagesList messages in a folder
Paginated with a cursor. Defaults to the INBOX folder, newest first.
| Param | Type | Notes |
|---|---|---|
| folder | string | INBOX (default) | STARRED | SENT | DRAFT | TRASH | SPAM |
| limit | number | 1–100, default 20 |
| cursor | string | id of the last message from the previous page |
| unread | true|false | filter by read state |
| q | string | free-text search across subject/body/from/to |
| transferMode | string | KTB_AUTO | EXPORT_KTB_BULK | EXPORT_SCB_ANYWHERE (also accepts KTBอัตโนมัติ, ExportKTBBulk, ExportSCBAnywhere) |
curl "https://mailkub.cloud/api/v1/messages?folder=INBOX&limit=20" \
-H "Authorization: Bearer mk_live_xxxxxxxxxxxxxxxxxxxxxxxx"{
"data": [
{
"id": "cme_1",
"threadId": "cmt_1",
"from": "customer@example.com",
"to": "you@yourdomain.com",
"cc": null,
"bcc": null,
"subject": "Question about my order",
"folder": "INBOX",
"status": "RECEIVED",
"isRead": false,
"isStarred": false,
"createdAt": "2026-09-08T10:15:00.000Z",
"updatedAt": "2026-09-08T10:15:00.000Z",
"snippet": "Hi, I wanted to ask about...",
"hasAttachments": true,
"transferMode": "KTB_AUTO"
}
],
"meta": { "count": 1, "nextCursor": null }
}To page forward, call again with cursor=<meta.nextCursor> — repeat until nextCursor is null.
2. ส่งข้อมูล — Send a message
/messagesSend a message from this mailbox
| Param | Type | Notes |
|---|---|---|
| to* | string | string[] | recipient address(es) |
| cc / bcc | string | string[] | optional |
| subject | string | optional, defaults to empty |
| text | string | plain-text body — text or html is required |
| html | string | HTML body — text or html is required |
| inReplyTo | string | optional Message-Id being replied to |
| transferMode | string | optional: KTB_AUTO | EXPORT_KTB_BULK | EXPORT_SCB_ANYWHERE |
| attachments[] | { filename, contentType?, content } | content is base64-encoded file bytes; max 10 files, 15 MB each |
curl -X POST "https://mailkub.cloud/api/v1/messages" \
-H "Authorization: Bearer mk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"to": "customer@example.com",
"subject": "Re: Question about my order",
"text": "Hi! Thanks for reaching out...",
"transferMode": "KTB_AUTO",
"attachments": [
{ "filename": "invoice.pdf", "contentType": "application/pdf", "content": "JVBERi0xLjQK..." }
]
}'// 201 Created
{
"data": {
"messageId": "<a1b2c3@acme.test>",
"deliveredLocal": []
}
}deliveredLocal lists recipients that were delivered straight into another Mailkub mailbox on the same server (no outbound SMTP hop). Everyone else goes out over SMTP as usual.
3. ดึงแสดงข้อมูล — Fetch a message to display
/messages/:idGet one message's full content
Returns the plain-text and HTML body plus attachment metadata — enough to render the message elsewhere. Does not mark it as read.
curl "https://mailkub.cloud/api/v1/messages/cme_1" \
-H "Authorization: Bearer mk_live_xxxxxxxxxxxxxxxxxxxxxxxx"{
"data": {
"id": "cme_1",
"threadId": "cmt_1",
"messageId": "<a1b2c3@example.com>",
"inReplyTo": null,
"from": "customer@example.com",
"to": "you@yourdomain.com",
"cc": null,
"bcc": null,
"replyTo": null,
"subject": "Question about my order",
"text": "Hi, I wanted to ask about...",
"html": "<p>Hi, I wanted to ask about...</p>",
"folder": "INBOX",
"status": "RECEIVED",
"isRead": false,
"isStarred": false,
"transferMode": "EXPORT_KTB_BULK",
"createdAt": "2026-09-08T10:15:00.000Z",
"updatedAt": "2026-09-08T10:15:00.000Z",
"attachments": [
{
"id": "att_1",
"filename": "receipt.png",
"mimeType": "image/png",
"size": 48213,
"downloadUrl": "/api/v1/messages/cme_1/attachments/att_1"
}
]
}
}/messages/:id/attachments/:attachmentIdDownload an attachment
Streams the raw file by default. Add ?format=base64 to get JSON with the file base64-encoded instead.
curl "https://mailkub.cloud/api/v1/messages/cme_1/attachments/att_1" -OJ \
-H "Authorization: Bearer mk_live_xxxxxxxxxxxxxxxxxxxxxxxx"
# or, as JSON/base64:
curl "https://mailkub.cloud/api/v1/messages/cme_1/attachments/att_1?format=base64" \
-H "Authorization: Bearer mk_live_xxxxxxxxxxxxxxxxxxxxxxxx"/messages/:idUpdate read / starred / folder state
Send only the fields you want to change.
curl -X PATCH "https://mailkub.cloud/api/v1/messages/cme_1" \
-H "Authorization: Bearer mk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "isRead": true }'/messages/:idMove to Trash
Calling it again on a message already in Trash permanently deletes it (same as the web app).
4. ใส่ API หลังบ้าน — inbound mail
Save the API key on your backend. SMTP mail sent to you@yourdomain.com arrives automatically. If the other system cannot send SMTP, have it POST here and the message lands in this inbox the same way — webhook included.
/messages/inboundPush incoming mail into this mailbox
Always delivers to the mailbox this API key belongs to. Use transferMode to tag KTB / SCB channels from the transfer-mode picker.
| Param | Type | Notes |
|---|---|---|
| from* | string | sender address |
| to | string | string[] | optional; stored against this mailbox |
| subject | string | optional |
| text / html | string | at least subject, text, or html |
| transferMode | string | KTB_AUTO | EXPORT_KTB_BULK | EXPORT_SCB_ANYWHERE — also accepted as header X-Transfer-Mode |
| attachments[] | { filename, contentType?, content } | content is base64; use this for KTB Bulk / SCB Anywhere export files |
curl -X POST "https://mailkub.cloud/api/v1/messages/inbound" \
-H "Authorization: Bearer mk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-H "X-Transfer-Mode: ExportKTBBulk" \
-d '{
"from": "payroll@company.com",
"subject": "KTB Bulk payment file",
"text": "Attached is the KTB Business export.",
"transferMode": "ExportKTBBulk",
"attachments": [
{ "filename": "ktb-bulk.txt", "contentType": "text/plain", "content": "S1RCIGJ1bGsgZmlsZQ==" }
]
}'// 201 Created
{
"data": { "accepted": ["you@yourdomain.com"] }
}| transferMode | UI label | Meaning |
|---|---|---|
| KTB_AUTO | KTBอัตโนมัติ | โอนผ่าน KTB Business API |
| EXPORT_KTB_BULK | ExportKTBBulk | ส่งออกไฟล์สำหรับ KTB Business |
| EXPORT_SCB_ANYWHERE | ExportSCBAnywhere | ส่งออกไฟล์สำหรับ SCB Anywhere |
5. Webhooks — get notified the moment mail arrives
Instead of polling GET /messages, set a webhook URL in the Webhook tab and Mailkub will POST a JSON payload to it the instant new mail lands in this mailbox. A common pattern: your test/automation requests a password reset on some third-party site using you@yourdomain.com as the address — the moment their recovery email arrives here, your endpoint is called with the full message so you can pull out the code or link without a polling loop.
Fires on event message.received — covers direct delivery, mailbox-to-mailbox delivery, auto-replies, and forwards. It never fires for the copy Mailkub keeps in your own Sent folder when you send a message.
Request
| Param | Type | Notes |
|---|---|---|
| Content-Type | header | application/json |
| X-Mailkub-Event | header | message.received | webhook.test |
| X-Mailkub-Signature | header | t=<unix ms>,v1=<hmac-sha256 hex of `${t}.${body}`> |
{
"event": "message.received",
"timestamp": "2026-09-09T10:15:00.000Z",
"data": {
"id": "cme_1",
"threadId": "cmt_1",
"messageId": "<a1b2c3@example.com>",
"from": "no-reply@example.com",
"to": "you@yourdomain.com",
"cc": null,
"bcc": null,
"subject": "Reset your password",
"text": "Your recovery code is 482913. It expires in 10 minutes.",
"html": "<p>Your recovery code is <b>482913</b>...</p>",
"folder": "INBOX",
"isRead": false,
"isStarred": false,
"transferMode": "EXPORT_SCB_ANYWHERE",
"createdAt": "2026-09-09T10:15:00.000Z",
"attachments": []
}
}Verifying the signature (Node.js)
const crypto = require("crypto");
function isValidMailkubSignature(secret, header, rawBody) {
const [tPart, vPart] = header.split(",");
const timestamp = tPart.split("=")[1];
const signature = vPart.split("=")[1];
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
// header = req.headers["x-mailkub-signature"], rawBody = the exact request body string
// secret = the whsec_... value shown once when you saved/regenerated the webhookRespond with any 2xx status within 8 seconds. A non-2xx response or timeout triggers one retry after ~4 seconds — after that, Mailkub gives up and you'd need to fetch the message via GET /messages/:id instead.
Use the Send test event button in the Webhook tab to verify your endpoint is reachable before relying on it — it sends the same headers with event: "webhook.test".
Errors
Errors always look like this:
{ "error": { "code": "invalid_body", "message": "Provide `text` or `html`..." } }| Param | Type | Notes |
|---|---|---|
| 400 | invalid_json / send_failed | malformed request or delivery failure |
| 401 | unauthorized | missing, invalid, or revoked API key |
| 403 | mailbox_disabled | the mailbox this key belongs to is disabled |
| 404 | not_found | message or attachment not found for this mailbox |
| 413 | payload_too_large | request body over the size limit |
| 422 | invalid_query / invalid_body | failed schema validation |
| 429 | rate_limited | too many requests — back off and retry |
Rate limits
Up to 300 requests/minute per API key and 120 requests/minute per IP for authentication attempts. Exceeding either returns 429 — retry with backoff.
Node.js / JavaScript example
const API_KEY = process.env.MAILKUB_API_KEY;
const BASE_URL = "https://mailkub.cloud/api/v1";
async function listUnread() {
const res = await fetch(`${BASE_URL}/messages?folder=INBOX&unread=true`, {
headers: { Authorization: `Bearer ${API_KEY}` },
});
if (!res.ok) throw new Error((await res.json()).error.message);
return (await res.json()).data;
}
async function sendMail({ to, subject, text }) {
const res = await fetch(`${BASE_URL}/messages`, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ to, subject, text }),
});
if (!res.ok) throw new Error((await res.json()).error.message);
return (await res.json()).data;
}Security notes
- Keys are shown once, at creation. Mailkub only ever stores a one-way hash of the key.
- Revoke a key immediately from the Keys tab if it may have leaked — the change takes effect instantly.
- Each key is scoped to one mailbox only; create a separate key per mailbox/integration.
- Always call the API over HTTPS and never embed a key in client-side code.