Automatic DNS management can delete unrelated apex TXT records when syncing SPF

Issue Description

I tested/reproduced this with Cloudflare, but it may affect other providers too since automatic DNS management uses the same set_rrset flow.

When automatic DNS management syncs SPF, it appears to target the entire apex TXT RRSet instead of only SPF TXT records.

For example, existing DNS may contain:

domain.com TXT "google-site-verification=..."
domain.com TXT "v=spf1 mx -all"

SPF sync is scoped to:
domain.com TXT

rather than only TXT values beginning with:

v=spf1

As a result, unrelated apex TXT records can be treated as stale and deleted. In my case, a non-SPF TXT record was deleted, then the task tried to add the SPF record and failed because the SPF record already existed:

Failed to create DNS record for domain.com.: Failed to create DNS record: API error: BadRequest {"result":null,"success":false,"errors":[{"code":81058,"message":"An identical record already exists."}],"messages":[]}

Expected Behavior

Only update SPF record.

Actual Behavior

Deletes unrelated TXT records when syncing DNS.

Reproduction Steps

  1. Configure a domain that has existing TXT records at Zone apex in Stalwart.
  2. Enable automatic DNS for that domain and make sure SPF Records are selected for Record types.
  3. Check zone and unrelated TXT record will be deleted.

Relevant Log Output

Failed to create DNS record for domain.com.: Failed to create DNS record: API error: BadRequest {"result":null,"success":false,"errors":[{"code":81058,"message":"An identical record already exists."}],"messages":[]}

Stalwart Version

v0.16.x

Installation Method

Docker

Database Backend

RocksDB

Blob Storage

RocksDB

Search Engine

Internal

Directory Backend

Internal

Additional Context

The current method of DNS syncing works for other record types since those names are specific and not typically configured at the apex.

I have reviewed the documentation and FAQ and confirm that my issue is NOT addressed there.

on

I have searched this support forum (open and closed topics) and confirm this is not a duplicate.

on

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

This confirms the bug clearly. The set_rrset call in dns.rs (line 76) replaces the entire RRSet for (name, record_type) – so for SPF, it calls set_rrset on domain.com. TXT, which replaces all TXT records at the apex (including unrelated ones like google-site-verification) with only the SPF record. The add_to_rrset and remove_from_rrset methods exist in the codebase but are not used for the SPF sync path.


This is a confirmed bug in the automatic DNS management logic. Here’s what’s happening at the code level:

In crates/services/src/task_manager/dns.rs, the sync task groups all records by (name, record_type) and calls set_rrset(...) for each group. For SPF, that means it calls set_rrset on domain.com. / TXT with only the SPF value – which replaces the entire apex TXT RRSet, wiping out any unrelated records (like google-site-verification=..., MS-verify=..., etc.) that Stalwart didn’t generate.

The underlying DnsUpdater struct already has add_to_rrset and remove_from_rrset methods available, which would allow surgical updates. The SPF sync path (and potentially DMARC/TLS-RPT if they share an apex) should use those instead of set_rrset for TXT records at the zone apex, scoping changes to only values beginning with v=spf1.

Workaround in the meantime: Disable SPF from the automatic DNS management record types and manage the apex TXT records manually. Other record types (DKIM, MX, DMARC, SRV, etc.) are published under unique subnames and are not affected by this issue.

This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong.

This has been fixed. The fix will be included in v0.16.7.