Docs Get key
All server guides
Open Source Mail Servers

Route recipient domains to multiple backends on one MX

Run one MailKite Server MX for several backends: per-domain ownership, priority conflicts, signed ingest fan-out, and the checks before production.

Reviewed

Multiple backends on one MX is useful when separate products, environments, or vendors must not share a mailbox store but can share port 25, the PTR record, and the IP reputation. The edge maps each recipient domain to exactly one backend; it does not merge their data or let one backend read another’s domains.

Prerequisites

  1. A pinned MailKite Server release whose mta/ includes lib/backends.js and the multi-backend mailkite_rcpt / mailkite_ingest plugins. Older releases accept one backend only.
  2. A backend that implements the contract: GET /api/mx/accepted-domains and POST /api/ingest, each authenticated with its own secret.
  3. One MX hostname whose TLS certificate covers every domain’s MX target. The certificate belongs to the edge, not the backends.
  4. A dedicated HMAC secret per backend, stored in the edge’s environment or systemd credentials. Do not put secrets in backends.json.

Configure the routing table

Copy mta/config/backends.example.json to config/backends.json and list the backends in priority order:

{
  "backends": [
    { "name": "cloud", "url": "https://api.mailkite.dev", "secretEnv": "MAILKITE_HMAC_SECRET" },
    { "name": "local", "url": "https://mail.internal.example", "secretEnv": "LOCAL_HMAC_SECRET" }
  ]
}

The edge appends /api/mx/accepted-domains and /api/ingest to each base URL. If two backends claim the same domain, the earlier entry wins and the edge logs a conflict once per domain. Appending a new backend therefore cannot shadow an existing one.

Without config/backends.json, the edge keeps the single-backend behavior from MAILKITE_API_URL / MAILKITE_HMAC_SECRET. Removing the file is the rollback.

Verify acceptance and delivery

After restarting the MX unit:

curl -fsS -H "Authorization: Bearer $LOCAL_HMAC_SECRET" \
  https://mail.internal.example/api/mx/accepted-domains

swaks --to test@new-domain.example --server mx.example.com   # claimed → 250
swaks --to test@unclaimed.example --server mx.example.com    # unclaimed → 550

Then send one real message to a domain only the new backend claims, and one to a domain the original backend owns. Confirm both are stored in the correct backend before relying on the split. An address accepted at RCPT and then tempfailed at ingest is retried by the sender, so backends must deduplicate on recipient and content hash.

Operate within the boundary

Multi-backend routing applies to the inbound MX only. Submission and IMAP remain single-backend in this release, and the routing table is operator-controlled: a backend can claim domains, but it cannot add itself or read another backend’s claim list. Keep the existing backends’ order and secrets unchanged when adding one, and rotate each secret independently.

For the full semantics, failure modes, and the security model, read the multi-backend spec.