Not able to send emails to certain domains due to DNSSEC error

Issue Description

When trying to send emails to certain domains, delivery is not attempted because of a DNSSEC Negative Record Response, e.g. “DNSSEC Negative Record Response for in1-smtp.messagingengine.com. IN AAAA, Bogus”. This domain does not publish AAAA records for their MX hosts and DNSSEC signs this correctly with NSEC3. On top of that, Stalwart is configured to only send using IPv4, so an AAAA lookup seems irrelevant.

Expected Behavior

Stalwart delivers the email to the IPv4 IP of that name.

Actual Behavior

Message is queued for retry.

Reproduction Steps

  1. Send email to a domain that is DNSSEC signed and does not publish AAAA records for MX hosts, e.g. sent.com

Relevant Log Output

2026-06-23T17:06:06Z INFO IP address lookup failed (delivery.ip-lookup-failed) queueId = 314290291469231616, queueName = "remote", from = "[email protected]", to = ["[email protected]"], size = 1699, total = 1, domain = "sent.com", hostname = "in1-smtp.messagingengine.com", details = "Temporary Failure for in1-smtp.messagingengine.com: Connection failed: lookup error: DNS resolution error: DNS error: DNSSEC Negative Record Response for in1-smtp.messagingengine.com. IN AAAA, Bogus", elapsed = 358ms

Stalwart Version

v0.16.x

Installation Method

Docker

Database Backend

RocksDB

Blob Storage

RocksDB

Search Engine

Internal

Directory Backend

Internal

Additional Context

This happens with any DNS resolver, system, Cloudflare, custom, …

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 is very revealing. Looking at the dnssec_ip_lookup function (lines ~206-213), there’s a critical issue: when the DNSSEC resolver returns an error for the AAAA query that is not DnsRecordNotFound (i.e., a DNSSEC “Bogus” validation error), it immediately returns that error via return Err(err) – even if IPv4 records were already found successfully. The code only silently absorbs the AAAA error if it is specifically a DnsRecordNotFound.

Now I have a clear picture of the issue. The source code confirms the root cause: when dnssec_ip_lookup is used, it queries both A and AAAA record types (based on the IpLookupStrategy), and if the AAAA query returns a DNSSEC error that is not DnsRecordNotFound, it returns that error immediately – even when IPv4 was found or when the strategy is IPv4-only. However, looking more carefully at the code, even with Ipv4Only strategy, the issue in the log says AAAA is still being queried. Let me check if there’s a DNSSEC-related setting in the TLS strategy that forces DNSSEC lookups.

Correction, the domains in question are unsigned.

2026-06-23T16:52:01Z INFO IP address lookup failed (delivery.ip-lookup-failed) queueId = 314190649744182272, queueName = "default", from = "[email protected]", to = ["[email protected]"], size = 2735, total = 1, domain = "rua.dmarc.emailanalyst.com", hostname = "mx5-prod.edatasource.com", details = "Temporary Failure for mx5-prod.edatasource.com: Connection failed: lookup error: DNS resolution error: DNS error: DNSSEC Negative Record Response for mx5-ec2.edatasource.com. IN AAAA, Bogus", elapsed = 161ms
delv a mx5-ec2.edatasource.com @1.1.1.1
; unsigned answer
mx5-ec2.edatasource.com. 600	IN	A	3.214.30.137
delv aaaa mx5-ec2.edatasource.com @1.1.1.1
;; resolution failed: ncache nxrrset
; negative response, unsigned answer
; mx5-ec2.edatasource.com. 900	IN	\-AAAA	;-$NXRRSET

Downgrade to v0.16.9 fixed it for now, seems to be a regression in v0.16.10.

I’m seeing this also.

Same problem here. Temporarly downgraded to v.0.16.9 - working again

Same problem here.

INFO IP address lookup failed (delivery.ip-lookup-failed) queueId = hiden , queueName = "remote", from = "``[email protected]``", to = ["``[email protected]``"], size = 1015, total = 1, domain = "``gmail.com``", hostname = "``mail-eu.smtp2go.com``", details = "Temporary Failure for ``mail-eu.smtp2go.com``: Connection failed: lookup error: DNS resolution error: DNS error: DNSSEC Negative Record Response for ``mail-eu.smtp2go.com``. IN AAAA, Bogus", elapsed = 248ms

