Send API
One endpoint, one key. The same address that receives can send — transactional mail, replies, and broadcasts go out over your own authenticated domain, DKIM-signed and aligned so they land in the inbox.
Send a message
POST /v1/send with an API key.
The from address must be on a verified domain. Pick your language —
the choice is remembered across the docs.
Messages → Compose
From hello@myapp.ai
To ada@example.com
Subject Your invoice #1042
Body Thanks! Receipt attached.
Click [ Send ]Email ada@example.com from hello@myapp.ai
with subject "Your invoice #1042" and body "Thanks! Receipt attached."import { MailKite } from "mailkite";
const mk = new MailKite(process.env.MAILKITE_API_KEY);
const { id, status } = await mk.send({
from: "hello@myapp.ai",
to: "ada@example.com",
subject: "Your invoice #1042",
html: "<p>Thanks! Receipt attached.</p>",
});import os
from mailkite import MailKite
mk = MailKite(os.environ["MAILKITE_API_KEY"])
res = mk.send({
"from": "hello@myapp.ai",
"to": "ada@example.com",
"subject": "Your invoice #1042",
"html": "<p>Thanks! Receipt attached.</p>",
})<?php
$mk = new \MailKite\Client(getenv('MAILKITE_API_KEY'));
$res = $mk->send([
'from' => 'hello@myapp.ai',
'to' => 'ada@example.com',
'subject' => 'Your invoice #1042',
'html' => '<p>Thanks! Receipt attached.</p>',
]);MailKite mk = new MailKite(System.getenv("MAILKITE_API_KEY"));
Object res = mk.send(Map.of(
"from", "hello@myapp.ai",
"to", "ada@example.com",
"subject", "Your invoice #1042",
"html", "<p>Thanks! Receipt attached.</p>"
));mk := mailkite.New(os.Getenv("MAILKITE_API_KEY"))
res, err := mk.Send(mailkite.Message{
From: "hello@myapp.ai",
To: "ada@example.com",
Subject: "Your invoice #1042",
HTML: "<p>Thanks! Receipt attached.</p>",
})require "mailkite"
mk = Mailkite::Client.new(ENV["MAILKITE_API_KEY"])
res = mk.send(
"from" => "hello@myapp.ai",
"to" => "ada@example.com",
"subject" => "Your invoice #1042",
"html" => "<p>Thanks! Receipt attached.</p>"
)curl https://api.mailkite.dev/v1/send \
-H "Authorization: Bearer $MAILKITE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "hello@myapp.ai",
"to": "ada@example.com",
"subject": "Your invoice #1042",
"html": "<p>Thanks! Receipt attached.</p>"
}' ← 202 Accepted
{ "id": "msg_2Hk9…", "status": "queued" }
A 202 Accepted means the message was queued for delivery. Use the
returned id to look it up later via the
Messages API.
Request fields
| Field | Type | Notes |
|---|---|---|
from | string | Required. An address on a verified domain. |
to | string · array | Required. One address or a list. |
subject | string | Subject line — required unless a template supplies it. |
html | string | HTML body. Provide html, text, or both. |
text | string | Plain-text body. Recommended alongside html. |
templateId | string | Send a saved template — a user template (tpl_…) or base template (base_…); explicit subject/html/text override it. |
templateData | object | Values for the template's {{merge_tags}} (e.g. { "name": "Ann" }). HTML values are auto-escaped. |
cc / bcc | string · array | Optional additional recipients. |
replyTo | string | Optional Reply-To address. |
attachments | array | Each: { filename, url } (fetched by us) or { filename, content } (base64). |
inReplyTo | string | A threadId/Message-ID to reply in-thread. |
headers | object | Extra raw MIME headers (string → string), applied after threading headers. For what the fields above can't express — List-Unsubscribe, a dedup key (X-Entity-Ref-ID), a tag (X-Tag). Carried on immediate and scheduled sends. |
scheduledAt | string · number | Send later: ISO 8601, "in 2 hours", or a ms-epoch. A future time parks the message (response carries an ssnd_… id, status scheduled; cancel via DELETE /v1/scheduled/{id}). |
trackOpens | boolean | Open-tracking override for this send (HTML only). Omitted → the domain's default applies. |
trackClicks | boolean | Click-tracking override (HTML only): links are rewritten to a signed redirect that records the click, then sends the reader on. Omitted → the domain's default applies. |
Every snippet above is the official client library —
same shape in Node, Python, PHP, Java, Go, and Ruby — or raw curl.
Batch send
POST /v1/send/batch sends 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 its templateData and headers merged
over the shared ones (per-recipient wins). Recipients never see each other.
POST /v1/send/batch {
"from": "billing@myapp.ai",
"subject": "Your {{plan}} invoice",
"html": "<p>Hi {{name}}, your {{plan}} invoice is ready.</p>",
"templateData": { "plan": "Pro" },
"recipients": [
{ "to": "Ada Lovelace <ada@example.com>", "templateData": { "name": "Ada" } },
{ "to": "grace@example.com", "templateData": { "name": "Grace", "plan": "Team" } }
]
}
response ← 200 OK
{
"results": [
{ "to": "Ada Lovelace <ada@example.com>", "id": "msg_2Hk9…", "status": "sent" },
{ "to": "grace@example.com", "id": "msg_8Rw2…", "status": "sent" }
],
"sent": 2, "scheduled": 0, "failed": 0
}
Every message passes the same gates as a single send and gets its own id, so
a batch can partially succeed — check each result's
status (sent · scheduled ·
failed). Results come back in request order. Pass
scheduledAt to park the whole batch: each recipient gets its own
cancelable ssnd_… scheduled send.
Batch request fields
Field Type Notes fromstring Required. An address on a verified domain, shared by every message. recipientsarray Required. 1–50 entries, each { to, templateData?, headers? } — one message to that one address, its values merged over the shared ones (per-recipient wins). subject / html / textstring The base message. May contain {{merge_tags}}, filled per recipient. templateIdstring Seed the base message from a saved template (tpl_… or base_…). templateDataobject Shared default merge values; a recipient's own templateData wins key-by-key. headersobject Shared extra MIME headers; a recipient's own headers win key-by-key. replyTo / inReplyTostring Applied to every message. attachmentsarray Attached to every message. Same shape as a single send. scheduledAtstring · number Park the whole batch for later — one cancelable scheduled send per recipient. trackOpensboolean Open-tracking override for every message (HTML only). trackClicksboolean Click-tracking override for every message (HTML only).
Replying in-thread
To reply to something you received, set inReplyTo to the inbound
event's threadId. The reply threads correctly in the
recipient's client:
reply.json {
"from": "support@myapp.ai",
"to": "ada@example.com",
"subject": "Re: invoice #1042",
"text": "Approved — thanks!",
"inReplyTo": "<a1b2c3@mail.example.com>"
}
Attachments
Attach files two ways:
- By URL —
{ filename, url }. We fetch it at send time. Best for files you already host. - Inline —
{ filename, content, contentType } with base64 content. Best for small, generated files.
Engagement events
Want the results pushed to you? Set a per-domain
tracking webhook and MailKite POSTs signed
email.sent, email.bounced,
email.complained, email.opened and
email.clicked events as they happen — opens and clicks flag
proxy/scanner hits (machine: true) so your human counts stay
honest, and bounces carry the DSN diagnostic your suppression logic needs.
It's a separate URL from the inbound webhook, verified the
same way.
Deliverability
Outbound is DKIM-signed and SPF/DMARC-aligned automatically on your verified
domain — there's nothing to configure beyond
publishing your DNS records. Always send a
text part alongside html, keep your
from consistent, and warm new domains gradually for the best
inbox placement.
See every parameter and response in the
API reference.