SMTP submission hangs after DATA on frontend without outbound MTA role

Issue Description

When trying to send via submission service thunderbird reports error: Sending of the message failed. The message could not be sent because the connection to Outgoing server (SMTP) submission.mxbox.cloud timed out. Try again. Message gets delivered to the recipient but doesn’t get saved in “Sent”

Expected Behavior

Stalwart returns 250 after accepting the message body.

Actual Behavior

Thunderbird received 354, sent a 708-byte body, then timed out after 100 seconds without a final reply. Stalwart queued the message and delivered it to all recipients during that wait. This can cause duplicate sends when users retry.

Submission awaits a local queue refresh, but the queue manager is not started when outboundMta is disabled. The same code path remains in 0.16.23. Skipping the local refresh on such nodes while retaining the cluster broadcast appears to address the issue.

Reproduction Steps

  1. Run four Stalwart processes against the same FoundationDB data store and Redis
    coordinator:
  • Three frontend replicas accepting SMTP and IMAP traffic, with outboundMta disabled.
  • One separate outbound worker with outboundMta enabled.
  1. Send sustained traffic through the same submission frontend (port 465, TLS)
    until its local queue notification channel fills, then submit another message. (The 1,024-event threshold is inferred from the code, not measured directly.)

Stalwart Version

v0.16.x

Installation Method

Built from source

Database Backend

FoundationDB

Blob Storage

S3-compatible

Search Engine

Internal

Directory Backend

Internal

Additional Context

Submission awaits a local queue refresh, but the queue manager is not started when outboundMta is disabled. The same code path remains in 0.16.23. Skipping the local refresh on such nodes while retaining the cluster broadcast appears to address the issue.

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 confirms the report’s finding exactly. Let’s check spool.rs where it sends QueueEvent::Refresh locally regardless of role, causing the hang since no queue manager consumes it on frontend nodes.

Your analysis matches what’s in the source. On any node started without the outboundMta cluster role, SpawnQueueManager::spawn_queue_manager skips creating the queue manager task entirely:

if !core.storage.registry.is_recovery_mode() && core.network.roles.outbound_mta {
    // Spawn queue manager
    self.queue_rx.take().unwrap().spawn(inner.clone());
    ...
}
```//`crates/smtp/src/lib.rs`

But the submission code path (`crates/smtp/src/queue/spool.rs`) always does:

```rust
server.inner.ipc.queue_tx.send(QueueEvent::Refresh).await

queue_tx/queue_rx is a bounded mpsc::channel with a fixed buffer of 1024 (IPC_CHANNEL_BUFFER in crates/common/src/lib.rs). On a frontend-only node nothing ever drains queue_rx (it’s never spawned), so once 1024 unread Refresh events accumulate, .send(...).await blocks, and since this happens synchronously in the SMTP submission handler after the message is written and queued, the client connection hangs waiting for the final 250 even though the message has already been queued and dispatched (via cluster_broadcast(BroadcastEvent::QueueRefresh), which is a separate, non-blocking path picked up by the outbound worker).

This is a genuine bug in the current source for the outbound-mta-disabled frontend role split you’re describing. I don’t have visibility into fix/release status beyond the source, so I can’t confirm whether a patch has already landed, but your reproduction and root-cause analysis line up with the code as it exists. A staff member will need to confirm the fix and release timeline.

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

This has been fixed in v0.16.24 (yet to be released).