Alias not working in 0.16.9

Issue Description

When receiving an email to an alias defined in Windows AD the email is rejected with “Relay not allowed”

Expected Behavior

Email should be accepted and delivered to recepient address.

Actual Behavior

Email is rejected with Relay not allowed

Reproduction Steps

  1. Stalwart was working in 0.16.8. Is connected through LDAP to Windows AD. In AD there are defined email addresses and aliases.
    Below are the lines from log (Trace level). It is pretty hard to know if I put all the lines reffering to a connection between there is no ID related to a transaction.
    The same configration is working fine in 0.16.8

Relevant Log Output

2026-06-17T13:20:38Z TRACE Raw SMTP input received (smtp.raw-input) listenerId = “smtp”, localPort = 25, remoteIp = 52.101.84.142, remotePort = 39831, size = 84, contents = “MAIL FROM:[email protected] SIZE=47897\r\nRCPT TO:[email protected]\r\n”
2026-06-17T13:20:39Z INFO IPREV check passed (smtp.iprev-pass) listenerId = “smtp”, localPort = 25, remoteIp = 52.101.84.142, remotePort = 39831, domain = “DB3PR0202CU003.outbound.protection.outlook.com”, result = IPREV check passed (iprev.pass) { details = [“mail-northeuropeazon11020142.outbound.protection.outlook.com.”] }, elapsed = 136ms
2026-06-17T13:20:39Z TRACE Expression evaluation result (eval.result) listenerId = “smtp”, localPort = 25, remoteIp = 52.101.84.142, remotePort = 39831, id = “MtaStageMail with id singleton”, key = “isSenderAllowed”, result = “Integer(1)”
2026-06-17T13:20:39Z TRACE Expression evaluation result (eval.result) listenerId = “smtp”, localPort = 25, remoteIp = 52.101.84.142, remotePort = 39831, id = “MtaStageMail with id singleton”, key = “script”, result = “Integer(0)”
2026-06-17T13:20:39Z TRACE Expression evaluation result (eval.result) listenerId = “smtp”, localPort = 25, remoteIp = 52.101.84.142, remotePort = 39831, id = “MtaStageMail with id singleton”, key = “script”, result = “”
2026-06-17T13:20:39Z TRACE Expression evaluation result (eval.result) listenerId = “smtp”, localPort = 25, remoteIp = 52.101.84.142, remotePort = 39831, id = “MtaStageMail with id singleton”, key = “rewrite”, result = “Integer(0)”
2026-06-17T13:20:39Z TRACE Expression evaluation result (eval.result) listenerId = “smtp”, localPort = 25, remoteIp = 52.101.84.142, remotePort = 39831, id = “MtaStageMail with id singleton”, key = “rewrite”, result = “”
2026-06-17T13:20:39Z TRACE Expression evaluation result (eval.result) listenerId = “smtp”, localPort = 25, remoteIp = 52.101.84.142, remotePort = 39831, id = “MtaStageData with id singleton”, key = “maxMessageSize”, result = “Integer(104857600)”
2026-06-17T13:20:39Z INFO SPF From check passed (smtp.spf-from-pass) listenerId = “smtp”, localPort = 25, remoteIp = 52.101.84.142, remotePort = 39831, domain = “DB3PR0202CU003.outbound.protection.outlook.com”, from = “[email protected]”, result = SPF check passed (spf.pass), elapsed = 0ms
2026-06-17T13:20:39Z TRACE Expression evaluation result (eval.result) listenerId = “smtp”, localPort = 25, remoteIp = 52.101.84.142, remotePort = 39831, id = “SpfReportSettings with id singleton”, key = “sendFrequency”, result = “Array([Integer(1), Integer(86400000)])”
2026-06-17T13:20:39Z INFO SMTP MAIL FROM command (smtp.mail-from) listenerId = “smtp”, localPort = 25, remoteIp = 52.101.84.142, remotePort = 39831, from = “[email protected]
2026-06-17T13:20:39Z TRACE Expression evaluation result (eval.result) listenerId = “smtp”, localPort = 25, remoteIp = 52.101.84.142, remotePort = 39831, id = “MtaStageRcpt with id singleton”, key = “maxFailures”, result = “Integer(5)”
2026-06-17T13:20:39Z TRACE Expression evaluation result (eval.result) listenerId = “smtp”, localPort = 25, remoteIp = 52.101.84.142, remotePort = 39831, id = “MtaStageRcpt with id singleton”, key = “waitOnFail”, result = “Integer(5000)”
2026-06-17T13:20:39Z TRACE Expression evaluation result (eval.result) listenerId = “smtp”, localPort = 25, remoteIp = 52.101.84.142, remotePort = 39831, id = “MtaStageRcpt with id singleton”, key = “maxRecipients”, result = “Integer(100)”
2026-06-17T13:20:39Z TRACE Expression evaluation result (eval.result) listenerId = “smtp”, localPort = 25, remoteIp = 52.101.84.142, remotePort = 39831, id = “MtaExtensionswith id singleton”, key = “dsn”, result = “Integer(0)”
2026-06-17T13:20:39Z TRACE Expression evaluation result (eval.result) listenerId = “smtp”, localPort = 25, remoteIp = 52.101.84.142, remotePort = 39831, id = “MtaStageData with id singleton”, key = “maxMessageSize”, result = “Integer(104857600)”
2026-06-17T13:20:39Z TRACE Raw SMTP output sent (smtp.raw-output) listenerId = “smtp”, localPort = 25, remoteIp = 52.101.84.142, remotePort = 39831, size = 14, contents = “250 2.1.0 OK\r\n”
2026-06-17T13:20:39Z TRACE Expression evaluation result (eval.result) listenerId = “smtp”, localPort = 25, remoteIp = 52.101.84.142, remotePort = 39831, id = “MtaStageRcpt with id singleton”, key = “script”, result = “Integer(0)”
2026-06-17T13:20:39Z TRACE Expression evaluation result (eval.result) listenerId = “smtp”, localPort = 25, remoteIp = 52.101.84.142, remotePort = 39831, id = “MtaStageRcpt with id singleton”, key = “script”, result = “”
2026-06-17T13:20:39Z TRACE Expression evaluation result (eval.result) listenerId = “smtp”, localPort = 25, remoteIp = 52.101.84.142, remotePort = 39831, id = “MtaStageRcpt with id singleton”, key = “rewrite”, result = “Integer(0)”
2026-06-17T13:20:39Z TRACE Expression evaluation result (eval.result) listenerId = “smtp”, localPort = 25, remoteIp = 52.101.84.142, remotePort = 39831, id = “MtaStageRcpt with id singleton”, key = “rewrite”, result = “”
2026-06-17T13:20:39Z DEBUG Cache hit (store.cache-hit) key = “destinationdomain.ro”, collection = “domainNameNegative”
2026-06-17T13:20:39Z TRACE Expression evaluation result (eval.result) listenerId = “smtp”, localPort = 25, remoteIp = 52.101.84.142, remotePort = 39831, id = “MtaStageRcpt with id singleton”, key = “allowRelaying”, result = “Integer(0)”
2026-06-17T13:20:39Z INFO Relay not allowed (smtp.relay-not-allowed) listenerId = “smtp”, localPort = 25, remoteIp = 52.101.84.142, remotePort = 39831, to = “[email protected]

