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
- A pinned MailKite Server release whose
mta/includeslib/backends.jsand the multi-backendmailkite_rcpt/mailkite_ingestplugins. Older releases accept one backend only. - A backend that implements the contract:
GET /api/mx/accepted-domainsandPOST /api/ingest, each authenticated with its own secret. - One MX hostname whose TLS certificate covers every domain’s MX target. The certificate belongs to the edge, not the backends.
- 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.