Get your API key
API reference

Sending

Send transactional email over a verified domain, and upload attachments to reference in sends. See Send API for a guided walk-through.

Auth: API key · Base URL: https://api.mailkite.dev

Endpoints

Each row expands to its request and response schema. The address bar follows along, so any endpoint can be linked to directly.

POST /v1/send Send a message over a verified domain. Pass `templateId` (+ optional `templateData`) to send from a saved or base template.

Send a message over a verified domain. Pass `templateId` (+ optional `templateData`) to send from a saved or base template.

Request body

send-request.json
FieldTypeNotes
from req stringAn address on a verified domain.
to req string · string[]One recipient or a list.
subjectstringRequired unless supplied by a template.
htmlstring
textstring
templateIdstringSend using a saved template — a user template (tpl_…) or a base template (base_…). Its subject/html/text seed the message; explicit subject/html/text here override them.
templateDataobjectValues substituted into the template's {{merge_tags}} (e.g. {"name":"Ann"} fills {{name}}). HTML values are auto-escaped.
ccstring · string[]
bccstring · string[]
replyTostring
inReplyTostring
headersobjectExtra raw MIME headers, applied after threading headers (caller wins). Use for what the structured fields can't express — e.g. `List-Unsubscribe`, a dedup/idempotency key (`X-Entity-Ref-ID`), or a tag header (`X-Tag`). Carried on both immediate and scheduled sends.
attachmentsobject[]
attachments[].filename req string
attachments[].urlstringFetch the file from this URL at send time and attach it. Any URL works; an uploadAttachment() URL is the secure, recommended choice. Prefer this over `content` for large files.
attachments[].contentstringInline file bytes as base64. Simple for tiny files, but re-uploaded on every send — use `url` (see uploadAttachment) for anything large.
attachments[].contentTypestringMIME type; defaults to application/octet-stream.
scheduledAtstring · numberSend later: ISO 8601, simple relative natural language ("in 2 hours"), or a ms-epoch. A future time parks the message with the scheduler — the response carries an ssnd_… id and status "scheduled", cancelable via DELETE /v1/scheduled/{id}. Omitted or past sends now.
trackOpensbooleanOpen-tracking override for this send (HTML only). Omitted → the from-domain's default applies.
trackClicksbooleanClick-tracking override for this send (HTML only): http(s) links are rewritten to a signed redirect that records the click, then 302s to the destination. Omitted → the from-domain's default applies.

Response body

send-response.json
FieldTypeNotes
id req string
status req string
scheduledAtnumberPresent on scheduled sends: when the message will fire, in ms epoch.
POST /v1/send/batch Send one personalized message per recipient (up to 50) in a single call. Shared fields form the base message; each `recipients[]` entry gets its own message to exactly one address, with per-recipient `templateData` and `headers` merged over the shared ones. Every message passes the same gates as send() and gets its own id; the response reports each recipient's outcome in order, so a batch can partially succeed. Pass `scheduledAt` to park the whole batch for later (one cancelable ssnd_… per recipient).

Send one personalized message per recipient (up to 50) in a single call. Shared fields form the base message; each `recipients[]` entry gets its own message to exactly one address, with per-recipient `templateData` and `headers` merged over the shared ones. Every message passes the same gates as send() and gets its own id; the response reports each recipient's outcome in order, so a batch can partially succeed. Pass `scheduledAt` to park the whole batch for later (one cancelable ssnd_… per recipient).

Request body

batch-send-request.json

Send one personalized message per recipient in a single call. Shared fields (subject/html/text/templateId/templateData/headers/…) form the base message; each recipients[] entry produces an independent message to exactly one address, with its own templateData and headers merged over the shared ones (per-recipient wins). Each message passes the same gates as a single send() and gets its own id — recipients never see each other.