Stalwart Version

v0.16.x

Installation Method

Binary (Linux)

Database Backend

RocksDB

Blob Storage

RocksDB

Search Engine

Internal

Directory Backend

LDAP

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

The key clue in the logs is this line:

Cache hit (store.cache-hit) key = "destinationdomain.ro", collection = "domainNameNegative"

The domain destinationdomain.ro is cached as not a local domain (negative cache hit), so when the RCPT TO check runs, allowRelaying evaluates to 0 and the message is rejected. This is a caching issue – the negative cache entry from before the alias/domain was properly recognized is preventing the lookup from succeeding.

The log output reveals the root cause clearly. There are two separate issues to look at:

The key line is this:

Cache hit (store.cache-hit) key = "destinationdomain.ro", collection = "domainNameNegative"

Followed immediately by:

allowRelaying = Integer(0)
Relay not allowed

destinationdomain.ro was previously looked up and not found as a local domain, and that negative result was cached. Because the domain is not recognized as local, allowRelaying evaluates to 0 and the message is rejected. There are two things to verify:


1. Is destinationdomain.ro registered as a Domain object in Stalwart?

Per the Domains documentation, a Domain object must exist for every domain the server should accept mail for. Without it, the server treats that domain as external and refuses delivery. Check in the WebUI under Management > Domains that destinationdomain.ro is listed and isEnabled is true.

2. The negative domain name cache (TTL 1 hour by default)

Even if the domain IS correctly configured, a prior failed lookup may be stuck in the domainNamesNegative cache for up to 1 hour (the default negativeTtl). After fixing the domain configuration, you can either wait for the TTL to expire or restart the server to flush in-memory caches.

You can tune the negative cache TTL in the WebUI under Settings > Storage > Cache by lowering negativeTtl if you want faster propagation of directory changes.

3. LDAP filterMailbox for aliases

If the domain IS registered but the alias address specifically isn’t being resolved, double-check that your LDAP filterMailbox filter includes the attribute used by Windows AD for aliases (e.g. proxyAddresses). The default filter uses mailAlias, which may not match your AD schema. See the LDAP directory docs for filter configuration.

The most likely cause given the log is #1 or #2 – the domainNameNegative cache hit for destinationdomain.ro is preventing the server from even attempting a recipient lookup.

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

destinationdomain.ro was not registered as alias but worked in 0.16.8

When I find out that is not working in 0.16.9 I registered as a domain alias in WebUI but I didn’t expected 1 hour.

I don’t remember if I restarted the server or not between registering the destinationdomain.ro as alias and downgrading to 0.16.8

I did another upgrade now (after destinationdomain.ro was registered as alias for more then 1 hour) and everything is working as expected.

From my point of view was a bug solved in 0.16.9 (not taking into consideration the domain alias when a LDAP connection was in place) and not mentioned in Relese notes.

Anyway everything is working now as it should.