MailKite Server is an open-source (AGPL-3.0) email server for apps and AI agents: Haraka SMTP MX + submission edges, an IMAP server, a zero-dependency SQLite backend, and a web console. Run the whole stack on a $5 VPS — or point the same components at MailKite Cloud and never think about port 25.
Not a code drop — the SMTP and IMAP edges are the same code serving MailKite production today.
git clone https://github.com/mailkite/server
cd server
docker compose up -d
# backend + web console → http://localhost:8787
# --profile edges adds the MX edge on :25 Take all five, or just the piece you're missing — every part is a stateless protocol head talking to the same small HTTP contract, so they run alone or together.
Haraka on :25. Accepts mail with live anti-open-relay recipient checks, then POSTs the raw message to your backend as a signed webhook.
SMTP AUTH on :587/:465 with DKIM signing — for every app and library that only speaks SMTP.
IMAP4 on :993 (implicit TLS). Read received mail from Thunderbird, Apple Mail, or your agent's IMAP library.
One Node process, node:sqlite, zero npm dependencies. Signed inbound webhooks with retries, plus smarthost outbound through any SMTP relay.
Domains, DNS records, message log, webhooks, and credentials — magic-link sign-in, no password table to run.
The adapter boundary that ties it all together — with a conformance suite as its executable form.
Running a mail server has a reputation. Most of that reputation belongs to deliverability — which is exactly the part you can rent. The server itself is four steps.
git clone the repo and docker compose up -d. The backend and web console come up on localhost:8787, and the first visit bootstraps your admin account — you sign in with an emailed magic link, no password table to manage.
Do it in the console — it generates your DKIM key and shows you the exact DNS records to set: MX pointing at your box, SPF, and the DKIM public key. Paste them into your DNS provider and you're verified.
docker compose --profile edges up -d adds the MX edge on :25. On a bare VPS, the self-hosting guide walks through submission (:587), IMAP (:993), TLS certs, and the systemd units — one VPS, three daemons, your backend URL in one env var. Can't get port 25 unblocked? Point our hosted MX at your box and skip it entirely.
Email yourself from anywhere. Watch it clear the recipient check, land in the message log, and hit your webhook — signed, with retries if your endpoint is down.
That's genuinely it. The full walkthrough — DNS records, TLS provisioning, systemd units, Fly.io and Railway deploys — lives in the docs:
Already running Haraka? You don't need to adopt the repo — npm install haraka-plugin-mailkite teaches your existing MX or submission server the same backend contract.
Every component is a protocol head with no storage of its own. State sits behind a handful of HMAC-authenticated HTTP endpoints — implement them over any store and every edge here works unchanged.
That's the whole trick: the bundled SQLite backend and MailKite Cloud are just two implementations of the same contract. One MX edge can even serve several backends at once, routing each recipient domain to its owner.
Read the contract — the conformance suite is its executable form.
POST /api/imap/auth # verify a mailbox credential
GET /api/imap/status # message counts, uidvalidity
GET /api/imap/list # list messages in a mailbox
POST /api/imap/flags # read / unread / deleted
GET /api/imap/raw # fetch a raw RFC822 message
POST <ingest hook> # signed inbound delivery
POST <inject endpoint> # authenticated submission relay The edges and the console run unchanged against either backend. Switching is a credential swap, not a migration.
Start self-hosted and move to Cloud when a product takes off — or the other way around. The contract doesn't care.
AGPL-3.0 — use it, change it, redistribute it. If you offer a modified copy as a service, you publish those changes too, so improvements flow back. The same license Grafana and Mastodon use.
The license covers the code, not the MailKite name and logo — so if you take a fork in your own direction, it ships under your own brand. The same approach Mozilla and Plausible take.
The public repo is the upstream — MailKite Cloud pins it as a dependency, so your merged PR ships to real traffic, not a read-only mirror. A bot collects a one-time CLA on your first PR.
MailKite Server is an open-source, programmable mail server for apps and AI agents: a Haraka-based SMTP MX edge, a submission edge (587/465) with DKIM signing, an IMAP4 server, a zero-dependency SQLite reference backend, and a web console. It is licensed AGPL-3.0 and runs on any Node.js ≥ 22.5 host.
The mail server is — the SMTP MX, submission, and IMAP edges, the reference backend, and the web console are AGPL-3.0 on GitHub, and they are the same code serving MailKite production. The hosted control plane (billing, deliverability tooling, retention tiers) is the commercial product and stays closed.
Yes. docker compose up -d starts the backend and web console; the repo ships guides for a bare VPS (systemd units, DNS records, TLS), Fly.io, and Railway. The short version is one VPS, three daemons, and your backend URL in one environment variable. Serverless runtimes are out of scope — they can't hold SQLite state or raw TCP mail ports.
AGPL-3.0-only for the code. The MailKite name and logo are trademarks and are not covered by the code license, so forks must use their own branding. Contributions require a signed CLA.
No. The MX edge routes per recipient domain across multiple backends, and MailKite's hosted MX runs the same code — so you can point our hosted MX at your self-hosted backend and receive mail without exposing port 25 at all. Outbound works the same way: set SMARTHOST=cloud to send through MailKite Cloud's deliverability while keeping storage on your box.
They implement the same backend contract. MailKite Server is the self-hosted stack you run and operate yourself. MailKite Cloud is the hosted backend and dashboard, with deliverability, IP reputation, retention, and human support handled for you. The edges and the console run unchanged against either — switching is a credential swap, not a migration.
The repo is one docker compose up from a running mail stack. Cloud is one API key from the same thing, hosted.