You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by hq...@apache.org on 2026/10/16 08:03:32 UTC

(james-project) 03/07: JAMES-4236 JMAP: reject delayed sends the server cannot honour

This is an automated email from the ASF dual-hosted git repository.

quantranhong1999 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 45e99ab17883d949e0febcb0081071097faf5c54
Author: Min Nguyen <mi...@gmail.com>
AuthorDate: Tue Oct 13 15:51:58 2026 +0700

    JAMES-4236 JMAP: reject delayed sends the server cannot honour
---
 docs/modules/servers/partials/configure/jmap.adoc  |  4 +-
 .../sample-configuration/jmap.properties           |  2 +-
 .../sample-configuration/jmap.properties           |  2 +-
 .../java/org/apache/james/jmap/JMAPModule.java     | 44 +++++++++++-
 .../java/org/apache/james/jmap/JMAPModuleTest.java | 38 ++++++++++-
 ...istributedDelaySendsOnRabbitMQStartUpTest.java} | 59 ++++++----------
 .../EmailSubmissionSetMethodContract.scala         | 79 ++++++++++++++++++++++
 ...> PostgresDelaySendsOnRabbitMQStartUpTest.java} | 58 ++++++----------
 .../jmap/method/EmailSubmissionSetMethod.scala     | 11 +--
 9 files changed, 210 insertions(+), 87 deletions(-)

diff --git a/docs/modules/servers/partials/configure/jmap.adoc b/docs/modules/servers/partials/configure/jmap.adoc
index 2eadeaa05e..9bf073725e 100644
--- a/docs/modules/servers/partials/configure/jmap.adoc
+++ b/docs/modules/servers/partials/configure/jmap.adoc
@@ -119,7 +119,9 @@ for JMAP filters. This projection optimizes reads, but needs to be correctly pop
 systems with filters already defined would result in those filters to be not read.
 
 | delay.sends.enabled
-| Optional boolean. Defaults to false. Whether to support or not the delay send with JMAP protocol.
+| Optional boolean. Defaults to false. Whether to support or not the delay send with JMAP protocol. When disabled,
+`EmailSubmission/set` rejects `holdFor` / `holdUntil`. Requires a mail queue supporting delays: the RabbitMQ mail
+queue does not, and the server refuses to start when this option is enabled on top of it.
 
 | send.validate.rcpt
 | Optional boolean. Defaults to false. Whether `EmailSubmission/set` rejects recipients James knows it cannot
diff --git a/server/apps/distributed-app/sample-configuration/jmap.properties b/server/apps/distributed-app/sample-configuration/jmap.properties
index fc37f1499c..0b27f829ed 100644
--- a/server/apps/distributed-app/sample-configuration/jmap.properties
+++ b/server/apps/distributed-app/sample-configuration/jmap.properties
@@ -6,7 +6,7 @@ enabled=true
 tls.keystoreURL=file://conf/keystore
 tls.secret=james72laBalle
 
-# only not work for RabbitMQ mail queue
+# Not supported by the RabbitMQ mail queue: the server refuses to start when enabled on top of it
 #delay.sends.enabled=true
 
 # Reject recipients James knows it cannot deliver to upon EmailSubmission/set, rather than bouncing
diff --git a/server/apps/postgres-app/sample-configuration/jmap.properties b/server/apps/postgres-app/sample-configuration/jmap.properties
index fc37f1499c..0b27f829ed 100644
--- a/server/apps/postgres-app/sample-configuration/jmap.properties
+++ b/server/apps/postgres-app/sample-configuration/jmap.properties
@@ -6,7 +6,7 @@ enabled=true
 tls.keystoreURL=file://conf/keystore
 tls.secret=james72laBalle
 
-# only not work for RabbitMQ mail queue
+# Not supported by the RabbitMQ mail queue: the server refuses to start when enabled on top of it
 #delay.sends.enabled=true
 
 # Reject recipients James knows it cannot deliver to upon EmailSubmission/set, rather than bouncing
diff --git a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPModule.java b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPModule.java
index b35544b0e8..bd71becc4f 100644
--- a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPModule.java
+++ b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPModule.java
@@ -62,6 +62,8 @@ import org.apache.james.mailbox.MailboxManager;
 import org.apache.james.mailbox.MailboxManager.SearchCapabilities;
 import org.apache.james.modules.server.MailetContainerModule;
 import org.apache.james.modules.server.MailetContainerModule.ProcessorsCheck;
