API DOCUMENTATION
Introduction
MailKaka is the email API for India-first products.
Everything you need to send with MailKaka.
MailKaka runs a familiar transactional email API on Mumbai-region infrastructure. This page covers the base URL, authentication, request/response shapes, webhook events, and error codes -- grounded directly in the current API implementation, not aspirational copy.
A plain-markdown version of this page (for feeding to an AI tool or coding agent) is available at www.mailkaka.in/docs.md. An index for AI crawlers is at www.mailkaka.in/llms.txt.
BASE URL
One base URL for every request.
https://www.mailkaka.in/apiSelf-hosting MailKaka? Use https://<your-deployment-host>/api instead.
Use the www.mailkaka.in host shown above for MailKaka Cloud examples. Older examples using https://mailkaka.in/api should be updated to https://www.mailkaka.in/api.
AUTHENTICATION
Bearer token, one header.
Every request needs an API key created from the MailKaka dashboard, sent as a Bearer token:
Authorization: Bearer mk_xxxxxxxx...
Content-Type: application/jsonKeys are scoped to one sending domain and a set of permissions (e.g. send). A missing or non-matching key returns 401.
VALIDATE A KEY
Check a key without sending mail.
Confirm a key is live, and see exactly what it's scoped to, before wiring up real sends:
curl 'https://www.mailkaka.in/api/api-keys/validate' \
-H 'Authorization: Bearer mk_xxxxxxxx...'{
"valid": true,
"key": { "name": "Production", "permissions": ["send"] },
"domain": { "domain": "yourcompany.in", "status": "verified" },
"organization": { "name": "Your Company" }
}Any key that authenticates reports valid: true even if it lacks send permission or its domain isn't verified -- those come back as data so you can see what's wrong, rather than the check itself failing. A bad or missing key returns 401 with { "valid": false, "error": "..." }.
SEND AN EMAIL
POST /emails.
curl -X POST 'https://www.mailkaka.in/api/emails' \
-H 'Authorization: Bearer mk_xxxxxxxx...' \
-H 'Content-Type: application/json' \
-d '{
"from": "hello@yourcompany.in",
"to": ["customer@example.com"],
"subject": "Your order is confirmed",
"html": "<strong>Shipped from India.</strong>"
}'await fetch('https://www.mailkaka.in/api/emails', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.MAILKAKA_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
from: 'hello@yourcompany.in',
to: ['customer@example.com'],
subject: 'Your order is confirmed',
html: '<strong>Shipped from India.</strong>',
}),
});| Field | Type | Notes |
|---|---|---|
| from | string, required | Accepts email@domain.com or "Name <email@domain.com>". The inner address must match the verified sending domain on the key. |
| to | string or string[], required | One or more recipients. At most 50 total recipients across to, cc, and bcc. |
| cc, bcc | string or string[], optional | Same shape as to; included in the 50-recipient limit. |
| subject | string, required | |
| html, text | string, optional | At least one of the two is required. |
| attachments | array, optional | { filename, content (base64), contentType }. Combined base64 content is capped at 8MB; executable extensions (.exe, .js, .msi, etc.) are rejected. |
| reply_to | string or string[], optional | |
| tags | object or array, optional | Flat string map, or Resend-style [{ name, value }]. |
| unsubscribe | boolean, optional, default false | Adds a one-click List-Unsubscribe header. Requires exactly one to recipient -- rejected with 400 otherwise. Opt-in only; never set on OTP/receipt mail. |
Send an Idempotency-Key header to make retries safe: a repeated key returns the original result instead of sending again.
SEND A BATCH
POST /emails/batch.
Send between 1 and 100 independently addressed messages in one request. The full payload is validated first, and quota plus queue inserts are committed atomically.
const response = await fetch('https://www.mailkaka.in/api/emails/batch', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.MAILKAKA_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'order-notifications-2026-08-03',
},
body: JSON.stringify([
{
from: 'Acme <hello@yourcompany.in>',
to: ['first@example.com'],
subject: 'Your first update',
html: '<strong>First message</strong>',
},
{
from: 'Acme <hello@yourcompany.in>',
to: ['second@example.com'],
subject: 'Your second update',
html: '<strong>Second message</strong>',
},
]),
});{
"data": [
{ "id": "..." },
{ "id": "..." }
]
}One Idempotency-Key protects the whole batch. A retry returns the original IDs in request order without consuming quota again. If any item is invalid, no item is queued.
MIGRATING FROM RESEND
What changes when you switch.
MailKaka is India-first email infrastructure, not a Resend clone. If you are moving an existing Resend integration over, the single-message send shape at POST /emails is intentionally familiar, including array-shaped to, cc, and bcc. Display-name senders and batch sends are supported; other Resend-specific features may still need explicit changes.
from: "Name <email@domain.com>"is accepted. The inner address must belong to the domain scoped to the API key.resend.batch.send(...)/POST /emails/batchsupports up to 100 messages.- Open and click tracking events are not emitted.
LOGS
Dashboard logs plus authenticated API.
Delivery and request logs are visible in the dashboard. An authenticated logs API is also available:
GET https://www.mailkaka.in/api/emails/logs
Authorization: Bearer mk_xxxxxxxx...Logs are scoped to the API key's organization and can be filtered by query params such as days, status, domain_id, api_key_id, and search. There is no public unauthenticated logs endpoint.
WEBHOOKS
sent, delivered, bounced, complained.
Configure an endpoint URL and event subscriptions from the dashboard. MailKaka posts:
sentdeliveredbouncedcomplained
There is no open/click tracking, and no inbound-email webhook -- inbound mail is delivered into MailKaka's own hosted team mailbox rather than forwarded to your endpoint. If you need programmatic access to inbound mail today, that's a known gap in the current API; there is no workaround yet.
Each delivery is signed:
X-Mailkaka-Signature: <hex HMAC-SHA256 of the raw request body, keyed with your endpoint's whsec_ secret>Verify by recomputing the HMAC over the raw body with your stored secret and comparing.
ERRORS
Status codes you'll actually see.
| Status | Meaning |
|---|---|
| 400 | Request validation failed (missing html/text, invalid email, from domain mismatch, or batch outside 1-100 messages). Used instead of 422. |
| 401 | Missing or invalid Authorization header / API key. |
| 403 | Key lacks send permission, the sending domain isn't verified, or the organization is suspended. |
| 409 | A batch Idempotency-Key was reused with a different payload. |
| 429 | Monthly send quota exhausted for the plan. |
{ "error": "From email must be from domain: yourcompany.in" }MIGRATING YOUR INTEGRATION
Hand this prompt to an AI coding agent.
MailKaka's send API is intentionally shaped like the send-email APIs most libraries and codebases already use, so most integrations only need a base URL and key swap. If you're moving over from an existing provider, paste this into Claude Code, Cursor, or ChatGPT for a full audit against your codebase:
You are migrating a codebase from Resend (https://resend.com) to MailKaka (https://www.mailkaka.in), an India-first transactional email and business mailbox platform. Full reference: https://www.mailkaka.in/docs.md
Do the following:
1. Find every use of the Resend SDK/API in this repo: `new Resend(...)`, `resend.emails.send(...)`, `resend.batch.send(...)`, the `RESEND_API_KEY` env var, and any raw HTTP calls to `api.resend.com`.
2. Base URL and auth:
- Replace the Resend base URL with MailKaka's: https://www.mailkaka.in/api
- Replace `RESEND_API_KEY` with a MailKaka key (starts with `mk_`) in an env var such as `MAILKAKA_API_KEY`.
- Existing Resend Node SDK single-message sends can usually be migrated by passing `baseURL` in the constructor: `new Resend(MAILKAKA_API_KEY, { baseURL: 'https://www.mailkaka.in/api' })`. Raw HTTP/cURL callers just change the host and Bearer token.
3. Sender addresses: MailKaka accepts both `from: 'email@domain.com'` and `from: 'Display Name <email@domain.com>'`. The inner email address must belong to the verified domain scoped to the API key.
4. `to`/`cc`/`bcc` behavior matches Resend: pass an array of addresses in one request; do not fan a multi-recipient send out into one request per recipient. Exception: if the code opts into MailKaka's `unsubscribe: true` field, that requires exactly one `to` recipient per request -- Resend has no equivalent flag, so only handle this if you're adding new MailKaka-specific behavior, not as part of a like-for-like migration.
5. Batch sends: MailKaka implements `POST /emails/batch` for arrays of 1-100 messages and returns `{ data: [{ id }] }` in request order. Existing `resend.batch.send(...)` calls can use it after the same base URL and key change. The full batch is rejected before queueing if any item is invalid. Preserve any existing `Idempotency-Key` header; MailKaka uses it to deduplicate the whole batch.
6. Webhooks: MailKaka only emits `sent`, `delivered`, `bounced`, `complained` events, signed via HMAC-SHA256 in an `X-Mailkaka-Signature` header (verify the same way you'd verify Resend's `svix-signature`, just with a plain HMAC of the raw body instead of Svix). MailKaka has no `opened`/`clicked` tracking events and no customer-facing inbound-email webhook -- inbound mail is instead delivered into MailKaka's own hosted team mailbox, not forwarded to your endpoint. If the code has handlers for those Resend event types, flag them clearly as unsupported rather than silently dropping the logic; do not fabricate a workaround.
7. Error shapes differ: MailKaka returns HTTP 400 (not 422) for request-validation failures, 401 for a missing/invalid API key, 403 for a key without send permission (or an unverified/suspended sending domain), and 429 when the plan's monthly send quota is exhausted. Update any status-code-specific error handling accordingly.
8. Before cutting over traffic, call `GET https://www.mailkaka.in/api/api-keys/validate` with the new key (`Authorization: Bearer mk_...`) to confirm it's live and see its permissions/domain without sending a real email.
Ask the user before removing any Resend-specific code you're not fully certain has a MailKaka equivalent -- prefer flagging an unsupported feature over deleting working logic.