This has been fixed a few days ago but not yet released. The issue is that your resolver does not support DNSSEC. To fix it you need to enable DNSSEC in your reseolver or disable DANE in the TLS strategy. Version 0.16.11 will probe DNSSEC support and disable DANE automatically but this is still something you should check if you want DANE support.

It also happens with Cloudflare resolver from the dropdown which supports DNSSEC, no? DANE is configured as optional. It fails at MX AAAA lookup. How is this related to DANE?

I’m also still getting this error in 0.16.11

What’s the exact error you’re getting?

I also suffer from this problem (Stalwart 0.16.11).

Error message is: Temporary Failure for mailsec.protonmail.ch: Connection failed: lookup error: DNS resolution error: DNS error: DNSSEC Negative Record Response for mailsec.protonmail.ch. IN AAAA, Bogus

To fix this either disable DANE or upgrade to a resolver that supports DNSSEC.

Ok, I’ve changed my primary DNS resolver to 1.1.1.1 Cloudflare, did a restart of Stalwart & that seems to solve the issue.

There was a related DANE issue, now resolved, but the root issue here remains unresolved. The DANE-related report is a red herring.

Valid negative responses for unsigned zones are being rejected as “bogus” by Stalwart, even when the upstream DNSSEC-enabled resolver (and other DNSSEC-validating tools like delv) accept the responses fine. This can be verified against default installations of DNSSEC-enabled resolvers, including PowerDNS and Technitium DNS Server (even with a root resolver sidecar for up-to-date root records). Stalwart appears to reliably accept responses from Google DNS, but public resolvers like Google and Cloudflare are blocked by popular DNSBLs, so that’s not a good solution.

I still observe this issue when trying to use an AWS SES relay host (email-smtp.us-west-2.amazonaws.com), where the AAAA query returns a negative result and the zone is unsigned. That is irrespective of DANE.

Claude loves to call everything a “red herring” :wink:

What you are almost certainly hitting is UDP truncation, not a signed-versus-unsigned problem. Your resolver (PowerDNS, Technitium, and similar defaults) answers over UDP with a truncation threshold around 1232 bytes. DNSSEC responses routinely blow past that, the root DNSKEY set that anchors validation being the worst offender. When that response is truncated and the client has no TCP transport to retry over, validation cannot build a chain, so everything comes back “bogus”: signed zones, unsigned zones, existing records, and negative answers alike. It only looks like “unsigned negatives” because that is the case that surfaces as a hard failure. If you test a signed name and an existing record through the same resolver, you will see they are quietly bogus too. Google appears to work only because Stalwart’s built-in Google and Cloudflare profiles are configured with both UDP and TCP, so they transparently fall back to TCP on truncation.

The fix is to give the resolver a TCP transport as well as UDP. If you configured a custom resolver, add the same server address a second time with the TCP protocol (or as a TCP entry alongside the UDP one). Hickory will use UDP normally and fall back to TCP whenever a response is truncated. delv accepts these responses precisely because it uses TCP when needed.

If your resolver really cannot serve DNSSEC over TCP, then disable DANE, because some of these responses simply do not fit in a UDP packet and validation will keep failing.

Also, it looks like you are not on the latest v0.16.x. The current release only performs DNSSEC validation on MX records, not on A/AAAA lookups, which removes a large class of these failures (including the AWS SES relay case you mentioned, since relay and address lookups no longer go through the validating resolver). Upgrading alone will likely resolve what you are seeing on the SES host.

And going forward, v0.16.13 will automatically disable DANE when the configured resolver has no TCP transport, so a UDP-only setup will no longer defer mail with spurious bogus errors. It will surface a warning telling you to add TCP if you want DANE back.

Maybe Claude got it from me and other pedants from places like Slashdot.

Good call on UDP truncation. I don’t know why I expected TCP fallback to happen automatically. As soon as I added the fallback TCP resolver to the Stalwart configuration, the problem disappeared.

The added warning to add TCP is a nice touch. That will certainly fix people like me who just expect it to fall back to TCP automagically.