Delivery failure for domains with DNSSEC enabled but no TLSA record

Issue Description

I was trying to send mails to domains protected by MS Exchange. For some, it works, but for others, it does not.

After investigating the issue, I found a potential culprit in the Server behaviour: if the recipient’s mailserver has a valid DNSSEC configuration, Stalwart expects it to have a TLSA record.

Expected Behavior

If no TLSA record is found and the DANE strategy is set to ‘Optional’, the mail should be sent.

Actual Behavior

INFO Error fetching TLSA record (dane.tlsa-record-fetch-error)
        queueName = "remote"
        from = "[email protected]"
        to = ["[email protected]"]
        domain = "targetserver.tld"
        hostname = "targetserver-tld.mail.protection.outlook.com"
        causedBy = DNS error (mail-auth.dns-error)
                details = "DNS error: error response: Server Failure"
        strict = false
        elapsed = 177ms

Reproduction Steps

  1. Use the default TLS strategies on the sender’s mailserver
  2. Configure DNS entries on the recipient’s server so that DNSSEC passes (https://powerdmarc.com/dnssec-checker/), but TLSA does not (https://powerdmarc.com/dane-record-checker/)

Relevant Log Output

When the recipient’s server does not have a valid DNSSEC configuration, everything works:

  INFO New delivery attempt for domain (delivery.domain-delivery-start)
  INFO TLS-RPT record not found (tls-rpt.record-not-found)
  INFO MTA-STS policy not found (mta-sts.policy-not-found)
+ INFO TLSA record not DNSSEC signed (dane.tlsa-record-not-dnssec-signed)
  INFO Connecting to remote server (delivery.connect)
  INFO SMTP STARTTLS command (delivery.start-tls)
  INFO Message delivered (delivery.delivered)
  INFO DSN success notification (delivery.dsn-success)
  INFO Delivery completed (delivery.completed)
  INFO Delivery attempt ended (delivery.attempt-end)

If the server has DNSSEC but not TLSA:

  INFO New delivery attempt for domain (delivery.domain-delivery-start)
  INFO TLS-RPT record not found (tls-rpt.record-not-found)
  INFO MTA-STS policy not found (mta-sts.policy-not-found)
- INFO Error fetching TLSA record (dane.tlsa-record-fetch-error)
  INFO Message rescheduled for delivery (queue.rescheduled)
  INFO Delivery attempt ended (delivery.attempt-end)

Stalwart Version

v0.16.x

Installation Method

Docker

Database Backend

FoundationDB

Blob Storage

S3-compatible

Search Engine

Elasticsearch

Directory Backend

OIDC

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

Looking at the outbound delivery code, this matches a genuine gap in the current DANE handling. When Stalwart looks up a TLSA record for a DNSSEC-secure MX host, the resolver’s error is inspected:

  • If the DNS error is a plain “record not found” (NXDOMAIN-style), Stalwart correctly treats it as “no TLSA record” and, when the strategy is optional, falls through to regular STARTTLS.
  • If the DNS error is anything else (including a SERVFAIL, which is what several Microsoft 365/Exchange domains apparently return when there’s no TLSA record but the zone is DNSSEC-signed), Stalwart logs dane.tlsa-record-fetch-error and unconditionally aborts the delivery attempt to that host, regardless of whether the DANE strategy is require or optional. Unlike the other TLSA outcome branches, this error path does not check the strict flag at all before failing.

So the “Optional” DANE strategy is only honored for a missing-record (NXDOMAIN) response, not for a SERVFAIL response while resolving _25._tcp.<host> records, which is why delivery to these particular Exchange-protected domains gets rescheduled/fails even though DANE is set to Optional.

Relevant docs on the intended behavior of the optional/require DANE setting: TLS | Stalwart (DANE section) and DANE | Stalwart

This looks like a real inconsistency in how SERVFAIL-type TLSA lookup failures are handled versus NXDOMAIN ones, worth a maintainer’s review; a human will follow up on this thread.

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

The trigger is not the absent TLSA record. A real “no such record” answer is handled and delivery goes ahead under optional; what you are hitting is a TLSA lookup that comes back SERVFAIL, which is a different thing. Once the recipient’s MX RRset has validated under DNSSEC, an inconclusive TLSA answer cannot be told apart from a stripped record, so RFC 7672 section 2.2 requires us to defer rather than downgrade. That is why optional does not help: it governs the missing-record case, not the failed-lookup case.

It’s a good idea to establish where the SERVFAIL comes from, because it may well be yours rather than theirs. Try dig +dnssec _25._tcp.<mx-hostname>. TLSA against your configured resolver and against an independent one. We run a validating resolver on top of whatever forwarder you have configured, and forward-mode validation throws SERVFAIL on awkward delegations often enough to be worth ruling out first.

If the destination cannot be fixed, set dane to disable on a TLS strategy scoped to that domain.

Using the connectivity test from Microsoft itself, it seems that they standardized this non-compliant behavior:

From what I see, the mail is still being delivered, while a SERVFAIL is returned for TLSA records.

You can also try with their own test domain: contoso.com:

~$ dig contoso.com MX

; <<>> DiG 9.18.49 <<>> contoso.com MX
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 31188
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 65494
;; QUESTION SECTION:
;contoso.com.                   IN      MX

;; ANSWER SECTION:
+  contoso.com.            3408    IN      MX      10 contoso-com.mail.protection.outlook.com.

;; Query time: 0 msec
;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP)
;; WHEN: Mon Aug 24 10:53:31 CEST 2026
;; MSG SIZE  rcvd: 92

~$ dig _25._tcp.contoso-com.mail.protection.outlook.com. TLSA

; <<>> DiG 9.18.49 <<>> _25._tcp.contoso-com.mail.protection.outlook.com. TLSA
;; global options: +cmd
;; Got answer:
-; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 55521
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 65494
;; QUESTION SECTION:
;_25._tcp.contoso-com.mail.protection.outlook.com. IN TLSA

;; Query time: 573 msec
;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP)
;; WHEN: Mon Aug 24 10:53:41 CEST 2026
;; MSG SIZE  rcvd: 77

I have also looked up the Microsoft Learn documentation about DANE, and it seems that this error is returned when the recipient’s server is wrongly configured.

Back to the square one :-/

EDIT: I found that the error is different based on which DNS server I use:

  • With Cloudflare, I have a NXDOMAIN answer (DANE optional passes)
  • With Quad9, I have a SERVFAIL
  • With Google, it’s a better SERVFAIL:
    ;; OPT PSEUDOSECTION:
    ; EDNS: version: 0, flags: do; udp: 512
    ; EDE: 23 (Network Error): ([104.47.72.81] rcode=NOTIMP for _25._tcp.contoso-com.mail.protection.outlook.com/tlsa)
    ; EDE: 23 (Network Error): ([104.47.34.49] rcode=NOTIMP for _25._tcp.contoso-com.mail.protection.outlook.com/tlsa)
    ; EDE: 22 (No Reachable Authority): (At delegation mail.protection.outlook.com for _25._tcp.contoso-com.mail.protection.outlook.com/tlsa)
    ;; QUESTION SECTION:
    ;_25._tcp.contoso-com.mail.protection.outlook.com. IN TLSA