Authenticated JMAP EmailSubmission to a recipient at a local (send-as) domain fails with "mailbox does not exist", while SMTP submission relays it

Your question

Summary

When a domain is configured as local (to host send-as aliases/identities at that
domain), an authenticated JMAP EmailSubmission to a recipient at that domain is
rejected with Mailbox does not exist, whereas an authenticated SMTP submission
(port 465) to the exact same recipient is correctly relayed to the outbound route.
Same authenticated user, same envelope — only the submission protocol differs.

Environment

  • Stalwart 0.16.20, self-hosted, object/webadmin config model.
  • External provider domains (gmail.com, yahoo.com, orange.fr, …) are added as
    local domains so the account can hold aliases + identities at those addresses
    (send-as / “send mail as”).
  • Outbound routing (MtaOutboundStrategy.route):
    is_local_domain(rcpt_domain) && rcpt_domain NOT IN [<those external domains>] -> 'local',
    else -> 'router' (a smarthost relay). So these domains are meant to be relayed out.

Expected

An authenticated user submitting to any recipient should follow the outbound routing
(relayed to the smarthost for these domains), regardless of protocol (SMTP vs JMAP).

Actual

  • SMTP submission (465, authenticated) RCPT TO:<[email protected]>
    queue.authenticated-message-queued (queueName remote) → Routed via <smarthost>
    delivered :white_check_mark:
  • JMAP EmailSubmission (authenticated, envelope.rcptTo = [{email:"[email protected]"}]) →
    smtp.mailbox-does-not-existrejected :cross_mark: (recipient at the local domain is treated
    as a local-delivery target instead of being relayed).

Minimal repro

  1. Add gmail.com as a local domain (with an alias, for send-as).
  2. Authenticated JMAP EmailSubmission/set, rcptTo = [email protected]fails
    mailbox does not exist.
  3. Same authenticated user, SMTP submission (465) to [email protected]relayed/delivered.

Notes

  • In the older TOML config this was solved with
    session.rcpt.directory = [{ if rcpt_domain=='X' then '*' } ...] + queue.outbound.next-hop
    (ref GitHub discussion #1169). The current object/webadmin model doesn’t seem to expose an equivalent.
  • Domain.allowRelaying = true does relay these recipients but bypasses authentication
    (verified: an unauthenticated SMTP session can then relay to that domain = open relay),
    so it isn’t a safe option here.

Request

Is there a supported, authentication-gated way (current config model) to let authenticated
submission — including JMAP — relay recipients at a local send-as domain, without opening
an unauthenticated relay? If not, could JMAP EmailSubmission be made consistent with SMTP
submission (honor the outbound routing / relay for authenticated users)?

Based on the current object model, this behavior is expected and is a limitation of how domain‑level relaying is implemented, not a JMAP‑vs‑SMTP inconsistency in itself.

There are two separate relay controls in Stalwart, and only one of them is authentication‑gated:

  • MtaStageRcpt.allowRelaying (session‑level, RCPT stage) — an expression evaluated for recipients whose domain does not match any configured local domain. The documented default only allows relaying when authenticated_as is non‑empty. This is the one that is safely auth‑gated.
  • Domain.allowRelaying — a per‑domain flag used for “split delivery”: once a domain is registered as local, any recipient that doesn’t resolve to a real local account/alias is relayed only if this flag is set. In the source (rcpt_resolve in crates/common/src/network/mta.rs), this check is applied unconditionally: if domain.flags & DOMAIN_FLAG_RELAY != 0 { Accept }, with no authentication check at all. That’s exactly why enabling it turns the domain into an open relay for unauthenticated senders too, as you found.

Because you’ve registered gmail.com etc. as local domains (to host aliases/identities), any unknown recipient at that domain is only evaluated against Domain.allowRelaying, not against the auth‑gated MtaStageRcpt.allowRelaying expression — that session‑level check only applies to domains that aren’t matched as local at all. There is currently no supported per‑domain, authentication‑gated equivalent to the old TOML session.rcpt.directory trick for this exact situation.

Both the SMTP RCPT path and the JMAP EmailSubmission path route through the same recipient‑resolution logic (Session::local internally reuses handle_rcpt_to), so if you’re seeing a real divergence between the two for the identical recipient/account/domain flag configuration, that would point to something protocol‑specific (e.g. a difference in how the account’s alias/identity list is consulted before falling through to rcpt_resolve) rather than a documented distinct rule. Worth double‑checking whether the differing behavior persists with Domain.allowRelaying left in the same state for both tests.

Reference: RCPT stage docs (see “Relay” and “Split‑delivery relaying” sections).

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