DEV Community

Bucabay
Bucabay

Posted on Originally published at mailkite.dev on

Announcing the MailKite Server developer API: use the client against your own server

MailKite Server now has a developer API that can replace the hosted API for its core send-and-receive path. The practical result is small: point the MailKite client at your server with baseUrl, use an mk_local_… key, and leave application code alone. You can run the backend privately, use MailKite Cloud for outbound delivery, or switch between the two.

The deployment target changes. The client calls do not.

Deployment target Endpoint Credential
MailKite Cloud api.mailkite.dev hosted API key
MailKite Server your baseUrl mk_local_… key
import { MailKite } from "mailkite";

const mail = new MailKite({
  apiKey: process.env.MAILKITE_API_KEY, // mk_local_…
  baseUrl: "https://mail.example.com",
});

const { id, status } = await mail.send({
  from: "hello@example.com",
  to: "ada@example.com",
  subject: "Hello from your server",
  text: "It works.",
});

console.log(id, status); // msg_… sent
Enter fullscreen mode Exit fullscreen mode

The MailKite Server repository contains the implementation and tests. The official open-source page has the installation path, supported runtimes, Docker instructions, deployment notes, and the self-hosting trade-offs. The short version is Node.js 22.5 or newer, a writable SQLite data directory, and a configured outbound smarthost if mail must leave the domains your server hosts.

What we shipped

The new surface lives in api-local, the repository's zero-dependency Node and SQLite reference backend. It is authenticated with bearer API keys minted by the local server. The key format is mk_local_…, and the from domain must belong to the account that owns the key.

The first compatibility slice covers the paths an application needs to send and inspect mail:

  • POST /v1/send accepts text, HTML, to, cc, bcc, replyTo, inReplyTo, and custom MIME headers. It returns { id, status } with HTTP 202.
  • POST /v1/send/batch sends up to 50 personalized recipients and returns per-recipient results.
  • GET /v1/me provides the account probe expected by clients, with an honest self-hosted plan value.
  • GET /api/messages lists inbound and outbound messages across the account.
  • GET /api/messages/:id returns the message-detail envelope, including decoded text and HTML bodies.
  • The existing mailbox API continues to serve address-scoped lists, flags, and raw RFC822 messages through app-password authentication.

That shape matters more than the route count. A client library should not need to know whether a message came from a hosted database or a SQLite file on your VPS. It should send a request, receive a predictable envelope, and handle a failure with the same code path.

The Node client now also handles raw non-JSON responses correctly. That sounds minor until an inbox reader asks for the original RFC822 message: the response is a MIME document, not JSON. The client returns it as text instead of trying to parse it and throwing a syntax error.

The compatibility contract is explicit

"Compatible" does not mean that every Cloud feature has been quietly approximated. The local server refuses hosted-only send fields that it cannot perform. templateId, templateData, attachments, and scheduledAt return 400 unsupported; tracking fields remain accepted no-ops. That is safer than reporting success for an attachment that was never stored or a scheduled message that will never run.

There is another difference in what status: "sent" means. In Cloud, it describes a message handed to the managed deliverability path. On your server, it means the message was stored and handed to local delivery or your configured smarthost. If no smarthost is configured, external recipients are rejected before a Sent copy is created.

The local pipeline is visible and configurable. The API envelope stays stable.

The self-hosted send flow: POST /v1/send → API key + input validation → MIME rendering → store the Sent copy → local delivery (your inbox loop) or your configured SMARTHOST (external SMTP).

This is also why the repository includes regression tests rather than only a route list. The tests cover the response status, message-detail envelope, account-wide ordering and search, batch results, custom headers, CRLF rejection, hosted-only field rejection, and the local delivery loop. SDK tests cover custom baseUrl requests and raw message bodies.

How to install and run it

The official MailKite Server page is the source of truth for installation and requirements. Follow that page for the current Docker command, bare-VPS setup, DNS, TLS, systemd, Fly.io, Railway, and port-25 options rather than copying a deployment recipe into this announcement.

The rough setup has three parts:

  1. Clone the server repository and start the backend and console with Docker, or run api-local directly on Node.js 22.5+.
  2. Add a domain in the console, create an mk_local_… API key, and configure the inbound edge or webhook path you need.
  3. Set SMARTHOST to cloud or to an SMTP relay when the server must deliver beyond its local domains. Then pass the server URL as the SDK baseUrl.

The browser client can use the same API. CORS is enabled for bearer-token requests by default, and CORS_ORIGIN narrows it for an internet-facing deployment. Keep API keys out of browser bundles; CORS makes a browser request possible, not a secret safe to expose.

What parity means today, and what comes next

The current goal is application portability, not a claim that the local server has become the Cloud control plane. Sending, batch sending, account probing, message listing, message details, mailbox reads, raw messages, and the client transport behavior are the useful core. They let an app develop against a private server, deploy without a hosted dependency, or move between local and Cloud environments by changing credentials and baseUrl.

Full API parity is the next project. The remaining scope includes domain and route management through the Cloud-shaped paths, templates, attachment storage and send integration, delivery retry, tracking events, realtime tokens, broadcasts, lists, suppressions, usage, and other account-management APIs. Each endpoint needs a local behavior, an authentication decision, a storage model, and tests. We will add those deliberately instead of returning plausible-looking no-ops.

That boundary is useful for planning. If your application only needs send and receive, the server is now a practical replacement for the production API. If it needs templates, scheduled sends, or managed tracking, keep those calls on Cloud or wait for the corresponding local implementation. A mixed deployment is valid: local storage and IMAP, Cloud smarthost delivery, or Cloud for the features that are not local yet.

MailKite (which we build) is still the hosted option when you want the deliverability path, retention, and account operations handled for you. The open-source server is the option when you want to own the storage and runtime. Both speak the same core JSON shape now, and the server developer API documentation records exactly where the boundary is.

Frequently asked questions

Does this replace the MailKite client?

No. It gives the client another compatible destination. Use the same MailKite library and change baseUrl plus the credential. The current local surface covers send, batch send, account probing, message list/detail, mailbox reads, and raw MIME responses; the rest remains Cloud-only until implemented.

Can the server send to Gmail or other external domains?

Yes, through a configured smarthost. Without SMARTHOST, the reference backend only delivers to domains it hosts locally and rejects external recipients before storage. Set SMARTHOST=cloud for MailKite Cloud delivery, or use an SMTP relay you operate or trust.

Are attachments supported?

Not in the developer send API yet. The local server rejects attachments with 400 unsupported instead of accepting a request that loses files. Attachment storage and MIME integration are part of the planned full API scope, not a hidden partial implementation.

Where are the installation requirements documented?

The MailKite Server open-source page links to the official installation guide and deployment documentation. It covers Node.js 22.5+, Docker, the SQLite backend, DNS, TLS, edge ports, and hosted-MX or smarthost hybrid options. The GitHub repository is the implementation source of truth.

The compatibility work is in the repository now. Start with the official setup page, read the developer API contract, and point your existing client at the server when the supported surface is enough for your application.


Originally published at mailkite.dev.

Top comments (0)