MailKite
Start free
All posts
Gabe 9 min read

Programmable email: send and receive mail from code (run it in your browser)

Programmable email means driving mail entirely from code — send with one API call, receive every message as JSON on a webhook, and give any app or agent its own inbox. Here's a live demo you can run right now: verify your email and send yourself a real, parsed message in about ten seconds, no signup form.

Programmable email is email you drive entirely from code: send with one API call, receive every inbound 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.

You don’t have to take my word for it. The widget below makes a real API call from your browser: enter your email, type the 6-digit code we send you, and click Run. A real message lands in your inbox a few seconds later — sent by a fetch() call, no signup form, no key to paste. (It uses a 15-minute, send-to-yourself-only token; more on how that works at the end.)

Live · POST api.mailkite.dev

Send yourself a real email from your browser

No account form. The proven email becomes your account; the token expires in 15 minutes and can only send to you.

Try it now — build the whole thing free →

That send is the easy half. The interesting half is receiving, and it’s the half most “email APIs” barely do.

Why you don’t want to run a mail server

Receiving email yourself means standing up an MX host, speaking SMTP to the open internet, and parsing MIME — a format with decades of encodings, nested multiparts, and senders who ignore the spec. Then you own deliverability: SPF, DKIM, and DMARC records, reverse DNS, IP warm-up, and feedback loops, or your mail silently lands in spam. None of it is your product. All of it is a tax.

Programmable email is the deal where you skip that: point your domain’s MX at a service, and every message that arrives is parsed and POSTed to your endpoint as JSON.

Receiving: email becomes a webhook

You give MailKite an HTTPS endpoint. Every inbound message to your address arrives as a signed POST:

{
  "type": "email.received",
  "from": { "name": "Ada Lovelace", "address": "ada@example.com" },
  "to": "support@myapp.ai",
  "subject": "Re: invoice #1042",
  "text": "Thanks — got it.",
  "html": "<p>Thanks — got it.</p>",
  "message_id": "<a1b2@example.com>",
  "in_reply_to": "<9f8e@myapp.ai>",
  "attachments": [
    { "filename": "po.pdf", "content_type": "application/pdf", "size": 84213 }
  ]
}

Handling it is a normal route. Verify the signature, then do whatever your app does — no IMAP, no polling, no MIME parser:

import express from "express";
import { MailKite } from "mailkite";

const app = express();
const SECRET = process.env.MAILKITE_WEBHOOK_SECRET;
app.use("/hooks/mail", express.raw({ type: "application/json" }));

app.post("/hooks/mail", (req, res) => {
  // HMAC signature + replay window + constant-time compare, one call
  if (!MailKite.verifyWebhook(req.headers["x-mailkite-signature"], req.body, SECRET)) {
    return res.sendStatus(401);
  }
  const event = JSON.parse(req.body);
  if (event.type === "email.received") {
    console.log(`mail from ${event.from.address}: ${event.subject}`);
  }
  res.type("application/json").send(MailKite.replyOk());
});

Sending is the mirror image — one POST, over your own authenticated domain:

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>"
  }'

Same shape in Node, Python, PHP, Java, Go, and Ruby, plus a CLI and raw REST.

The third piece: an inbox per app or agent

A domain gives you unlimited addresses, each pointable at its own webhook. That’s what makes this useful for AI agents: hand one a real address, and it can receive parsed mail, hold a thread, and reply — either by running your own model loop on the webhook, or by letting MailKite run the agent for you on a durable queue. I wrote that up separately in give your agent an inbox and the agent inbox guide.

Honest positioning

None of the primitives here are new. SendGrid, Postmark, Mailgun, and Resend are all good at sending, and most have some inbound-parse feature bolted on. What’s usually missing is inbound and reply/threading as first-class citizens, an inbox identity per agent, and pricing that doesn’t punish spinning up many addresses. MailKite’s bet is those three: both directions on the same verified domain, an inbox per agent, and unlimited domains and mailboxes free until one gets real traction. If you only ever send one-way notifications, a send-only API is genuinely simpler — use one. The difference between an email API and programmable email is the whole pitch.

How the demo let you run a real call without a key

The widget never had an API key. Pasting a full mk_live_… key into a web page would be reckless, so the “Run against the real API” arc works differently:

  1. POST /api/playground/request {email} emails you a 6-digit code (rate-limited, and it reveals nothing about whether an account exists).
  2. POST /api/playground/verify {email, code} proves the address — which is the account — and returns a 15-minute HS256 JWT with scope: "playground".
  3. That token can hit POST /v1/send for exactly one thing: a single immediate email from the platform’s own playground@mailkite.dev address to your verified address. No other recipient, no cc/bcc, 5 sends/hour. The full-account gate is skipped only because the blast radius is you.

So the token is safe to expose, and the email you just got is the same /v1/send path a production key uses. That’s the whole idea of programmable email: it’s just an HTTP call, all the way down.

Build the full thing free → · Read the docs · unlimited domains and mailboxes, 3,000 emails/mo, no daily cap.

Discuss this post: Hacker News Share on X

Related posts