V0.16 Migration: How to deal with internal domains (non-public TLD)

Your question

Quote from the Migration Guide:

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.

on

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 .lan only 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.

It’s a shame that I cannot use home.lan anymore, because of arbitrary checks.

Hence, right now, I won’t upgrade to 0.16.

Why this limitation?

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.

Although true, I just don’t see any point of enforcing this.

I’m pretty sure I am not the only one who uses a non-official non-global TLD internally.

Now, what? Should I rename my whole AD just because of Stalwart? Stay on 0.15 forever?

I really hope they will change it.

I’d like to use .lan as well - just discovered stalwart today and was looking to set it up so that all my devices can email to an internal server.

I dont want to have to rename everyting to .internal or whatever.

Any chance .lan can be added to that internal list?

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/local internal).

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.

The following internal TLDs will be supported in 0.16.8:

 "test", "localhost", "local", 
 "internal", "lan", "home", "corp", 
 "intranet", "private", "localdomain",

Question: Why restrict TLDs in the first place?

Let us add any TLD!

The only reason I can think of is ACME/Let’s Encrypt, but there you could implement a TLD check to allow it, otherwise use manualy certifiactes.