DKIM rotation retires the active key even when its successor fails to publish, leaving outgoing mail unsigned

Issue Description

When a DKIM rotation is due and writing the new key’s DNS record fails, or the propagation check does not confirm it, the new key is stored as Pending, but the current Active key of that algorithm is still moved to Retiring in the same task run. Only Active keys are used for signing, so that algorithm stops signing. If publication fails for all algorithms being rotated and no other Active keys remain, subsequent outgoing messages carry no DKIM-Signature header.

Code (v0.16.24):

  • crates/services/src/task_manager/dkim.rs L214-257: when set_rrset fails or wait_for_txt_propagation returns false, the new key is set to Pending and rescheduled (“Something went wrong, reschedule”).
  • crates/services/src/task_manager/dkim.rs L372-385: every due Active key collected in retiring_signatures is set to Retiring unconditionally, without checking that a successor for the same algorithm became Active.
  • crates/common/src/cache/principals.rs L917-924: signers are built only from keys whose stage is Active.

Repeated publication failures can lead to six-hour retry intervals, prolonging the period without an Active signing key.

Expected Behavior

As documented in DKIM key rotation | Stalwart : while the new key is Pending, “Signing continues with the current active key”, and only when DNS propagation is confirmed does the new key become active and the previous key become retiring. A failed publication should leave the current key Active, and the failure should be logged at WARN or ERROR.

Actual Behavior

After a rotation in which publication or the propagation check fails for every algorithm: the new keys are Pending, the previous keys are Retiring, no key is Active, and messages submitted afterwards carry no DKIM-Signature header. No WARN or ERROR reports that the domain has been left without an Active signing key.

Reproduction Steps

  1. Domain with DKIM management Automatic (Ed25519 + RSA) and DNS management Automatic with a DNS provider (Cloudflare), with DKIM included in the records to publish (publishRecords).
  2. Starting state: the existing keys are Active and a control message submitted through the submission listener carries their DKIM-Signature headers.
  3. Make the DNS write fail while the DNS provider configuration stays valid, so the updater is built and the failure happens at set_rrset (e.g. no network access to the provider’s API). A configuration so broken that the updater cannot be built ends the task earlier with “Failed to build DNS updater” and does not reach this code path.
  4. Trigger the documented on-demand rotation: set nextTransitionAt of the Active keys to now and create a DkimManagement task for the domain (x:Task/set).
  5. After the task runs, x:DkimSignature/get shows the new keys Pending, the previous keys Retiring and no Active key.
  6. Submit another message: it is delivered without any DKIM-Signature header.

Stalwart Version

v0.16.x

Installation Method

Binary (Linux)

Database Backend

RocksDB

Blob Storage

RocksDB

Search Engine

Internal

Directory Backend

Internal

Additional Context

Suggested fix: retire an Active key only after a successor for the same algorithm has become Active; log at WARN or ERROR when publication fails or when a domain is left with no Active signing key.

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 documented behavior: a Pending key should coexist with the current Active key continuing to sign, and only a confirmed publication should trigger the active→retiring transition. The report matches a genuine discrepancy between that documented lifecycle and the described code path.

This is a valid bug report with a clear, well-documented reproduction and an accurate reading of the rotation lifecycle described in the docs. I don’t have anything to add beyond confirming the documented intended behavior you quoted is correct; the fix you suggest (only retiring an Active key once its successor for the same algorithm reaches Active, and logging WARN/ERROR on publication failure or when no Active key remains) aligns with how the lifecycle is meant to work. This has been logged for a human to review.

Reference: DKIM key rotation | Stalwart

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

Fixed in v0.16.25 (will be released today).