+import org.apache.james.queue.api.MailQueue;
+import org.apache.james.queue.api.MailQueueFactory;
 import org.apache.james.queue.api.MailQueueItemDecoratorFactory;
 import org.apache.james.server.core.configuration.FileConfigurationProvider;
 import org.apache.james.transport.mailets.VacationMailet;
@@ -148,7 +150,9 @@ public class JMAPModule extends AbstractModule {
         bind(JsoupHtmlTextExtractor.class).in(Scopes.SINGLETON);
 
         bind(HtmlTextExtractor.class).to(JsoupHtmlTextExtractor.class);
-        Multibinder.newSetBinder(binder(), StartUpCheck.class).addBinding().to(RequiredCapabilitiesStartUpCheck.class);
+        Multibinder<StartUpCheck> startUpChecks = Multibinder.newSetBinder(binder(), StartUpCheck.class);
+        startUpChecks.addBinding().to(RequiredCapabilitiesStartUpCheck.class);
+        startUpChecks.addBinding().to(DelaySendsStartUpCheck.class);
 
         bind(MailQueueItemDecoratorFactory.class).to(PostDequeueDecoratorFactory.class).in(Scopes.SINGLETON);
 
@@ -319,4 +323,42 @@ public class JMAPModule extends AbstractModule {
             return CHECK_NAME;
         }
     }
+
+    /**
+     * A mail queue ignoring delays would send delayed mails right away: refuse to start rather than
+     * advertising delayed sends we cannot honour.
+     */
+    @Singleton
+    public static class DelaySendsStartUpCheck implements StartUpCheck {
+        public static final String CHECK_NAME = "DelaySendsForJMAP";
+
+        private final boolean supportsDelaySends;
+        private final MailQueueFactory<? extends MailQueue> mailQueueFactory;
+
+        @Inject
+        public DelaySendsStartUpCheck(@Named("supportsDelaySends") boolean supportsDelaySends, MailQueueFactory<? extends MailQueue> mailQueueFactory) {
+            this.supportsDelaySends = supportsDelaySends;
+            this.mailQueueFactory = mailQueueFactory;
+        }
+
+        @Override
+        public CheckResult check() {
+            if (supportsDelaySends && !mailQueueFactory.supportsDelays()) {
+                return CheckResult.builder()
+                    .checkName(checkName())
+                    .resultType(ResultType.BAD)
+                    .description("'delay.sends.enabled' is set in jmap.properties but the mail queue does not support delays")
+                    .build();
+            }
+            return CheckResult.builder()
+                .checkName(checkName())
+                .resultType(ResultType.GOOD)
+                .build();
+        }
+
+        @Override
+        public String checkName() {
+            return CHECK_NAME;
+        }
+    }
 }
diff --git a/server/container/guice/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPModuleTest.java b/server/container/guice/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPModuleTest.java
index 8192451e0b..49b414a803 100644
--- a/server/container/guice/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPModuleTest.java
+++ b/server/container/guice/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPModuleTest.java
@@ -26,8 +26,10 @@ import static org.mockito.Mockito.when;
 
 import java.util.EnumSet;
 
+import org.apache.james.jmap.JMAPModule.DelaySendsStartUpCheck;
 import org.apache.james.jmap.JMAPModule.RequiredCapabilitiesStartUpCheck;
 import org.apache.james.mailbox.MailboxManager;
+import org.apache.james.queue.api.MailQueueFactory;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
@@ -102,4 +104,38 @@ class JMAPModuleTest {
                 .isTrue();
         }
     }
