Issue Description
This is a follow-up to the finding in “DMARC Report: Strategy Not Found when STARTTLS Fails” (topic 1317), where the suggested remedy for the Strategy not found warning is to create an MtaDeliverySchedule named report.
We have exactly that configuration — a report delivery schedule AND a report virtual queue, with the outbound strategy’s Scheduling expression matching the canonical example in the docs — and the report schedule/queue still never engages. Every outgoing DMARC aggregate report is enqueued and delivered on queueName = "remote", with no warning logged.
Configuration, read back via stalwart-cli:
MtaVirtualQueue:
| Id | Name | Delivery Threads |
|---|---|---|
| d | report | 5 |
| c | dsn | 5 |
| b | remote | 50 |
| a | local | 25 |
MtaDeliverySchedule: four schedules — local → queue local, remote → remote, dsn → dsn, report → report (id: d).
MtaOutboundStrategy (singleton), Scheduling expression:
Conditions:
0 is_local_domain(rcpt_domain) 'local'
1 source == 'dsn' 'dsn'
2 source == 'report' 'report'
Default Value: 'remote'
Observed:
- This server generates a substantial volume of outgoing DMARC aggregate reports as a receiving MTA (dozens to hundreds of
queue.report-queuedevents per day), so thereportbranch has had thousands of chances to fire. - Across 7 days of logs (~7,200 queue/delivery events carrying a
queueNamefield), the only values that appear arelocal(4,729) andremote(2,451).reportanddsnappear zero times. - An end-to-end trace of a single aggregate report on v0.16.17 (log excerpt below) shows every event from
queue.report-queuedthroughdelivery.deliveredwithqueueName = "remote". - Unlike topic 1317 (schedule missing), no
smtp.id-not-foundwarning is ever logged — the message simply never lands on thereportqueue. - The Routing expression on the same strategy object evaluates correctly (
is_local_domain(rcpt_domain)routes local vs relay as configured), so expression evaluation as such works; it is specifically thesource-based Scheduling branches that appear inert at queue-placement time.
Behaviour is identical on 0.16.15 and 0.16.17 (the trace below is post-upgrade to 0.16.17).
Expected Behavior
Per the Scheduling documentation (Scheduling | Stalwart), a message generated as an aggregate report should match source == 'report' and be placed on the report delivery schedule, whose queueId maps it to the report virtual queue — i.e. log events for report deliveries should carry queueName = "report" (and DSNs queueName = "dsn").
Actual Behavior
Every aggregate report is enqueued and delivered with queueName = "remote" (the expression’s default). The report and dsn queue names have never appeared in any log line — 7 days / ~7,200 queueName-carrying events contain only local and remote — despite dozens-to-hundreds of reports being generated daily. No smtp.id-not-found warning is logged (the schedule exists), the report queue is simply never selected.
Reproduction Steps
- Define an
MtaVirtualQueuenamedreportand anMtaDeliverySchedulenamedreportreferencing it (and likewisedsn), alongside the stocklocal/remotepair. - Keep the outbound strategy’s Scheduling expression as the documented example:
is_local_domain(rcpt_domain) -> 'local',source == 'dsn' -> 'dsn',source == 'report' -> 'report', default'remote'. - Operate as a receiving MTA with incoming mail from DMARC-publishing domains so outgoing aggregate reports are generated (
queue.report-queuedevents). - Observe
queueNameon the subsequent delivery events for those reports: alwaysremote, neverreport.
Relevant Log Output
2026-08-11T18:00:34Z INFO Queued report for delivery (queue.report-queued) listenerId = “smtp”, localPort = 25, remoteIp = x.x.x.x, remotePort = 56268, queueId = 323362456101468672, from = “[email protected]”, to = [“[email protected]”], size = 3865, nextRetry = 2026-08-11T18:00:34Z, nextDsn = 2026-08-12T18:00:34Z, expires = 2026-08-14T18:00:34Z
2026-08-11T18:00:34Z INFO Delivery attempt started (delivery.attempt-start) queueId = 323362456101468672, queueName = “remote”, from = “[email protected]”, to = [“[email protected]”], size = 3865, total = 1
2026-08-11T18:00:34Z INFO New delivery attempt for domain (delivery.domain-delivery-start) queueId = 323362456101468672, queueName = “remote”, from = “[email protected]”, to = [“[email protected]”], size = 3865, total = 1, domain = “redacted.tld”
2026-08-11T18:00:34Z INFO Connecting to remote server (delivery.connect) queueId = 323362456101468672, queueName = “remote”, from = “[email protected]”, to = [“[email protected]”], size = 3865, total = 1, domain = “redacted.tld”, hostname = “mail.btinternet.com”, localIp = x.x.x.x, remoteIp = x.x.x.x, remotePort = 465, elapsed = 6ms
2026-08-11T18:00:38Z INFO Message delivered (delivery.delivered) queueId = 323362456101468672, queueName = “remote”, from = “[email protected]”, to = [“[email protected]”], size = 3865, total = 1, hostname = “mail.btinternet.com”, code = 250, details = “Mail accepted”, elapsed = 186ms
2026-08-11T18:00:38Z INFO Delivery completed (delivery.completed) queueId = 323362456101468672, queueName = “remote”, from = “[email protected]”, to = [“[email protected]”], size = 3865, total = 1, elapsed = 4000ms
7-day queueName histogram (ANSI colour stripped before grepping):
4729 queueName = “local”
2451 queueName = “remote”
(zero occurrences of “report” or “dsn”)
Stalwart Version
v0.16.x
Installation Method
Binary (Linux)
Database Backend
RocksDB
Blob Storage
RocksDB
Search Engine
Internal
Directory Backend
Internal
Additional Context
Exact versions: observed on v0.16.15 and v0.16.17 (trace in the log output is from 0.16.17). The report MtaDeliverySchedule/MtaVirtualQueue objects were created ~2 weeks before the trace, so this is steady-state behaviour, not a config-propagation race (the server has been restarted several times since, including for the 0.16.17 upgrade). Happy to provide full object dumps or run instrumented tests against this server if useful.
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