FieldTypeNotes
from req stringAn address on a verified domain. Shared by every message in the batch.
recipients req object[]One entry per message. Order is preserved in the response's results[].
recipients[].to req stringExactly one recipient address (bare or `Name <addr>`). This message is sent to this address only.
recipients[].templateDataobjectPer-recipient merge values, merged over the shared templateData (this wins on conflicts). Fills {{merge_tags}} in the subject/html/text for this recipient's message.
recipients[].headersobjectPer-recipient extra raw MIME headers, merged over the shared headers (this wins on conflicts).
subjectstringRequired unless supplied by a template. May contain {{merge_tags}}, filled per recipient.
htmlstringMay contain {{merge_tags}}, filled per recipient.
textstringMay contain {{merge_tags}}, filled per recipient.
templateIdstringSend using a saved template — a user template (tpl_…) or a base template (base_…). Its subject/html/text seed every message; explicit subject/html/text here override them.
templateDataobjectShared default merge values for every recipient; a recipient's own templateData overrides these key-by-key. HTML values are auto-escaped.
headersobjectShared extra raw MIME headers for every message, applied after threading headers (caller wins); a recipient's own headers override these key-by-key.
replyTostring
inReplyTostringThread every message under this Message-ID.
attachmentsobject[]Attached to every message in the batch. Same shape as send()'s attachments.
attachments[].filename req string
attachments[].urlstringFetch the file from this URL at send time and attach it. Any URL works; an uploadAttachment() URL is the secure, recommended choice. Prefer this over `content` for large files.
attachments[].contentstringInline file bytes as base64. Simple for tiny files, but re-uploaded on every send — use `url` (see uploadAttachment) for anything large.
attachments[].contentTypestringMIME type; defaults to application/octet-stream.
scheduledAtstring · numberSend later: ISO 8601, simple relative natural language ("in 2 hours"), or a ms-epoch. A future time parks every message with the scheduler (each gets its own ssnd_… id, individually cancelable); omitted or past sends now.
trackOpensbooleanOpen-tracking override for every message in the batch (HTML only). Omitted → the from-domain's default applies.
trackClicksbooleanClick-tracking override for every message in the batch (HTML only): http(s) links are rewritten to a signed redirect that records the click, then 302s to the destination. Omitted → the from-domain's default applies.

Response body

batch-send-response.json

Per-recipient outcomes, in the same order as the request's recipients[]. The call returns 200 whenever the batch itself was accepted — check each result's status: a batch can partially succeed (e.g. one address suppressed, the rest sent). Every non-failed result carries its own message id.

FieldTypeNotes
results req object[]One entry per recipients[] entry, same order.
results[].to req stringThe recipient this result is for (echoed from the request).
results[].idstringThe message id (msg_…), or the scheduled-send id (ssnd_…) when parked for later. Absent on failure.
results[].status req stringsent | scheduled | failed.
results[].errorstringHuman-readable reason, present only when status is failed.
results[].codestringMachine-readable reason (e.g. recipient_suppressed, daily_limit), present only when status is failed.
sent req integerCount of results with status sent.
scheduled req integerCount of results with status scheduled.
failed req integerCount of results with status failed.
POST /v1/attachments Upload a file to MailKite storage and get back a secure, time-limited URL. Reference the returned `url` as an attachment in send() (`{ filename, url }`) or link it inline in your HTML — instead of base64-inlining large files on every send. Give the file ONE of four ways: a local `path` (read and streamed as raw bytes by the CLI/SDK/local MCP), a remote `url` (MailKite fetches and re-hosts it), base64 `content`, or — over raw HTTP — the file bytes as the POST body with `?filename=`. `retentionDays` (7/30/90/365, default 7) sets how long the file and URL live.

Upload a file to MailKite storage and get back a secure, time-limited URL. Reference the returned `url` as an attachment in send() (`{ filename, url }`) or link it inline in your HTML — instead of base64-inlining large files on every send. Give the file ONE of four ways: a local `path` (read and streamed as raw bytes by the CLI/SDK/local MCP), a remote `url` (MailKite fetches and re-hosts it), base64 `content`, or — over raw HTTP — the file bytes as the POST body with `?filename=`. `retentionDays` (7/30/90/365, default 7) sets how long the file and URL live.

Request body

upload-attachment-request.json

Provide the file ONE of four ways: `path` (a local file the SDK/CLI/MCP reads and streams as raw bytes), `url` (a remote file MailKite fetches and re-hosts), `content` (base64-encoded bytes), or — at the HTTP layer — the raw file bytes as the POST body with `?filename=`. `path`/raw-bytes go up as a real binary upload (like an S3/R2 PUT); `url`/`content` go up as JSON.

FieldTypeNotes
filenamestringThe file's name, e.g. "invoice.pdf". Shown to recipients on download. Optional when it can be derived from `path` or `url`.
pathstringLocal filesystem path to the file. Read client-side by the CLI, SDKs, and the local MCP server, then uploaded as raw bytes. Not available on the hosted MCP (no filesystem).
urlstringA remote http(s) URL. MailKite fetches it and re-hosts the bytes under your account. Max 25 MB.
contentstringThe file bytes, base64-encoded. The lowest-common-denominator fallback when you can't send a path, URL, or raw bytes.
contentTypestringMIME type, e.g. "application/pdf". Defaults to application/octet-stream (or is inferred from the file extension / fetched response).
retentionDays7 · 30 · 90 · 365How long the file (and its signed URL) stays valid. One of 7, 30, 90, 365. Defaults to 7.

Response body

upload-attachment-response.json
FieldTypeNotes
idstringStorage key for the uploaded file.
urlstringSecure, time-limited URL. Pass it as an attachment's `url` in send(), or link it inline in your HTML.
filenamestring
contentTypestring
sizeintegerStored size in bytes.
expiresAtstringISO-8601 timestamp when the file and URL expire.