-}
\ No newline at end of file
+
+    @Nested
+    class DelaySendsStartUpCheckTest {
+        private MailQueueFactory<?> mailQueueFactory;
+
+        @BeforeEach
+        void beforeEach() {
+            mailQueueFactory = mock(MailQueueFactory.class);
+        }
+
+        @Test
+        void checkShouldReturnBadWhenDelaySendsAreEnabledOnAQueueNotSupportingDelays() {
+            when(mailQueueFactory.supportsDelays()).thenReturn(false);
+
+            assertThat(new DelaySendsStartUpCheck(true, mailQueueFactory).check().isBad())
+                .isTrue();
+        }
+
+        @Test
+        void checkShouldReturnGoodWhenDelaySendsAreEnabledOnAQueueSupportingDelays() {
+            when(mailQueueFactory.supportsDelays()).thenReturn(true);
+
+            assertThat(new DelaySendsStartUpCheck(true, mailQueueFactory).check().isGood())
+                .isTrue();
+        }
+
+        @Test
+        void checkShouldReturnGoodWhenDelaySendsAreDisabled() {
+            when(mailQueueFactory.supportsDelays()).thenReturn(false);
+
+            assertThat(new DelaySendsStartUpCheck(false, mailQueueFactory).check().isGood())
+                .isTrue();
+        }
+    }
+}
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedEmailSubmissionSetMethodFutureReleaseTest.java b/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedDelaySendsOnRabbitMQStartUpTest.java
similarity index 59%
rename from server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedEmailSubmissionSetMethodFutureReleaseTest.java
rename to server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedDelaySendsOnRabbitMQStartUpTest.java
index 3cd86a7e29..5e5bb48aad 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedEmailSubmissionSetMethodFutureReleaseTest.java
+++ b/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedDelaySendsOnRabbitMQStartUpTest.java
@@ -19,34 +19,32 @@
 
 package org.apache.james.jmap.rfc8621.distributed;
 
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
 import org.apache.james.CassandraExtension;
 import org.apache.james.CassandraRabbitMQJamesConfiguration;
 import org.apache.james.CassandraRabbitMQJamesServerMain;
-import org.apache.james.ClockExtension;
 import org.apache.james.DockerOpenSearchExtension;
 import org.apache.james.GuiceJamesServer;
 import org.apache.james.JamesServerBuilder;
 import org.apache.james.JamesServerExtension;
 import org.apache.james.MailQueueChoice;
 import org.apache.james.SearchConfiguration;
-import org.apache.james.jmap.rfc8621.contract.EmailSubmissionSetMethodFutureReleaseContract;
-import org.apache.james.jmap.rfc8621.contract.probe.DelegationProbeModule;
-import org.apache.james.mailbox.cassandra.ids.CassandraMessageId;
-import org.apache.james.mailbox.model.MessageId;
+import org.apache.james.StartUpChecksPerformer;
+import org.apache.james.jmap.JMAPModule.DelaySendsStartUpCheck;
+import org.apache.james.jmap.rfc8621.contract.tags.CategoryTags;
 import org.apache.james.modules.AwsS3BlobStoreExtension;
 import org.apache.james.modules.RabbitMQExtension;
 import org.apache.james.modules.TestJMAPServerModule;
 import org.apache.james.modules.blobstore.BlobStoreConfiguration;
