Receive any message as structured JSON, send with a single API call, and skip the mail server entirely. Give an agent its own inbox, or wire a whole domain — across unlimited domains and mailboxes, free. Set it up once and reuse it on every product you launch.
Start sending and receiving email
Unlimited domains ·Unlimited inboxes ·3,000 emails/mo — no daily cap
{ "from": "ada@example.com",
"subject": "Re: invoice #1042",
"attachments": [ "po.pdf" ] } prompt: "Reply to support requests yourself.
Forward anything important to me at
humans@myapp.ai." Programmable email is email you drive entirely from code — send with one API call, receive every message as structured JSON on a webhook, and give any app or AI agent its own inbox on a domain you control, with no mail server to run. It turns email into a two-way programmable primitive: not just a way to send notifications, but a channel your software can also receive, parse, and reply on.
(Not to be confused with the marketing sense of the term — templated personalization in campaign emails. This is the developer definition: email as programmable I/O. Read the full breakdown →)
One endpoint, one API key. Transactional mail, replies, and broadcasts over your own authenticated, warmed-up domain — built on Cloudflare's edge.
Point your MX at MailKite and every inbound message is parsed and POSTed to your endpoint as clean JSON — body, headers, and attachments. No IMAP, no mail server.
Give an AI agent its own address on a domain you own. It receives parsed mail, holds threads, and replies — bring your own model loop, or let MailKite run it.
Official libraries for Node, Python, PHP, Java, Go, and Ruby — one shape everywhere — plus the CLI, plain REST, SMTP, and IMAP. Or flip to the agent path and connect any MCP client in one snippet.
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 { MailKite } from "mailkite";
const mk = new MailKite(process.env.MAILKITE_API_KEY);
export async function POST() {
const { id, status } = await mk.send({
from: "hello@myapp.ai",
to: "ada@example.com",
subject: "Your invoice #1042",
html: "<p>Thanks! Receipt attached.</p>",
});
return Response.json({ id, status });
} import { MailKite } from "mailkite";
const mk = new MailKite(process.env.MAILKITE_API_KEY);
export async function action() {
const { id, status } = await mk.send({
from: "hello@myapp.ai",
to: "ada@example.com",
subject: "Your invoice #1042",
html: "<p>Thanks! Receipt attached.</p>",
});
return Response.json({ id, status });
} import { MailKite } from "mailkite";
const mk = new MailKite(process.env.MAILKITE_API_KEY);
export default defineEventHandler(async () => {
const { id, status } = await mk.send({
from: "hello@myapp.ai",
to: "ada@example.com",
subject: "Your invoice #1042",
html: "<p>Thanks! Receipt attached.</p>",
});
return { id, status };
}); import express from "express";
import { MailKite } from "mailkite";
const app = express();
const mk = new MailKite(process.env.MAILKITE_API_KEY);
app.post("/send", async (_req, res) => {
const { id, status } = await mk.send({
from: "hello@myapp.ai",
to: "ada@example.com",
subject: "Your invoice #1042",
html: "<p>Thanks! Receipt attached.</p>",
});
res.json({ id, status });
}); import { Hono } from "hono";
import { MailKite } from "mailkite";
const app = new Hono();
app.post("/send", async (c) => {
const mk = new MailKite(c.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>",
});
return c.json({ id, status });
});
export default app; import type { APIRoute } from "astro";
import { MailKite } from "mailkite";
const mk = new MailKite(import.meta.env.MAILKITE_API_KEY);
export const POST: APIRoute = async () => {
const { id, status } = await mk.send({
from: "hello@myapp.ai",
to: "ada@example.com",
subject: "Your invoice #1042",
html: "<p>Thanks! Receipt attached.</p>",
});
return Response.json({ id, status });
}; import { MailKite } from "mailkite";
const mk = new MailKite(Bun.env.MAILKITE_API_KEY);
Bun.serve({
async fetch() {
const { id, status } = await mk.send({
from: "hello@myapp.ai",
to: "ada@example.com",
subject: "Your invoice #1042",
html: "<p>Thanks! Receipt attached.</p>",
});
return Response.json({ id, status });
},
}); // wrangler.jsonc → "compatibility_flags": ["nodejs_compat"]
import { MailKite } from "mailkite";
export default {
async fetch(_req: Request, env: Env) {
const mk = new MailKite(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>",
});
return Response.json({ id, status });
},
}; import { MailKite } from "mailkite";
const mk = new MailKite(process.env.MAILKITE_API_KEY);
// Deploys as a Vercel Function — same code on Netlify or any Node runtime.
export async function POST() {
const { id, status } = await mk.send({
from: "hello@myapp.ai",
to: "ada@example.com",
subject: "Your invoice #1042",
html: "<p>Thanks! Receipt attached.</p>",
});
return Response.json({ id, status });
} import { MailKite } from "mailkite";
const mk = new MailKite(process.env.MAILKITE_API_KEY);
export const handler = async () => {
const { id, status } = await mk.send({
from: "hello@myapp.ai",
to: "ada@example.com",
subject: "Your invoice #1042",
html: "<p>Thanks! Receipt attached.</p>",
});
return { statusCode: 200, body: JSON.stringify({ id, status }) };
}; import { MailKite } from "npm:mailkite";
const mk = new MailKite(Deno.env.get("MAILKITE_API_KEY"));
Deno.serve(async () => {
const { id, status } = await mk.send({
from: "hello@myapp.ai",
to: "ada@example.com",
subject: "Your invoice #1042",
html: "<p>Thanks! Receipt attached.</p>",
});
return Response.json({ id, status });
}); import { MailKite } from "npm:mailkite";
const mk = new MailKite(Deno.env.get("MAILKITE_API_KEY"));
Deno.serve(async () => {
const { id, status } = await mk.send({
from: "hello@myapp.ai",
to: "ada@example.com",
subject: "Your invoice #1042",
html: "<p>Thanks! Receipt attached.</p>",
});
return Response.json({ id, status });
}); 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>",
}) import os
from django.http import JsonResponse
from mailkite import MailKite
mk = MailKite(os.environ["MAILKITE_API_KEY"])
def send(request):
res = mk.send({
"from": "hello@myapp.ai",
"to": "ada@example.com",
"subject": "Your invoice #1042",
"html": "<p>Thanks! Receipt attached.</p>",
})
return JsonResponse(res) import os
from flask import Flask, jsonify
from mailkite import MailKite
app = Flask(__name__)
mk = MailKite(os.environ["MAILKITE_API_KEY"])
@app.post("/send")
def send():
res = mk.send({
"from": "hello@myapp.ai",
"to": "ada@example.com",
"subject": "Your invoice #1042",
"html": "<p>Thanks! Receipt attached.</p>",
})
return jsonify(res) import os
from fastapi import FastAPI
from mailkite import MailKite
app = FastAPI()
mk = MailKite(os.environ["MAILKITE_API_KEY"])
@app.post("/send")
def send():
return 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>',
]); <?php
use MailKite\Client;
Route::post('/send', function () {
$mk = new Client(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>',
]);
return response()->json($res);
}); <?php
use MailKite\Client;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;
class SendController
{
#[Route('/send', methods: ['POST'])]
public function send(): JsonResponse
{
$mk = new Client($_ENV['MAILKITE_API_KEY']);
return new JsonResponse($mk->send([
'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>"
) class SendsController < ApplicationController
def create
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>"
)
render json: res
end
end require "sinatra"
require "mailkite"
mk = Mailkite::Client.new(ENV["MAILKITE_API_KEY"])
post "/send" do
res = mk.send(
"from" => "hello@myapp.ai",
"to" => "ada@example.com",
"subject" => "Your invoice #1042",
"html" => "<p>Thanks! Receipt attached.</p>"
)
content_type :json
res.to_json
end 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>",
}) 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>"
)); @RestController
public class SendController {
private final MailKite mk = new MailKite(System.getenv("MAILKITE_API_KEY"));
@PostMapping("/send")
public Object send() {
return mk.send(Map.of(
"from", "hello@myapp.ai",
"to", "ada@example.com",
"subject", "Your invoice #1042",
"html", "<p>Thanks! Receipt attached.</p>"
));
}
} npm i -g @mailkite/cli && mailkite login
mailkite send --from hello@myapp.ai --to ada@example.com \
--subject "Your invoice #1042" --html "<p>Thanks! Receipt attached.</p>"
mailkite messages tail # watch inbound arrive 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>"
}' wget -qO- https://api.mailkite.dev/v1/send \
--header "Authorization: Bearer $MAILKITE_API_KEY" \
--header "Content-Type: application/json" \
--post-data '{
"from": "hello@myapp.ai",
"to": "ada@example.com",
"subject": "Your invoice #1042",
"html": "<p>Thanks! Receipt attached.</p>"
}' // No SDK — one POST with the built-in fetch.
const res = await fetch("https://api.mailkite.dev/v1/send", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MAILKITE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
from: "hello@myapp.ai",
to: "ada@example.com",
subject: "Your invoice #1042",
html: "<p>Thanks! Receipt attached.</p>",
}),
});
const { id, status } = await res.json(); # No SDK — one POST with requests.
import os, requests
res = requests.post(
"https://api.mailkite.dev/v1/send",
headers={"Authorization": f"Bearer {os.environ['MAILKITE_API_KEY']}"},
json={
"from": "hello@myapp.ai",
"to": "ada@example.com",
"subject": "Your invoice #1042",
"html": "<p>Thanks! Receipt attached.</p>",
},
)
print(res.json()) # { "id": "msg_…", "status": "queued" } # Point WordPress, a CRM, or any app that speaks SMTP at the relay.
SMTP_HOST=smtp.mailkite.dev
SMTP_PORT=587 # STARTTLS · 465 for implicit TLS
SMTP_USER=mailkite # any username works
SMTP_PASS=mk_live_… # your MailKite API key — not a separate SMTP password
SMTP_FROM=you@your-verified-domain.dev import nodemailer from "nodemailer";
const tx = nodemailer.createTransport({
host: "smtp.mailkite.dev",
port: 587, // STARTTLS · use 465 for implicit TLS
auth: {
user: "mailkite", // any username works
pass: process.env.MAILKITE_API_KEY, // your mk_live_… key
},
});
await tx.sendMail({
from: "hello@myapp.ai", // an address on a verified domain
to: "ada@example.com",
subject: "Your invoice #1042",
html: "<p>Thanks! Receipt attached.</p>",
}); import os
EMAIL_HOST = "smtp.mailkite.dev"
EMAIL_PORT = 587 # STARTTLS · 465 for implicit TLS
EMAIL_USE_TLS = True
EMAIL_HOST_USER = "mailkite" # any username works
EMAIL_HOST_PASSWORD = os.environ["MAILKITE_API_KEY"] # your mk_live_… key
DEFAULT_FROM_EMAIL = "hello@myapp.ai" # an address on a verified domain
# Anywhere in your app:
# from django.core.mail import send_mail
# send_mail("Your invoice #1042", "Thanks! Receipt attached.",
# "hello@myapp.ai", ["ada@example.com"]) MAIL_MAILER=smtp
MAIL_HOST=smtp.mailkite.dev
MAIL_PORT=587 # STARTTLS · 465 for implicit TLS
MAIL_USERNAME=mailkite # any username works
MAIL_PASSWORD=mk_live_… # your MailKite API key
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=hello@myapp.ai # an address on a verified domain
# Then Mail::to('ada@example.com')->send(new InvoiceMail) just works. config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: "smtp.mailkite.dev",
port: 587, # STARTTLS · 465 for implicit TLS
user_name: "mailkite", # any username works
password: ENV["MAILKITE_API_KEY"], # your mk_live_… key
enable_starttls_auto: true,
}
# Action Mailer now sends from any address on a verified domain. # Read your MailKite inbox from any IMAP client or library — over IMAPS,
# with a scoped app-password (Settings → IMAP), never your API key.
import imaplib
M = imaplib.IMAP4_SSL("imap.mailkite.dev", 993)
M.login("agent@yourdomain.com", "mk_imap_…") # your IMAP app-password
M.select("INBOX")
typ, data = M.search(None, "UNSEEN") /plugin marketplace add mailkite/claude-code
/plugin install mailkite@mailkite claude mcp add --transport http mailkite https://mcp.mailkite.dev/mcp claude mcp add mailkite \
-e MAILKITE_API_KEY=mk_live_3a9f… \
-- npx -y @mailkite/mcp claude mcp add --transport http mailkite https://mcp.mailkite.dev/mcp \
--header "Authorization: Bearer mk_live_3a9f…" npx skills add mailkite/agent-skills Install the MailKite agent skill. Download
https://github.com/mailkite/claude-code/releases/latest/download/mailkite.zip
and unzip it into ~/.claude/skills/ (user-wide) or .claude/skills/ (this project), then load it. {
"mcpServers": {
"mailkite": {
"type": "http",
"url": "https://mcp.mailkite.dev/mcp"
}
}
} {
"mcpServers": {
"mailkite": {
"command": "npx",
"args": ["-y", "@mailkite/mcp"],
"env": { "MAILKITE_API_KEY": "mk_live_3a9f…" }
}
}
} {
"mcpServers": {
"mailkite": {
"type": "http",
"url": "https://mcp.mailkite.dev/mcp"
}
}
} {
"mcpServers": {
"mailkite": {
"command": "npx",
"args": ["-y", "@mailkite/mcp"],
"env": { "MAILKITE_API_KEY": "mk_live_3a9f…" }
}
}
} {
"servers": {
"mailkite": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@mailkite/mcp"],
"env": { "MAILKITE_API_KEY": "mk_live_3a9f…" }
}
}
} {
"mcpServers": {
"mailkite": {
"serverUrl": "https://mcp.mailkite.dev/mcp"
}
}
} {
"mcpServers": {
"mailkite": {
"command": "npx",
"args": ["-y", "@mailkite/mcp"],
"env": { "MAILKITE_API_KEY": "mk_live_3a9f…" }
}
}
} {
"mcpServers": {
"mailkite": {
"httpUrl": "https://mcp.mailkite.dev/mcp"
}
}
} {
"mcpServers": {
"mailkite": {
"command": "npx",
"args": ["-y", "@mailkite/mcp"],
"env": { "MAILKITE_API_KEY": "mk_live_3a9f…" }
}
}
} [mcp_servers.mailkite]
command = "npx"
args = ["-y", "@mailkite/mcp"]
env = { MAILKITE_API_KEY = "mk_live_3a9f…" } Connect to the MailKite MCP server (Streamable HTTP) at
https://mcp.mailkite.dev/mcp and authenticate in the browser when prompted.
Then use the mailkite_* tools to send, read inbound, and reply to email. {
"mcpServers": {
"mailkite": {
"type": "http",
"url": "https://mcp.mailkite.dev/mcp"
}
}
} {
"mcpServers": {
"mailkite": {
"command": "npx",
"args": ["-y", "@mailkite/mcp"],
"env": { "MAILKITE_API_KEY": "mk_live_3a9f…" }
}
}
} No infrastructure, no inbound queue to manage. Connect a domain and point us at a URL.
Add the DNS records we generate for you — MX, SPF, and DKIM. We verify them automatically and flip your address live.
Tell MailKite where to POST inbound mail, and grab an API key to send. Any HTTPS endpoint and any stack works.
Inbound arrives as clean JSON at your webhook; outbound goes out with one API call. Same address, both directions.
The plumbing of a mail provider, exposed as a clean developer API.
Every message that hits your address is parsed and POSTed to your endpoint as clean JSON — body, headers, and attachments included. No IMAP, no polling, no mail server to babysit. If your endpoint is down, we retry on a backoff schedule and alert you — so you never miss an inbound email.
One endpoint, one API key. Send transactional mail, replies, and broadcasts over your own authenticated, warmed-up domain — built on Cloudflare's edge.
Spin up as many products as you want, with unlimited mailboxes on every domain. Each domain is free until it has real traction — you only pay once one actually takes off. No per-domain fees, ever.
Deliverability, security, and uptime are handled. SPF, DKIM, and DMARC are configured and monitored for you, so your mail lands in the inbox.
Flip on zero-retention passthrough and we never store a byte — your webhook keeps the only copy. Or paste a public key and we encrypt every message we retain, so only you can read it.
No server to run — but when a human needs to read along, point Apple Mail, Thunderbird, a phone, or your agent's IMAP library at your inbox over IMAPS. A scoped, revocable app-password, never your API key.
No MX hosts, no Postfix, no IP warm-up. Point DNS at MailKite and skip the mail server entirely.
Send with one POST. Every inbound message lands on your webhook as clean, parsed JSON.
Give every product its own domain. Pay only when one gets traction — no per-domain fees.
This is exactly what hits your endpoint the moment an email arrives — no MIME wrangling, no encoding surprises.
POST /your/webhook HTTP/1.1
Content-Type: application/json
{
"from": "ada@example.com",
"to": "hello@myapp.ai",
"subject": "Re: invoice #1042",
"text": "Looks good — approved!",
"html": "<p>Looks good — approved!</p>",
"attachments": [
{
"id": "msg_8f3a...:0",
"filename": "po.pdf",
"contentType": "application/pdf",
"size": 18213,
"url": "https://api.mailkite.dev/att/9f86d0c2"
}
],
"messageId": "<a1b2c3@mail.example.com>",
"receivedAt": "2026-06-19T17:42:10Z"
} POST /v1/send HTTP/1.1
Host: api.mailkite.dev
Authorization: Bearer mk_live_3a9f…
Content-Type: application/json
{
"from": "hello@myapp.ai",
"to": "ada@example.com",
"subject": "Your invoice #1042",
"html": "<p>Thanks! Receipt attached.</p>",
"attachments": [
{ "filename": "receipt.pdf", "url": "https://…" }
]
}
← 202 Accepted
{ "id": "msg_2Hk9…", "status": "queued" } The same address that receives can send. One endpoint, one key — transactional mail, replies, and broadcasts go out over your own authenticated domain, built on Cloudflare's edge.
No code to add? Point WordPress, a CRM, or any app that speaks SMTP at our relay →
Mail goes to and arrives from any provider — Gmail, Outlook, and the rest. Keep the inboxes your team already uses; MailKite is the programmable layer on top.
Every agent gets a real, catch-all inbox — it receives mail sent to any address on the domain and sends from any of them, over MCP, an AI skill, or plain REST. Inbound arrives as structured JSON your agent can reason over.
Spin up a fresh address per agent, per task, per tenant — or point a whole domain at one agent. It's a catch-all: mail to any address lands where your agent reads it, and it sends from any of them. Sign-ups, verification, and 2FA codes included — no OAuth, no human in the loop.
In Claude Code, /plugin install mailkite@mailkite and authenticate — no key to copy. Any other client points at the hosted remote server at mcp.mailkite.dev/mcp. Your agent gets tools to send, list, read, and reply — inbound included, which most email MCPs don't expose.
A published skill wires email into your coding agent in one step — send and act on real inbound without building the plumbing.
Received mail arrives as clean JSON your agent acts on and replies to in-thread. Don't want to host a loop? Switch on a built-in inbox agent that triages, replies, filters spam, tags, escalates — and can call any MailKite tool straight from your prompt. No server.
Three grants, from the whole account down to a single address — each one revocable on its own.
*
Every domain, every mailbox
One key runs all your email: the agent can create domains, set webhooks, send and read everywhere — unlimited domains and mailboxes. For your own trusted automation.
*@agents.yourapp.com
Every mailbox on one domain
The agent sends, reads, and replies on that domain and nothing else — the rest of your account is out of reach. Mint one per agent; revoke one without disturbing the others.
support@yourapp.com
One mailbox
A route hands one address's mail to your agent — that inbox is all it sees and all it replies from. The narrowest grant: the agent never holds a key at all.
Point a domain, drop in a webhook URL, and you're done. Unlimited domains, free to start.