Domain: create failed for create-N: invalidPatch | Invalid domain name: the domain in export.json does not pass the v0.16 hostname check (typically a missing or non-public TLD). Either correct the domain in v0.15 before redumping, or hand-edit the offending block in export.json.
I use my own internal domain - home.lan - in Stalwart, primarly for status mails from my internal services.
The guide suggest to either correct the domain or hand-edit, but how?
Can I mark the domain internal, so apply will work?
I understand that topics in this category are triaged by a bot first but a human reply will follow up. If I’d prefer a human-only reply, I’ll add the no-ai tag to my topic.
Now I have the exact answer. The is_valid_domain function shows exactly which TLDs are accepted.
The is_valid_domain check in the server source accepts .lanonly if it is in the hardcoded reserved-TLD list: ["test", "localhost", "local", "internal"]. .lan is not in that list, so home.lan fails the check.
Your options are:
Option 1 (recommended): Hand-edit export.json before running apply
Find the Domain create block for home.lan and change the name field to a TLD that will pass validation – the reserved TLDs the server explicitly allows are:
*.test
*.localhost
*.local
*.internal
So rename home.lan to e.g. home.internal or home.local in export.json, and update any Account/Alias records in the same file that reference that domain. Then run apply as normal.
Option 2: Rename the domain in v0.15 first, then re-dump
Change the domain from home.lan to home.internal (or another allowed reserved TLD) in your running v0.15 instance, update all accounts to use the new domain, and then re-run the export. This keeps the exported file clean without manual JSON editing.
Option 3: Use a real public TLD
If home.lan was only ever used internally and you own a real domain (e.g. home.example.com), you could migrate to that instead.
There is no “mark as internal/exempt” flag you can set in the domain object to bypass the validation – the check is enforced at the type level for every domain name string.
This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong.
After beating it to death for a decade, ICANN decided in 2024 that .internal was to be the standard for private use domains, and banned it from ever being a global TLD.
Seems like Stalwart is making the best attempt to follow all modern standards.
I’m surprised .local is allowed as it collides with Bonjour.
We have exactly this requirement and would really value first-class support for internal domains.
We operate within the German healthcare telematics infrastructure (gematik), where the domain naming for certain services is mandated by the platform specification rather than chosen by us. These are internal, non-public domains by design. They are not registered on the public internet and never will be, but because the structure is fixed externally, switching them to a PSL-valid form isn’t something we can do.
On 0.15 these domains worked end-to-end. On 0.16 the create/update validation (is_valid_domain in crates/utils/src/lib.rs) rejects them with invalidPatch / Invalid domain name, because the final label isn’t in the PSL and isn’t one of the reserved special-use TLDs (test/localhost/localinternal).
For deployments like ours, what we’d ideally like is a supported, durable way to use internal/non-public domains, for example an opt-in flag (per-domain or global) that permits non-public TLDs, or relaxing the create/update check to accept any syntactically valid hostname and keeping the strict PSL check only where it’s actually needed.
Is there a supported path for this today, or one planned? We rely on internal-domain support and would be glad to test a patch or provide more detail.
I spent a lot of time creating a declarative configuration for v0.15. v0.16 broke all of that (though with good reason from what I’ve read in the GitHub issues) and now that I am finally setting up a new instance for migration it tells me that it no longer likes my domain.
Why does this check exist at all?
It’s breaking existing setups for no benefit, is not consistent with IANA, and poses arbitrary restrictions on operators.
This arbitrary limitation should not even exist for ACME as there are internal ACME CAs (step for example).
I sincerely hope that this anti-feature will get removed soon and entirely @stalwart
You are building a great piece of software, made to enable people and organizations alike. Don’t ruin it with arbitrary limitations and prescriptions.
Edit: As it stands, my choices boil down to patching this function i.e. replace the body with return true and building Stalwart myself or dropping it as a dependency entirely. I really hope that it’s neither one of those options.
I happened to have .intra domain used for my homelab. I know some people use .homelab or .lab. Reconfiguring everything to use .internal is way too much. Just wanted to see what it can do and if I could replace my postfix setup with this thing and get extra features.
I would like to have at least some environment variable that would just let me turn off the restriction and use any domain.
For now I guess I have to clone the code, make modification in there, build it and push it to my LXC to even test the solution.