-import org.apache.james.utils.UpdatableTickingClock;
-import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
-import com.datastax.oss.driver.api.core.uuid.Uuids;
-import com.google.inject.name.Names;
-
-public class DistributedEmailSubmissionSetMethodFutureReleaseTest implements EmailSubmissionSetMethodFutureReleaseContract {
-    public static final CassandraMessageId.Factory MESSAGE_ID_FACTORY = new CassandraMessageId.Factory();
+import com.google.common.collect.ImmutableMap;
 
+class DistributedDelaySendsOnRabbitMQStartUpTest {
     @RegisterExtension
     static JamesServerExtension testExtension = new JamesServerBuilder<CassandraRabbitMQJamesConfiguration>(tmpDir ->
         CassandraRabbitMQJamesConfiguration.builder()
@@ -65,35 +63,18 @@ public class DistributedEmailSubmissionSetMethodFutureReleaseTest implements Ema
         .extension(new CassandraExtension())
         .extension(new RabbitMQExtension())
         .extension(new AwsS3BlobStoreExtension())
-        .extension(new ClockExtension())
         .server(configuration -> CassandraRabbitMQJamesServerMain.createServer(configuration)
-            .overrideWith(new TestJMAPServerModule(), new DelegationProbeModule()))
-        .overrideServerModule(binder -> binder.bind(Boolean.class).annotatedWith(Names.named("supportsDelaySends")).toInstance(true))
-        .lifeCycle(JamesServerExtension.Lifecycle.PER_CLASS)
+            .overrideWith(new TestJMAPServerModule(ImmutableMap.of("delay.sends.enabled", true))))
+        .disableAutoStart()
         .build();
 
-    @Override
-    public MessageId randomMessageId() {
-        return MESSAGE_ID_FACTORY.of(Uuids.timeBased());
-    }
-
-    @Disabled("Not work for distributed test")
-    @Override
-    public void emailSubmissionSetCreateShouldDeliverEmailWhenHoldForExpired(GuiceJamesServer server, UpdatableTickingClock updatableTickingClock){
-    }
-
-    @Disabled("Not work for distributed test")
-    @Override
-    public void emailSubmissionSetCreateShouldDeliverEmailWhenHoldUntilExpired(GuiceJamesServer server, UpdatableTickingClock updatableTickingClock){
-    }
-
-    @Disabled("Not work for distributed test")
-    @Override
-    public void emailSubmissionSetCreateShouldDelayEmailWithHoldFor(GuiceJamesServer server, UpdatableTickingClock updatableTickingClock){
-    }
-
-    @Disabled("Not work for distributed test")
-    @Override
-    public void emailSubmissionSetCreateShouldDelayEmailWithHoldUntil(GuiceJamesServer server, UpdatableTickingClock updatableTickingClock){
+    @Test
+    @Tag(CategoryTags.BASIC_FEATURE)
+    void serverShouldNotStartWhenDelaySendsAreEnabledOnTheRabbitMQMailQueue(GuiceJamesServer server) {
+        assertThatThrownBy(server::start)
+            .isInstanceOfSatisfying(StartUpChecksPerformer.StartUpChecksException.class,
+                e -> assertThat(e.getBadChecks())
+                    .extracting(check -> check.getName())
+                    .contains(DelaySendsStartUpCheck.CHECK_NAME));
     }
 }
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSubmissionSetMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSubmissionSetMethodContract.scala
index 879e41ffb4..f49151eff0 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSubmissionSetMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSubmissionSetMethodContract.scala
@@ -2483,4 +2483,83 @@ trait EmailSubmissionSetMethodContract {
       .isObject
       .containsKey("k1490")
   }
+
+  private def appendDraft(server: GuiceJamesServer): MessageId = {
+    val message: Message = Message.Builder
+      .of
+      .setSubject("test")
+      .setSender(bobUsername.asString)
+      .setFrom(bobUsername.asString)
+      .setTo(andreUsername.asString)
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+
+    val bobDraftsPath = MailboxPath.forUser(bobUsername, DefaultMailboxes.DRAFTS)
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobDraftsPath)
+    server.getProbe(classOf[MailboxProbeImpl]).appendMessage(bobUsername.asString(), bobDraftsPath, AppendCommand.builder()
+      .build(message))
+      .getMessageId
+  }
+
+  private def post(request: String): String =
+    `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(request)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+  private def creationWithParameters(messageId: MessageId, parameters: String): String =
+    s"""{
+       |  "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail", "urn:ietf:params:jmap:submission"],
+       |  "methodCalls": [
+       |     ["EmailSubmission/set", {
+       |       "accountId": "$bobAccountId",
+       |       "create": {
+       |         "k1490": {
+       |           "emailId": "${messageId.serialize}",
+       |           "envelope": {
+       |             "mailFrom": {"email": "${bobUsername.asString}", "parameters": $parameters},
+       |             "rcptTo": [{"email": "${andreUsername.asString}"}]
+       |           }
+       |         }
+       |    }
+       |  }, "c1"]]
+       |}""".stripMargin
+
+  @Test
+  def holdForShouldBeRejectedWhenDelayedSendsAreNotSupported(server: GuiceJamesServer): Unit = {
+    val response = post(creationWithParameters(appendDraft(server), """{"holdFor": "20"}"""))
+
+    assertThatJson(response)
+      .inPath("methodResponses[0][1].notCreated.k1490")
+      .isEqualTo("""{
+                   |  "type": "invalidArguments",
+                   |  "description": "Delayed sends are not supported"
+                   |}""".stripMargin)
+  }
+
+  @Test
+  def holdUntilShouldBeRejectedWhenDelayedSendsAreNotSupported(server: GuiceJamesServer): Unit = {
+    val response = post(creationWithParameters(appendDraft(server), """{"holdUntil": "2100-01-01T00:00:00Z"}"""))
+
+    assertThatJson(response)
+      .inPath("methodResponses[0][1].notCreated.k1490.type")
+      .isEqualTo("invalidArguments")
+  }
+
+  @Test
+  def zeroHoldForShouldBeAcceptedWhenDelayedSendsAreNotSupported(server: GuiceJamesServer): Unit = {
+    val response = post(creationWithParameters(appendDraft(server), """{"holdFor": "0"}"""))
+
+    assertThatJson(response)
+      .inPath("methodResponses[0][1].created")
+      .isObject
+      .containsKey("k1490")
+  }
 }
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/postgres-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/postgres/PostgresEmailSubmissionSetMethodFutureReleaseTest.java b/server/protocols/jmap-rfc-8621-integration-tests/postgres-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/postgres/PostgresDelaySendsOnRabbitMQStartUpTest.java
similarity index 58%
rename from server/protocols/jmap-rfc-8621-integration-tests/postgres-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/postgres/PostgresEmailSubmissionSetMethodFutureReleaseTest.java
rename to server/protocols/jmap-rfc-8621-integration-tests/postgres-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/postgres/PostgresDelaySendsOnRabbitMQStartUpTest.java
index 128ade1373..18877c7cab 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/postgres-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/postgres/PostgresEmailSubmissionSetMethodFutureReleaseTest.java
+++ b/server/protocols/jmap-rfc-8621-integration-tests/postgres-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/postgres/PostgresDelaySendsOnRabbitMQStartUpTest.java
@@ -20,31 +20,29 @@
 package org.apache.james.jmap.rfc8621.postgres;
 
 import static org.apache.james.data.UsersRepositoryModuleChooser.Implementation.DEFAULT;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
-import org.apache.james.ClockExtension;
 import org.apache.james.GuiceJamesServer;
 import org.apache.james.JamesServerBuilder;
 import org.apache.james.JamesServerExtension;
 import org.apache.james.PostgresJamesConfiguration;
 import org.apache.james.PostgresJamesServerMain;
 import org.apache.james.SearchConfiguration;
+import org.apache.james.StartUpChecksPerformer;
 import org.apache.james.backends.postgres.PostgresExtension;
-import org.apache.james.jmap.rfc8621.contract.EmailSubmissionSetMethodFutureReleaseContract;
-import org.apache.james.jmap.rfc8621.contract.probe.DelegationProbeModule;
-import org.apache.james.mailbox.model.MessageId;
-import org.apache.james.mailbox.postgres.PostgresMessageId;
+import org.apache.james.jmap.JMAPModule.DelaySendsStartUpCheck;
+import org.apache.james.jmap.rfc8621.contract.tags.CategoryTags;
 import org.apache.james.modules.RabbitMQExtension;
 import org.apache.james.modules.TestJMAPServerModule;
 import org.apache.james.modules.blobstore.BlobStoreConfiguration;
-import org.apache.james.utils.UpdatableTickingClock;
-import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
-import com.google.inject.name.Names;
-
-public class PostgresEmailSubmissionSetMethodFutureReleaseTest implements EmailSubmissionSetMethodFutureReleaseContract {
-    public static final PostgresMessageId.Factory MESSAGE_ID_FACTORY = new PostgresMessageId.Factory();
+import com.google.common.collect.ImmutableMap;
 
+class PostgresDelaySendsOnRabbitMQStartUpTest {
     @RegisterExtension
     static JamesServerExtension testExtension = new JamesServerBuilder<PostgresJamesConfiguration>(tmpDir ->
         PostgresJamesConfiguration.builder()
@@ -61,36 +59,18 @@ public class PostgresEmailSubmissionSetMethodFutureReleaseTest implements EmailS
             .build())
         .extension(PostgresExtension.empty())
         .extension(new RabbitMQExtension())
-        .extension(new ClockExtension())
         .server(configuration -> PostgresJamesServerMain.createServer(configuration)
-            .overrideWith(new TestJMAPServerModule())
-            .overrideWith(new DelegationProbeModule()))
-        .overrideServerModule(binder -> binder.bind(Boolean.class).annotatedWith(Names.named("supportsDelaySends")).toInstance(true))
-        .lifeCycle(JamesServerExtension.Lifecycle.PER_CLASS)
+            .overrideWith(new TestJMAPServerModule(ImmutableMap.of("delay.sends.enabled", true))))
+        .disableAutoStart()
         .build();
 
-    @Override
-    public MessageId randomMessageId() {
-        return MESSAGE_ID_FACTORY.generate();
-    }
-
-    @Disabled("Not work for postgres test")
-    @Override
-    public void emailSubmissionSetCreateShouldDeliverEmailWhenHoldForExpired(GuiceJamesServer server, UpdatableTickingClock updatableTickingClock){
-    }
-
-    @Disabled("Not work for postgres test")
-    @Override
-    public void emailSubmissionSetCreateShouldDeliverEmailWhenHoldUntilExpired(GuiceJamesServer server, UpdatableTickingClock updatableTickingClock){
-    }
-
-    @Disabled("Not work for postgres test")
-    @Override
-    public void emailSubmissionSetCreateShouldDelayEmailWithHoldFor(GuiceJamesServer server, UpdatableTickingClock updatableTickingClock){
-    }
-
-    @Disabled("Not work for postgres test")
-    @Override
-    public void emailSubmissionSetCreateShouldDelayEmailWithHoldUntil(GuiceJamesServer server, UpdatableTickingClock updatableTickingClock){
+    @Test
+    @Tag(CategoryTags.BASIC_FEATURE)
+    void serverShouldNotStartWhenDelaySendsAreEnabledOnTheRabbitMQMailQueue(GuiceJamesServer server) {
+        assertThatThrownBy(server::start)
+            .isInstanceOfSatisfying(StartUpChecksPerformer.StartUpChecksException.class,
+                e -> assertThat(e.getBadChecks())
+                    .extracting(check -> check.getName())
+                    .contains(DelaySendsStartUpCheck.CHECK_NAME));
     }
 }
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSubmissionSetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSubmissionSetMethod.scala
index 716e90a334..9cc0cca669 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSubmissionSetMethod.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSubmissionSetMethod.scala
@@ -28,7 +28,7 @@ import com.google.common.collect.ImmutableMap
 import eu.timepit.refined.auto._
 import eu.timepit.refined.refineV
 import jakarta.annotation.PreDestroy
-import jakarta.inject.Inject
+import jakarta.inject.{Inject, Named}
 import jakarta.mail.Address
 import jakarta.mail.Message.RecipientType
 import jakarta.mail.internet.{InternetAddress, MimeMessage}
@@ -182,6 +182,7 @@ class EmailSubmissionSetMethod @Inject()(serializer: EmailSubmissionSetSerialize
                                          emailSetMethod: EmailSetMethod,
                                          clock: Clock,
                                          javaValidations: java.util.Set[EmailSubmissionSetValidation],
+                                         @Named("supportsDelaySends") supportsDelaySends: Boolean,
                                          val metricFactory: MetricFactory,
                                          val sessionSupplier: SessionSupplier,
                                          val sessionTranslator: SessionTranslator) extends MethodRequiringAccountId[EmailSubmissionSetRequest] with Startable {
@@ -375,10 +376,12 @@ class EmailSubmissionSetMethod @Inject()(serializer: EmailSubmissionSetSerialize
 
 
   def validateDelay(delay: Duration): Try[Duration] =
-    if (delay.getSeconds >= 0 && delay.getSeconds <= SubmissionCapabilityFactory.maximumDelays.getSeconds) {
-      Success(delay)
-    } else {
+    if (delay.getSeconds < 0 || delay.getSeconds > SubmissionCapabilityFactory.maximumDelays.getSeconds) {
       Failure(new IllegalArgumentException("Invalid delayed time!"))
+    } else if (!delay.isZero && !supportsDelaySends) {
+      Failure(new IllegalArgumentException("Delayed sends are not supported"))
+    } else {
+      Success(delay)
     }
 
   def validateMimeMessages(mimeMessage: MimeMessage) : SMono[MimeMessage] = validateMailAddressHeaderMimeMessage(mimeMessage)


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org