Email to domain alias randomly treated as non local

Issue Description

When an email arrives with an RCPT using a domain alias, it’s randomly treated as non local, and sent to my outbound relay instead of being delivered localy. It’s not systematic (sometimes, it’s correctly delivered localy) so I suspect a cache issue a bit like Fix Directory: Mail addressed to a domain alias is rejected with `550… · stalwartlabs/stalwart@deb034b · GitHub

This is running stalwart 0.16.19

Expected Behavior

Email using a domain alias should always be treated as local

Actual Behavior

The email is first accepted, and placed in the “local” queue. Then the stalwart instance responsible for outbound delivery treats it like remote, and tries to deliver it to my outbound relay. Not sure if running stalwart as a distributed install has an impact (I have only 2 kinds of nodes : 1 for outbound delivery, and 1 for everything else. Using valkey as coordinator)

The problem exist after a cold start of every components. But once an email is delivered to the main domain, after that, emails to the domain alias start working

Reproduction Steps

  1. Configure a domain with an alias
  2. Send an email to a valid recipient, but using the domain alias
  3. The mail is accepted by the inbound SMTP, and placed in the local queue
  4. The email in the local queue is delivered as if it was remote (eg, to an outbound relay) instead of being delivered localy

Relevant Log Output

In these logs snippets, [email protected] is an external address trying to send an email to [email protected]. dani is a valid account on the domain foo.org, which has alias.foo.org as a domain alias configured

First, we can see the email being accepted and placed in the local queue (I just removed the DNSBL lookup logs as they are not relevent)

2026-08-25T10:32:19 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:19Z INFO SMTP EHLO command (smtp.ehlo) listenerId = "smtp", localPort = 25, remoteIp = 10.99.2.14, remotePort = 59924, domain = "pmg.foo.org" 
2026-08-25T10:32:19 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:19Z INFO SPF EHLO check failed (smtp.spf-ehlo-fail) listenerId = "smtp", localPort = 25, remoteIp = 10.99.2.14, remotePort = 59924, domain = "pmg.foo.org", result = No SPF record (spf.none), elapsed = 0ms 
2026-08-25T10:32:19 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:19Z DEBUG SMTP STARTTLS command (smtp.start-tls) listenerId = "smtp", localPort = 25, remoteIp = 10.99.2.14, remotePort = 59924 
2026-08-25T10:32:19 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:19Z WARN Multiple TLS certificates available (tls.multiple-certificates-available) total = 5 
2026-08-25T10:32:19 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:19Z INFO TLS handshake (tls.handshake) listenerId = "smtp", localPort = 25, remoteIp = 10.99.2.14, remotePort = 59924, listenerId = "smtp", version = "TLSv1_3", details = "TLS13_AES_256_GCM_SHA384" 
2026-08-25T10:32:19 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:19Z INFO IPREV check passed (smtp.iprev-pass) listenerId = "smtp", localPort = 25, remoteIp = 10.99.2.14, remotePort = 59924, domain = "pmg.foo.org", result = IPREV check passed (iprev.pass) { details = ["pmg.foo.org."] }, elapsed = 1ms 
2026-08-25T10:32:19 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:19Z INFO SPF From check failed (smtp.spf-from-fail) listenerId = "smtp", localPort = 25, remoteIp = 10.99.2.14, remotePort = 59924, domain = "pmg.foo.org", from = "[email protected]", result = SPF check failed (spf.fail), elapsed = 2ms 
2026-08-25T10:32:19 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:19Z INFO SMTP MAIL FROM command (smtp.mail-from) listenerId = "smtp", localPort = 25, remoteIp = 10.99.2.14, remotePort = 59924, from = "[email protected]" 
2026-08-25T10:32:19 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:19Z DEBUG Cache hit (store.cache-hit) key = "alias.foo.org", collection = "domainName" 
2026-08-25T10:32:19 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:19Z DEBUG Cache hit (store.cache-hit) key = 4, collection = "domainId" 
2026-08-25T10:32:19 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:19Z DEBUG Cache hit (store.cache-hit) key = "dani", domain = 4, collection = "email" 
2026-08-25T10:32:19 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:19Z DEBUG Cache hit (store.cache-hit) key = 66, collection = "account" 
2026-08-25T10:32:19 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:19Z INFO SMTP RCPT TO command (smtp.rcpt-to) listenerId = "smtp", localPort = 25, remoteIp = 10.99.2.14, remotePort = 59924, to = "[email protected]" 
2026-08-25T10:32:19 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:19Z INFO DKIM verification passed (smtp.dkim-pass) listenerId = "smtp", localPort = 25, remoteIp = 10.99.2.14, remotePort = 59924, strict = false, result = [DKIM verification passed (dkim.pass) { domain = "acme.com" }], elapsed = 0ms 
2026-08-25T10:32:19 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:19Z INFO DMARC check passed (smtp.dmarc-pass) listenerId = "smtp", localPort = 25, remoteIp = 10.99.2.14, remotePort = 59924, strict = false, domain = "acme.com", policy = "quarantine", result = DMARC check passed (dmarc.pass), elapsed = 9ms 
[...]
2026-08-25T10:32:20 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:20Z DEBUG Cache miss (store.cache-miss) key = "acme.com", collection = "domainName" 
[...]
2026-08-25T10:32:20 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:20Z DEBUG Cache hit (store.cache-hit) key = "acme.com", collection = "domainNameNegative" 
[...]
2026-08-25T10:32:21 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:21Z DEBUG Cache miss (store.cache-miss) key = "pmg.foo.org", collection = "domainName" 
[...]
2026-08-25T10:32:22 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:22Z DEBUG Cache miss (store.cache-miss) key = "zmstore.acme.com", collection = "domainName" 
[...]
2026-08-25T10:32:22 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:22Z DEBUG Cache miss (store.cache-miss) key = "zmproxy.acme.com", collection = "domainName" 
[...]
2026-08-25T10:32:22 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:22Z DEBUG Cache miss (store.cache-miss) key = "pmg.acme.com", collection = "domainName" 
[...]
2026-08-25T10:32:23 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:23Z DEBUG Cache hit (store.cache-hit) key = "acme.com", collection = "domainNameNegative" 
2026-08-25T10:32:23 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:23Z DEBUG Cache miss (store.cache-miss) key = "linkedin.com", collection = "domainName" 
2026-08-25T10:32:23 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:23Z DEBUG Cache hit (store.cache-hit) key = "acme.com", collection = "domainNameNegative" 
2026-08-25T10:32:23 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:23Z DEBUG Cache miss (store.cache-miss) key = "goo.gl", collection = "domainName" 
2026-08-25T10:32:23 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:23Z DEBUG Sieve runtime error (sieve.runtime-error) listenerId = "smtp", localPort = 25, remoteIp = 10.99.2.14, remotePort = 59924, reason = "error sending request for url (https://goo.gl/maps/euyoe1ypqcy29tzy8)", details = "Failed to send request" 
2026-08-25T10:32:23 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:23Z DEBUG Cache hit (store.cache-hit) key = "acme.com", collection = "domainNameNegative" 
2026-08-25T10:32:23 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:23Z DEBUG Cache hit (store.cache-hit) key = "acme.com", collection = "domainNameNegative" 
2026-08-25T10:32:23 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:23Z DEBUG Cache hit (store.cache-hit) key = "acme.com", collection = "domainNameNegative" 
2026-08-25T10:32:23 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:23Z DEBUG Cache hit (store.cache-hit) key = "acme.com", collection = "domainNameNegative" 
2026-08-25T10:32:23 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:23Z DEBUG Cache hit (store.cache-hit) key = "acme.com", collection = "domainNameNegative" 
2026-08-25T10:32:23 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:23Z DEBUG Cache hit (store.cache-hit) key = "acme.com", collection = "domainNameNegative" 
2026-08-25T10:32:23 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:23Z DEBUG Cache hit (store.cache-hit) key = "acme.com", collection = "domainNameNegative" 
2026-08-25T10:32:23 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:23Z DEBUG Cache hit (store.cache-hit) key = "linkedin.com", collection = "domainNameNegative" 
2026-08-25T10:32:23 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:23Z DEBUG Cache hit (store.cache-hit) key = "acme.com", collection = "domainNameNegative" 
2026-08-25T10:32:23 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:23Z DEBUG Cache hit (store.cache-hit) key = "acme.com", collection = "domainNameNegative" 
2026-08-25T10:32:23 [j=stalwart, g=backend, t=stalwart, i=0] 2026-08-25T08:32:23Z DEBUG Cache hit (store.cache-hit) key = "acme.com", collection = "domainNameNegative" 
[...]

Then, my node dedicated to outbound delivery (which STALWART_ROLE only has the “Outbound Email MTA” task) takes it from the local queue, and tries to deliver it remotely to my outbound relay, instead of delivering it localy (note that my outbound relay isn’t working here, and this is on purpose as I’m working on a test system)

2026-08-25T10:32:29 [j=stalwart-sender, g=sender, t=stalwart, i=0] 2026-08-25T08:32:29Z INFO Delivery attempt started (delivery.attempt-start) queueId = 325827668083736576, queueName = "local", from = "[email protected]", to = ["[email protected]"], size = 12485, total = 1 
2026-08-25T10:32:29 [j=stalwart-sender, g=sender, t=stalwart, i=0] 2026-08-25T08:32:29Z DEBUG Cache miss (store.cache-miss) key = "alias.foo.org", collection = "domainName" 
2026-08-25T10:32:29 [j=stalwart-sender, g=sender, t=stalwart, i=0] 2026-08-25T08:32:29Z INFO New delivery attempt for domain (delivery.domain-delivery-start) queueId = 325827668083736576, queueName = "local", from = "[email protected]", to = ["[email protected]"], size = 12485, total = 1, domain = "alias.foo.org" 
2026-08-25T10:32:29 [j=stalwart-sender, g=sender, t=stalwart, i=0] 2026-08-25T08:32:29Z DEBUG IP address lookup (delivery.ip-lookup) queueId = 325827668083736576, queueName = "local", from = "[email protected]", to = ["[email protected]"], size = 12485, total = 1, domain = "alias.foo.org", hostname = "127.0.0.1", details = [127.0.0.1], limit = 10, elapsed = 0ms 
2026-08-25T10:32:29 [j=stalwart-sender, g=sender, t=stalwart, i=0] 2026-08-25T08:32:29Z INFO TLSA record not DNSSEC signed (dane.tlsa-record-not-dnssec-signed) queueId = 325827668083736576, queueName = "local", from = "[email protected]", to = ["[email protected]"], size = 12485, total = 1, domain = "alias.foo.org", hostname = "127.0.0.1", strict = false, elapsed = 0ms 
2026-08-25T10:32:29 [j=stalwart-sender, g=sender, t=stalwart, i=0] 2026-08-25T08:32:29Z INFO Connecting to remote server (delivery.connect) queueId = 325827668083736576, queueName = "local", from = "[email protected]", to = ["[email protected]"], size = 12485, total = 1, domain = "alias.foo.org", hostname = "127.0.0.1", localIp = 0.0.0.0, remoteIp = 127.0.0.1, remotePort = 2525, elapsed = 0ms 
2026-08-25T10:32:29 [j=stalwart-sender, g=sender, t=stalwart, i=0] 2026-08-25T08:32:29Z INFO SMTP greeting failed (delivery.greeting-failed) queueId = 325827668083736576, queueName = "local", from = "[email protected]", to = ["[email protected]"], size = 12485, total = 1, domain = "alias.foo.org", hostname = "127.0.0.1", details = "Temporary Failure for 127.0.0.1: Connection failed: Unparseable SMTP reply" 
2026-08-25T10:32:29 [j=stalwart-sender, g=sender, t=stalwart, i=0] 2026-08-25T08:32:29Z DEBUG Cache hit (store.cache-hit) key = "alias.foo.org", collection = "domainNameNegative" 
2026-08-25T10:32:29 [j=stalwart-sender, g=sender, t=stalwart, i=0] 2026-08-25T08:32:29Z INFO Message rescheduled for delivery (queue.rescheduled) queueId = 325827668083736576, queueName = "local", from = "[email protected]", to = ["[email protected]"], size = 12485, total = 1, nextRetry = 2026-08-25T08:34:29Z, nextDsn = 2026-08-26T08:32:29Z, expires = 2026-08-28T08:32:29Z 
2026-08-25T10:32:29 [j=stalwart-sender, g=sender, t=stalwart, i=0] 2026-08-25T08:32:29Z INFO Delivery attempt ended (delivery.attempt-end) queueId = 325827668083736576, queueName = "local", from = "[email protected]", to = ["[email protected]"], size = 12485, total = 1, elapsed = 10ms 

Note that the final failure of the outbound relay (“Connection failed: Unparseable SMTP reply”). I’ve blocked the connection as this is a test system. But stalwart shouldn’t try to talk to this relay at all, and just deliver this email localy

Stalwart Version

v0.16.x

Installation Method

Docker

Database Backend

PostgreSQL

Blob Storage

PostgreSQL

Search Engine

PostgreSQL

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

Before that fix, email aliases were not being indexed. Try removing the alias from the domain, saving the changes and then adding the alias to the domain again so it is indexed.

Indeed, removing, then adding the domain alias back seems to fix the issue. Thanks !