OutboundPolicy for notifications is hard-coded to `OutboundPolicy::PublicOnly`

Issue Description

I’m running Stalwart on a server on an internal network with several other services accessible using split DNS, including ntfy, for push notifications.

Stalwart allows registrations but doesn’t send any notifications. As best I can tell, this is because the HTTP client used to send them is hard-coded to OutboundPolicy::PublicOnly, which results in internal IP addresses being discarded, which is problematic because my notification server has an internal IP address.

To confirm, I ran the server with the following patch applied. When I did, push notifications worked correctly.

diff --git a/crates/jmap/src/push/set.rs b/crates/jmap/src/push/set.rs
index c8e7484c..574052c4 100644
--- a/crates/jmap/src/push/set.rs
+++ b/crates/jmap/src/push/set.rs
@@ -425,7 +425,7 @@ fn validate_push_value(
 }

 fn validate_push_url(url: &str) -> Result<(), OutboundError> {
-    OutboundPolicy::PublicOnly.parse_url(url).map(|_| ())
+    OutboundPolicy::Trusted.parse_url(url).map(|_| ())
 }

 fn parse_email_push(
diff --git a/crates/services/src/state_manager/http.rs b/crates/services/src/state_manager/http.rs
index bb9f5242..1b825932 100644
--- a/crates/services/src/state_manager/http.rs
+++ b/crates/services/src/state_manager/http.rs
@@ -259,7 +259,7 @@ async fn post_object(
 pub(crate) fn build_push_client() -> OutboundClient {
     OutboundClient::new(
         utils::http::http_client_builder(cfg!(stalwart_tests)),
-        OutboundPolicy::PublicOnly,
+        OutboundPolicy::Trusted,
         Redirects::SameOrganization(MAX_REDIRECTS),
     )
 }

Expected Behavior

OutboundPolicy::PublicOnly is configurable. When not used, notifications to an internal IP succeed.

Actual Behavior

OutboundPolicy is not configurable. Notifications to an internal IP are blocked.

Reproduction Steps

  1. Set up ntfy on an internal server with split DNS.
  2. Register a client for notifications using ntfy
  3. Trigger something that results in a notification.

Relevant Log Output

2026-10-30T20:55:46Z DEBUG Push subscription error (push-subscription.error) details = "HTTP POST failed", url = "<url>", reason = "error sending request for url (<url>)"

Stalwart Version

v1.x

Installation Method

Docker

Database Backend

RocksDB

Blob Storage

RocksDB

Search Engine

Internal

Directory Backend

Internal

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

Your reading is correct, and it’s documented behaviour. I found no setting that changes it.

Stalwart accepts a push subscription only for a public https URL. A URL is refused if it uses another scheme, contains credentials, or has a host that is private, loopback or otherwise non-public. The docs say the same check is applied again when notifications are sent. So a name that resolves to an internal address through split DNS won’t receive notifications, even if the subscription was accepted. This matches what your patch shows: the restriction is applied at both validation and send time, and neither place has a configuration option.

Until the policy becomes configurable, there are two workarounds I can see in the docs:

  • Expose ntfy on a public address with https.
  • Keep running your patched build.

I found no documented setting for allowing internal destinations. Making the policy configurable would be a feature request, and a human will follow up here.

Docs: Push notifications | Stalwart

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

Regarding the documented behaviour:

A URL with another scheme, a URL with credentials, and a URL whose host is a private, loopback or otherwise non-public address or name are refused with invalidProperties on url, with a description such as URL must use the https scheme or Destination is a local or reserved IP address.

This does not seem to be the case. At no point do I see an error like this, I only see “HTTP POST failed”.