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

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

commit a8f8bc2fce003b4f9050269f90b174566a626e36
Author: Rene Cordier <[email protected]>
AuthorDate: Mon Sep 22 16:49:43 2025 +0700

    JAMES-4148 Webadmin route to run a filtering rule on a mailbox of a user
---
 .../server/JmapTaskSerializationModule.java        |  19 +
 .../james/modules/server/JmapTasksModule.java      |   4 +
 .../james/jmap/mailet/filter/RuleMatcher.java      |   6 +-
 ...dminServerTaskSerializationIntegrationTest.java |  59 +++
 server/protocols/webadmin/webadmin-jmap/pom.xml    |   5 +-
 .../data/jmap/RunRulesOnMailboxRoutes.java         | 156 +++++++
 .../data/jmap/RunRulesOnMailboxService.java        | 126 ++++++
 .../webadmin/data/jmap/RunRulesOnMailboxTask.java  | 207 +++++++++
 ...RulesOnMailboxTaskAdditionalInformationDTO.java | 106 +++++
 .../data/jmap/RunRulesOnMailboxTaskDTO.java        |  84 ++++
 .../data/jmap/RunRulesOnMailboxRoutesTest.java     | 480 +++++++++++++++++++++
 ...OnMailboxTaskAdditionalInformationDTOTest.java} |  45 +-
 .../RunRulesOnMailboxTaskSerializationTest.java    |  65 +++
 .../runRulesOnMailbox.additionalInformation.json   |   8 +
 .../resources/json/runRulesOnMailbox.task.json     |  35 ++
 .../webadmin/service/UserMailboxesService.java     |   1 -
 .../UserMailboxesRoutesNoIndexationTest.java       |   3 +-
 .../routes/UserRoutesWithMailboxParamTest.java     |   3 +-
 18 files changed, 1377 insertions(+), 35 deletions(-)

diff --git 
a/server/container/guice/protocols/webadmin-jmap/src/main/java/org/apache/james/modules/server/JmapTaskSerializationModule.java
 
b/server/container/guice/protocols/webadmin-jmap/src/main/java/org/apache/james/modules/server/JmapTaskSerializationModule.java
index c7233e1c28..c1c648cc71 100644
--- 
a/server/container/guice/protocols/webadmin-jmap/src/main/java/org/apache/james/modules/server/JmapTaskSerializationModule.java
+++ 
b/server/container/guice/protocols/webadmin-jmap/src/main/java/org/apache/james/modules/server/JmapTaskSerializationModule.java
@@ -38,6 +38,9 @@ import 
org.apache.james.webadmin.data.jmap.RecomputeAllFastViewProjectionItemsTa
 import 
org.apache.james.webadmin.data.jmap.RecomputeAllFastViewTaskAdditionalInformationDTO;
 import 
org.apache.james.webadmin.data.jmap.RecomputeUserFastViewProjectionItemsTask;
 import 
org.apache.james.webadmin.data.jmap.RecomputeUserFastViewTaskAdditionalInformationDTO;
+import org.apache.james.webadmin.data.jmap.RunRulesOnMailboxService;
+import 
org.apache.james.webadmin.data.jmap.RunRulesOnMailboxTaskAdditionalInformationDTO;
+import org.apache.james.webadmin.data.jmap.RunRulesOnMailboxTaskDTO;
 import org.apache.james.webadmin.dto.DTOModuleInjections;
 
 import com.google.inject.AbstractModule;
@@ -76,6 +79,11 @@ public class JmapTaskSerializationModule extends 
AbstractModule {
         return RecomputeUserFastViewProjectionItemsTask.module(corrector);
     }
 
+    @ProvidesIntoSet
+    public TaskDTOModule<? extends Task, ? extends TaskDTO> 
runRulesOnMailboxTask(RunRulesOnMailboxService runRulesOnMailboxService) {
+        return RunRulesOnMailboxTaskDTO.module(runRulesOnMailboxService);
+    }
+
     @ProvidesIntoSet
     public AdditionalInformationDTOModule<? extends 
TaskExecutionDetails.AdditionalInformation, ? extends  
AdditionalInformationDTO> recomputeAllJmapPreviewsAdditionalInformation() {
         return RecomputeAllFastViewTaskAdditionalInformationDTO.module();
@@ -119,4 +127,15 @@ public class JmapTaskSerializationModule extends 
AbstractModule {
     public AdditionalInformationDTOModule<? extends 
TaskExecutionDetails.AdditionalInformation, ? extends  
AdditionalInformationDTO> 
webAdminRecomputeUserJmapPreviewsAdditionalInformation() {
         return RecomputeUserFastViewTaskAdditionalInformationDTO.module();
     }
+
+    @ProvidesIntoSet
+    public AdditionalInformationDTOModule<? extends 
TaskExecutionDetails.AdditionalInformation, ? extends  
AdditionalInformationDTO> runRulesOnMailboxAdditionalInformation() {
+        return 
RunRulesOnMailboxTaskAdditionalInformationDTO.SERIALIZATION_MODULE;
+    }
+
+    @Named(DTOModuleInjections.WEBADMIN_DTO)
+    @ProvidesIntoSet
+    public AdditionalInformationDTOModule<? extends 
TaskExecutionDetails.AdditionalInformation, ? extends  
AdditionalInformationDTO> webAdminRunRulesOnMailboxAdditionalInformation() {
+        return 
RunRulesOnMailboxTaskAdditionalInformationDTO.SERIALIZATION_MODULE;
+    }
 }
diff --git 
a/server/container/guice/protocols/webadmin-jmap/src/main/java/org/apache/james/modules/server/JmapTasksModule.java
 
b/server/container/guice/protocols/webadmin-jmap/src/main/java/org/apache/james/modules/server/JmapTasksModule.java
index c035925e66..08a91c9e09 100644
--- 
a/server/container/guice/protocols/webadmin-jmap/src/main/java/org/apache/james/modules/server/JmapTasksModule.java
+++ 
b/server/container/guice/protocols/webadmin-jmap/src/main/java/org/apache/james/modules/server/JmapTasksModule.java
@@ -19,10 +19,12 @@
 
 package org.apache.james.modules.server;
 
+import org.apache.james.webadmin.Routes;
 import org.apache.james.webadmin.data.jmap.PopulateEmailQueryViewRequestToTask;
 import 
org.apache.james.webadmin.data.jmap.PopulateFilteringProjectionRequestToTask;
 import 
org.apache.james.webadmin.data.jmap.RecomputeAllFastViewProjectionItemsRequestToTask;
 import 
org.apache.james.webadmin.data.jmap.RecomputeUserFastViewProjectionItemsRequestToTask;
+import org.apache.james.webadmin.data.jmap.RunRulesOnMailboxRoutes;
 import org.apache.james.webadmin.routes.MailboxesRoutes;
 import org.apache.james.webadmin.routes.UserMailboxesRoutes;
 import org.apache.james.webadmin.tasks.TaskFromRequestRegistry;
@@ -48,5 +50,7 @@ public class JmapTasksModule extends AbstractModule {
         Multibinder.newSetBinder(binder(), 
TaskFromRequestRegistry.TaskRegistration.class, 
Names.named(UserMailboxesRoutes.USER_MAILBOXES_OPERATIONS_INJECTION_KEY))
             
.addBinding().to(RecomputeUserFastViewProjectionItemsRequestToTask.class);
 
+        Multibinder<Routes> routesMultiBinder = 
Multibinder.newSetBinder(binder(), Routes.class);
+        routesMultiBinder.addBinding().to(RunRulesOnMailboxRoutes.class);
     }
 }
diff --git 
a/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/mailet/filter/RuleMatcher.java
 
b/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/mailet/filter/RuleMatcher.java
index 4e59fb4611..c98ec1de8c 100644
--- 
a/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/mailet/filter/RuleMatcher.java
+++ 
b/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/mailet/filter/RuleMatcher.java
@@ -29,10 +29,10 @@ import org.apache.mailet.Mail;
 
 import com.google.common.base.Preconditions;
 
-class RuleMatcher {
+public class RuleMatcher {
     private final List<Rule> filteringRules;
 
-    RuleMatcher(List<Rule> filteringRules) {
+    public RuleMatcher(List<Rule> filteringRules) {
         Preconditions.checkNotNull(filteringRules);
 
         this.filteringRules = filteringRules;
@@ -45,7 +45,7 @@ class RuleMatcher {
             .filter(rule -> MailMatcher.from(rule).match(filteringHeaders));
     }
 
-    Stream<Rule> findApplicableRules(MessageResult messageResult) throws 
MailboxException {
+    public Stream<Rule> findApplicableRules(MessageResult messageResult) 
throws MailboxException {
         FilteringHeaders filteringHeaders = new 
FilteringHeaders.MessageResultFilteringHeaders(messageResult);
 
         return filteringRules.stream()
diff --git 
a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQWebAdminServerTaskSerializationIntegrationTest.java
 
b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQWebAdminServerTaskSerializationIntegrationTest.java
index 6378713a4e..cc5933b40d 100644
--- 
a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQWebAdminServerTaskSerializationIntegrationTest.java
+++ 
b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQWebAdminServerTaskSerializationIntegrationTest.java
@@ -76,6 +76,7 @@ import org.apache.james.utils.MailRepositoryProbeImpl;
 import org.apache.james.utils.WebAdminGuiceProbe;
 import org.apache.james.vault.VaultConfiguration;
 import org.apache.james.webadmin.WebAdminUtils;
+import org.apache.james.webadmin.data.jmap.RunRulesOnMailboxTask;
 import org.apache.james.webadmin.routes.CassandraMailboxMergingRoutes;
 import org.apache.james.webadmin.routes.MailQueueRoutes;
 import org.apache.james.webadmin.routes.MailRepositoriesRoutes;
@@ -715,6 +716,64 @@ class 
RabbitMQWebAdminServerTaskSerializationIntegrationTest {
             .body("additionalInformation.mailboxName", 
is(MailboxConstants.INBOX));
     }
 
+    @Test
+    void runRulesOnMailboxShouldComplete(GuiceJamesServer server) throws 
Exception {
+        server.getProbe(DataProbeImpl.class).addUser(USERNAME, "secret");
+        mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME, 
MailboxConstants.INBOX);
+        MailboxId otherMailboxId = 
mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME, 
"otherMailbox");
+
+        mailboxProbe.appendMessage(
+            USERNAME,
+            MailboxPath.inbox(Username.of(USERNAME)),
+            new ByteArrayInputStream("Subject: 
test\r\n\r\ntestmail".getBytes()),
+            new Date(),
+            false,
+            new Flags());
+
+        String taskId = given()
+            .queryParam("action", "triage")
+            .body("""
+            {
+              "id": "1",
+              "name": "rule 1",
+              "action": {
+                "appendIn": {
+                  "mailboxIds": ["%s"]
+                },
+                "important": false,
+                "keyworkds": [],
+                "reject": false,
+                "seen": false
+              },
+              "conditionGroup": {
+                "conditionCombiner": "AND",
+                "conditions": [
+                  {
+                    "comparator": "contains",
+                    "field": "subject",
+                    "value": "test"
+                  }
+                ]
+              }
+            }""".formatted(otherMailboxId.serialize()))
+            .post("users/" + USERNAME + "/mailboxes/" + MailboxConstants.INBOX 
+ "/messages")
+            .jsonPath()
+            .getString("taskId");
+
+        with()
+            .basePath(TasksRoutes.BASE)
+        .when()
+            .get(taskId + "/await")
+        .then()
+            .body("status", is(TaskManager.Status.COMPLETED.getValue()))
+            .body("taskId", is(taskId))
+            .body("type", is(RunRulesOnMailboxTask.TASK_TYPE.asString()))
+            .body("additionalInformation.rulesOnMessagesApplySuccessfully", 
is(1))
+            .body("additionalInformation.rulesOnMessagesApplyFailed", is(0))
+            .body("additionalInformation.username", is(USERNAME))
+            .body("additionalInformation.mailboxName", 
is(MailboxConstants.INBOX));
+    }
+
     @Test
     void cleanUploadRepositoryShouldComplete() throws Exception {
         String taskId = given()
diff --git a/server/protocols/webadmin/webadmin-jmap/pom.xml 
b/server/protocols/webadmin/webadmin-jmap/pom.xml
index 3ccdc223c7..9db6f16b62 100644
--- a/server/protocols/webadmin/webadmin-jmap/pom.xml
+++ b/server/protocols/webadmin/webadmin-jmap/pom.xml
@@ -69,7 +69,6 @@
         <dependency>
             <groupId>${james.groupId}</groupId>
             <artifactId>james-server-jmap-rfc-8621</artifactId>
-            <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
@@ -91,6 +90,10 @@
             <type>test-jar</type>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-webadmin-mailbox</artifactId>
+        </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
             <artifactId>metrics-tests</artifactId>
diff --git 
a/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxRoutes.java
 
b/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxRoutes.java
new file mode 100644
index 0000000000..8d183b4597
--- /dev/null
+++ 
b/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxRoutes.java
@@ -0,0 +1,156 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.webadmin.data.jmap;
+
+import static org.apache.james.webadmin.Constants.SEPARATOR;
+
+import jakarta.inject.Inject;
+
+import org.apache.james.core.Username;
+import org.apache.james.jmap.api.filtering.RuleDTO;
+import org.apache.james.jmap.api.filtering.Rules;
+import org.apache.james.jmap.api.filtering.Version;
+import org.apache.james.mailbox.MailboxManager;
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.exception.MailboxException;
+import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.task.Task;
+import org.apache.james.task.TaskManager;
+import org.apache.james.user.api.UsersRepository;
+import org.apache.james.user.api.UsersRepositoryException;
+import org.apache.james.webadmin.Routes;
+import org.apache.james.webadmin.tasks.TaskFromRequestRegistry;
+import org.apache.james.webadmin.tasks.TaskRegistrationKey;
+import org.apache.james.webadmin.utils.ErrorResponder;
+import org.apache.james.webadmin.utils.JsonTransformer;
+import org.apache.james.webadmin.validation.MailboxName;
+import org.eclipse.jetty.http.HttpStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.guava.GuavaModule;
+import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+import reactor.core.publisher.Mono;
+import spark.Request;
+import spark.Route;
+import spark.Service;
+
+public class RunRulesOnMailboxRoutes implements Routes {
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(RunRulesOnMailboxRoutes.class);
+
+    private static final TaskRegistrationKey TRIAGE = 
TaskRegistrationKey.of("triage");
+    private static final String MAILBOX_NAME = ":mailboxName";
+    private static final String MAILBOXES = "mailboxes";
+    private static final String USER_NAME = ":userName";
+    private static final String USERS_BASE = "/users";
+    public static final String USER_MAILBOXES_BASE = USERS_BASE + SEPARATOR + 
USER_NAME + SEPARATOR + MAILBOXES;
+    public static final String SPECIFIC_MAILBOX = USER_MAILBOXES_BASE + 
SEPARATOR + MAILBOX_NAME;
+    public static final String MESSAGES_PATH = SPECIFIC_MAILBOX + "/messages";
+
+    private final UsersRepository usersRepository;
+    private final MailboxManager mailboxManager;
+    private final RunRulesOnMailboxService runRulesOnMailboxService;
+    private final JsonTransformer jsonTransformer;
+    private final TaskManager taskManager;
+    private final ObjectMapper jsonDeserialize;
+
+    @Inject
+    RunRulesOnMailboxRoutes(UsersRepository usersRepository,
+                            MailboxManager mailboxManager,
+                            TaskManager taskManager,
+                            JsonTransformer jsonTransformer,
+                            RunRulesOnMailboxService runRulesOnMailboxService) 
{
+        this.usersRepository = usersRepository;
+        this.mailboxManager = mailboxManager;
+        this.taskManager = taskManager;
+        this.jsonTransformer = jsonTransformer;
+        this.runRulesOnMailboxService = runRulesOnMailboxService;
+        this.jsonDeserialize = new ObjectMapper()
+            .registerModule(new Jdk8Module())
+            .registerModule(new GuavaModule());
+    }
+
+    @Override
+    public String getBasePath() {
+        return USER_MAILBOXES_BASE;
+    }
+
+    @Override
+    public void define(Service service) {
+        service.post(MESSAGES_PATH, runRulesOnMailboxRoute(), jsonTransformer);
+    }
+
+    public Route runRulesOnMailboxRoute() {
+        return TaskFromRequestRegistry.builder()
+            .parameterName("action")
+            .register(TRIAGE, this::runRulesOnMailbox)
+            .buildAsRoute(taskManager);
+    }
+
+    public Task runRulesOnMailbox(Request request) throws 
UsersRepositoryException, MailboxException {
+        Username username = getUsernameParam(request);
+        MailboxName mailboxName = new 
MailboxName(request.params(MAILBOX_NAME));
+        try {
+            usernamePreconditions(username);
+            mailboxExistPreconditions(username, mailboxName);
+            RuleDTO ruleDTO = jsonDeserialize.readValue(request.body(), 
RuleDTO.class);
+            Rules rules = new 
Rules(RuleDTO.toRules(ImmutableList.of(ruleDTO)), Version.INITIAL);
+
+            return new RunRulesOnMailboxTask(username, mailboxName, rules, 
runRulesOnMailboxService);
+        } catch (IllegalStateException e) {
+            LOGGER.info("Invalid argument on user mailboxes", e);
+            throw ErrorResponder.builder()
+                .statusCode(HttpStatus.NOT_FOUND_404)
+                .type(ErrorResponder.ErrorType.NOT_FOUND)
+                .message("Invalid argument on user mailboxes")
+                .cause(e)
+                .haltError();
+        } catch (JsonProcessingException e) {
+            throw ErrorResponder.builder()
+                .statusCode(HttpStatus.BAD_REQUEST_400)
+                .type(ErrorResponder.ErrorType.INVALID_ARGUMENT)
+                .message("JSON payload of the request is not valid")
+                .cause(e)
+                .haltError();
+        }
+    }
+
+    private Username getUsernameParam(Request request) {
+        return Username.of(request.params(USER_NAME));
+    }
+
+    private void usernamePreconditions(Username username) throws 
UsersRepositoryException {
+        Preconditions.checkState(usersRepository.contains(username), "User 
does not exist");
+    }
+
+    private void mailboxExistPreconditions(Username username, MailboxName 
mailboxName) throws MailboxException {
+        MailboxSession mailboxSession = 
mailboxManager.createSystemSession(username);
+        MailboxPath mailboxPath = MailboxPath.forUser(username, 
mailboxName.asString())
+            .assertAcceptable(mailboxSession.getPathDelimiter());
+        
Preconditions.checkState(Boolean.TRUE.equals(Mono.from(mailboxManager.mailboxExists(mailboxPath,
 mailboxSession)).block()),
+            "Mailbox does not exist. " + mailboxPath.asString());
+        mailboxManager.endProcessingRequest(mailboxSession);
+    }
+}
diff --git 
a/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxService.java
 
b/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxService.java
new file mode 100644
index 0000000000..0a5190dd03
--- /dev/null
+++ 
b/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxService.java
@@ -0,0 +1,126 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.webadmin.data.jmap;
+
+import static org.apache.james.util.ReactorUtils.DEFAULT_CONCURRENCY;
+
+import java.util.List;
+
+import jakarta.inject.Inject;
+
+import org.apache.commons.lang3.NotImplementedException;
+import org.apache.james.core.Username;
+import org.apache.james.jmap.api.filtering.Rule;
+import org.apache.james.jmap.api.filtering.Rules;
+import org.apache.james.jmap.mailet.filter.RuleMatcher;
+import org.apache.james.mailbox.MailboxManager;
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.MessageIdManager;
+import org.apache.james.mailbox.exception.MailboxException;
+import org.apache.james.mailbox.model.FetchGroup;
+import org.apache.james.mailbox.model.MailboxId;
+import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.mailbox.model.MessageId;
+import org.apache.james.mailbox.model.MessageRange;
+import org.apache.james.mailbox.model.MessageResult;
+import org.apache.james.task.Task;
+import org.apache.james.webadmin.validation.MailboxName;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.github.fge.lambdas.Throwing;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+public class RunRulesOnMailboxService {
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(RunRulesOnMailboxService.class);
+
+    private final MailboxManager mailboxManager;
+    private final MailboxId.Factory mailboxIdFactory;
+    private final MessageIdManager messageIdManager;
+
+    @Inject
+    public RunRulesOnMailboxService(MailboxManager mailboxManager, 
MailboxId.Factory mailboxIdFactory, MessageIdManager messageIdManager) {
+        this.mailboxManager = mailboxManager;
+        this.mailboxIdFactory = mailboxIdFactory;
+        this.messageIdManager = messageIdManager;
+    }
+
+    public Mono<Task.Result> runRulesOnMailbox(Username username, MailboxName 
mailboxName, Rules rules, RunRulesOnMailboxTask.Context context) {
+        MailboxSession mailboxSession = 
mailboxManager.createSystemSession(username);
+        RuleMatcher ruleMatcher = new RuleMatcher(rules.getRules());
+
+        return 
Mono.from(mailboxManager.getMailboxReactive(MailboxPath.forUser(username, 
mailboxName.asString()), mailboxSession))
+            .flatMapMany(messageManager -> 
Flux.from(messageManager.getMessagesReactive(MessageRange.all(), 
FetchGroup.HEADERS, mailboxSession))
+                .flatMap(Throwing.function(messageResult -> 
runRulesOnMessage(ruleMatcher, messageResult, mailboxSession, context)), 
DEFAULT_CONCURRENCY))
+            .onErrorResume(e -> {
+                LOGGER.error("Error when applying rules to mailbox. Mailbox {} 
for user {}", mailboxName.asString(), username, e);
+                context.incrementFails();
+                return Mono.just(Task.Result.PARTIAL);
+            })
+            .reduce(Task::combine)
+            .switchIfEmpty(Mono.just(Task.Result.COMPLETED))
+            .doFinally(any -> 
mailboxManager.endProcessingRequest(mailboxSession));
+    }
+
+    private Flux<Task.Result> runRulesOnMessage(RuleMatcher ruleMatcher, 
MessageResult messageResult, MailboxSession mailboxSession, 
RunRulesOnMailboxTask.Context context) throws MailboxException {
+        return Flux.fromStream(ruleMatcher.findApplicableRules(messageResult))
+            .map(Rule::getAction)
+            .concatMap(action -> applyActionOnMessage(messageResult, action, 
mailboxSession, context));
+    }
+
+    private Mono<Task.Result> applyActionOnMessage(MessageResult 
messageResult, Rule.Action action, MailboxSession mailboxSession, 
RunRulesOnMailboxTask.Context context) {
+        actionOnMessagePreconditions(action);
+        return appendInMailboxes(messageResult.getMessageId(), action, 
mailboxSession, context)
+            .onErrorResume(e -> {
+                LOGGER.error("Error when moving message to mailboxes. Message 
{} for user {}", messageResult.getMessageId(), 
mailboxSession.getUser().asString(), e);
+                context.incrementFails();
+                return Mono.just(Task.Result.PARTIAL);
+            });
+    }
+
+    private void actionOnMessagePreconditions(Rule.Action action) {
+        if (action.isMarkAsSeen() || action.isMarkAsImportant() || 
action.isReject()
+            || action.getForward().isPresent() || 
!action.getWithKeywords().isEmpty()) {
+            throw new NotImplementedException("Only action on moving messages 
is supported for now");
+        }
+
+        if (action.getAppendInMailboxes().getMailboxIds().isEmpty()) {
+            throw new IllegalArgumentException("Move action should not be 
empty");
+        }
+    }
+
+    private Mono<Task.Result> appendInMailboxes(MessageId messageId, 
Rule.Action action, MailboxSession mailboxSession, 
RunRulesOnMailboxTask.Context context) {
+        List<MailboxId> mailboxIds = action.getAppendInMailboxes()
+            .getMailboxIds()
+            .stream()
+            .map(mailboxIdFactory::fromString)
+            .toList();
+
+        if (mailboxIds.isEmpty()) {
+            return Mono.just(Task.Result.COMPLETED);
+        }
+
+        return Mono.from(messageIdManager.setInMailboxesReactive(messageId, 
mailboxIds, mailboxSession))
+            .doOnSuccess(next -> context.incrementSuccesses())
+            .then(Mono.just(Task.Result.COMPLETED));
+    }
+}
diff --git 
a/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxTask.java
 
b/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxTask.java
new file mode 100644
index 0000000000..0b3f30be1d
--- /dev/null
+++ 
b/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxTask.java
@@ -0,0 +1,207 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.webadmin.data.jmap;
+
+import java.time.Clock;
+import java.time.Instant;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.james.core.Username;
+import org.apache.james.jmap.api.filtering.Rules;
+import org.apache.james.task.Task;
+import org.apache.james.task.TaskExecutionDetails;
+import org.apache.james.task.TaskType;
+import org.apache.james.webadmin.validation.MailboxName;
+
+import com.google.common.base.MoreObjects;
+
+public class RunRulesOnMailboxTask implements Task {
+    public static class Context {
+        public static class Snapshot {
+            private final long rulesOnMessagesApplySuccessfully;
+            private final long rulesOnMessagesApplyFailed;
+
+            private Snapshot(long rulesOnMessagesApplySuccessfully, long 
rulesOnMessagesApplyFailed) {
+                this.rulesOnMessagesApplySuccessfully = 
rulesOnMessagesApplySuccessfully;
+                this.rulesOnMessagesApplyFailed = rulesOnMessagesApplyFailed;
+            }
+
+            public long getRulesOnMessagesApplySuccessfully() {
+                return rulesOnMessagesApplySuccessfully;
+            }
+
+            public long getRulesOnMessagesApplyFailed() {
+                return rulesOnMessagesApplyFailed;
+            }
+
+            @Override
+            public final boolean equals(Object o) {
+                if (o instanceof Context.Snapshot) {
+                    Context.Snapshot that = (Context.Snapshot) o;
+
+                    return 
Objects.equals(this.rulesOnMessagesApplySuccessfully, 
that.rulesOnMessagesApplySuccessfully)
+                        && Objects.equals(this.rulesOnMessagesApplyFailed, 
that.rulesOnMessagesApplyFailed);
+                }
+                return false;
+            }
+
+            @Override
+            public final int hashCode() {
+                return Objects.hash(rulesOnMessagesApplySuccessfully, 
rulesOnMessagesApplyFailed);
+            }
+
+            @Override
+            public String toString() {
+                return MoreObjects.toStringHelper(this)
+                    .add("rulesOnMessagesApplySuccessfully", 
rulesOnMessagesApplySuccessfully)
+                    .add("rulesOnMessagesApplyFailed", 
rulesOnMessagesApplyFailed)
+                    .toString();
+            }
+        }
+
+        private final AtomicLong rulesOnMessagesApplySuccessfully;
+        private final AtomicLong rulesOnMessagesApplyFailed;
+
+        public Context() {
+            this.rulesOnMessagesApplySuccessfully = new AtomicLong();
+            this.rulesOnMessagesApplyFailed = new AtomicLong();
+        }
+
+        public Context(long rulesOnMessagesApplySuccessfully, long 
rulesOnMessagesApplyFailed) {
+            this.rulesOnMessagesApplySuccessfully = new 
AtomicLong(rulesOnMessagesApplySuccessfully);
+            this.rulesOnMessagesApplyFailed = new 
AtomicLong(rulesOnMessagesApplyFailed);
+        }
+
+        public void incrementSuccesses() {
+            rulesOnMessagesApplySuccessfully.incrementAndGet();
+        }
+
+
+        public void incrementFails() {
+            rulesOnMessagesApplyFailed.incrementAndGet();
+        }
+
+        public Context.Snapshot snapshot() {
+            return new 
Context.Snapshot(rulesOnMessagesApplySuccessfully.get(), 
rulesOnMessagesApplyFailed.get());
+        }
+    }
+
+    public static class AdditionalInformation implements 
TaskExecutionDetails.AdditionalInformation {
+
+        private static AdditionalInformation from(Username username,
+                                                                        
MailboxName mailboxName,
+                                                                        
RunRulesOnMailboxTask.Context context) {
+            Context.Snapshot snapshot = context.snapshot();
+            return new AdditionalInformation(username, mailboxName, 
Clock.systemUTC().instant(), snapshot.rulesOnMessagesApplySuccessfully, 
snapshot.rulesOnMessagesApplyFailed);
+        }
+
+        private final Username username;
+        private final MailboxName mailboxName;
+        private final Instant timestamp;
+        private final long rulesOnMessagesApplySuccessfully;
+        private final long rulesOnMessagesApplyFailed;
+
+        public AdditionalInformation(Username username,
+                                     MailboxName mailboxName,
+                                     Instant timestamp,
+                                     long rulesOnMessagesApplySuccessfully,
+                                     long rulesOnMessagesApplyFailed) {
+            this.username = username;
+            this.mailboxName = mailboxName;
+            this.timestamp = timestamp;
+            this.rulesOnMessagesApplySuccessfully = 
rulesOnMessagesApplySuccessfully;
+            this.rulesOnMessagesApplyFailed = rulesOnMessagesApplyFailed;
+        }
+
+        public Username getUsername() {
+            return username;
+        }
+
+        public MailboxName getMailboxName() {
+            return mailboxName;
+        }
+
+        public Instant getTimestamp() {
+            return timestamp;
+        }
+
+        public long getRulesOnMessagesApplySuccessfully() {
+            return rulesOnMessagesApplySuccessfully;
+        }
+
+        public long getRulesOnMessagesApplyFailed() {
+            return rulesOnMessagesApplyFailed;
+        }
+
+        @Override
+        public Instant timestamp() {
+            return timestamp;
+        }
+    }
+
+    public static final TaskType TASK_TYPE = 
TaskType.of("RunRulesOnMailboxTask");
+
+    private final Context context;
+    private final Username username;
+    private final MailboxName mailboxName;
+    private final Rules rules;
+    private final RunRulesOnMailboxService runRulesOnMailboxService;
+
+    public RunRulesOnMailboxTask(Username username,
+                                 MailboxName mailboxName,
+                                 Rules rules,
+                                 RunRulesOnMailboxService 
runRulesOnMailboxService) {
+        this.username = username;
+        this.mailboxName = mailboxName;
+        this.rules = rules;
+        this.runRulesOnMailboxService = runRulesOnMailboxService;
+        this.context = new Context();
+    }
+
+    @Override
+    public Result run() {
+        return runRulesOnMailboxService.runRulesOnMailbox(username, 
mailboxName, rules, context)
+            .block();
+    }
+
+    @Override
+    public TaskType type() {
+        return TASK_TYPE;
+    }
+
+    @Override
+    public Optional<TaskExecutionDetails.AdditionalInformation> details() {
+        return Optional.of(AdditionalInformation.from(username, mailboxName, 
context));
+    }
+
+    public Username getUsername() {
+        return username;
+    }
+
+    public MailboxName getMailboxName() {
+        return mailboxName;
+    }
+
+    public Rules getRules() {
+        return rules;
+    }
+}
diff --git 
a/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxTaskAdditionalInformationDTO.java
 
b/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxTaskAdditionalInformationDTO.java
new file mode 100644
index 0000000000..a3b101f23e
--- /dev/null
+++ 
b/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxTaskAdditionalInformationDTO.java
@@ -0,0 +1,106 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.webadmin.data.jmap;
+
+import java.time.Instant;
+
+import org.apache.james.core.Username;
+import org.apache.james.json.DTOModule;
+import org.apache.james.server.task.json.dto.AdditionalInformationDTO;
+import org.apache.james.server.task.json.dto.AdditionalInformationDTOModule;
+import org.apache.james.webadmin.validation.MailboxName;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class RunRulesOnMailboxTaskAdditionalInformationDTO implements 
AdditionalInformationDTO {
+    public static final 
AdditionalInformationDTOModule<RunRulesOnMailboxTask.AdditionalInformation, 
RunRulesOnMailboxTaskAdditionalInformationDTO> SERIALIZATION_MODULE =
+        
DTOModule.forDomainObject(RunRulesOnMailboxTask.AdditionalInformation.class)
+            .convertToDTO(RunRulesOnMailboxTaskAdditionalInformationDTO.class)
+            
.toDomainObjectConverter(RunRulesOnMailboxTaskAdditionalInformationDTO::toDomainObject)
+            
.toDTOConverter(RunRulesOnMailboxTaskAdditionalInformationDTO::toDto)
+            .typeName(RunRulesOnMailboxTask.TASK_TYPE.asString())
+            .withFactory(AdditionalInformationDTOModule::new);
+
+    private static RunRulesOnMailboxTask.AdditionalInformation 
toDomainObject(RunRulesOnMailboxTaskAdditionalInformationDTO dto) {
+        return new RunRulesOnMailboxTask.AdditionalInformation(
+            Username.of(dto.getUsername()),
+            new MailboxName(dto.getMailboxName()),
+            dto.getTimestamp(),
+            dto.getRulesOnMessagesApplySuccessfully(),
+            dto.getRulesOnMessagesApplyFailed());
+    }
+
+    private static RunRulesOnMailboxTaskAdditionalInformationDTO 
toDto(RunRulesOnMailboxTask.AdditionalInformation domain, String type) {
+        return new RunRulesOnMailboxTaskAdditionalInformationDTO(
+            type,
+            domain.getUsername().asString(),
+            domain.getMailboxName().asString(),
+            domain.getTimestamp(),
+            domain.getRulesOnMessagesApplySuccessfully(),
+            domain.getRulesOnMessagesApplyFailed());
+    }
+
+    private final String type;
+    private final String username;
+    private final String mailboxName;
+    private final Instant timestamp;
+    private final long rulesOnMessagesApplySuccessfully;
+    private final long rulesOnMessagesApplyFailed;
+
+    public RunRulesOnMailboxTaskAdditionalInformationDTO(@JsonProperty("type") 
String type,
+                                                         
@JsonProperty("username") String username,
+                                                         
@JsonProperty("mailboxName") String mailboxName,
+                                                         
@JsonProperty("timestamp") Instant timestamp,
+                                                         
@JsonProperty("rulesOnMessagesApplySuccessfully") long 
rulesOnMessagesApplySuccessfully,
+                                                         
@JsonProperty("rulesOnMessagesApplyFailed") long rulesOnMessagesApplyFailed) {
+        this.type = type;
+        this.username = username;
+        this.mailboxName = mailboxName;
+        this.timestamp = timestamp;
+        this.rulesOnMessagesApplySuccessfully = 
rulesOnMessagesApplySuccessfully;
+        this.rulesOnMessagesApplyFailed = rulesOnMessagesApplyFailed;
+    }
+
+    @Override
+    public String getType() {
+        return type;
+    }
+
+    @Override
+    public Instant getTimestamp() {
+        return timestamp;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public String getMailboxName() {
+        return mailboxName;
+    }
+
+    public long getRulesOnMessagesApplySuccessfully() {
+        return rulesOnMessagesApplySuccessfully;
+    }
+
+    public long getRulesOnMessagesApplyFailed() {
+        return rulesOnMessagesApplyFailed;
+    }
+}
diff --git 
a/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxTaskDTO.java
 
b/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxTaskDTO.java
new file mode 100644
index 0000000000..266401be7a
--- /dev/null
+++ 
b/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxTaskDTO.java
@@ -0,0 +1,84 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.webadmin.data.jmap;
+
+import org.apache.james.core.Username;
+import org.apache.james.jmap.api.filtering.RuleDTO;
+import org.apache.james.jmap.api.filtering.Rules;
+import org.apache.james.jmap.api.filtering.Version;
+import org.apache.james.json.DTOModule;
+import org.apache.james.server.task.json.dto.TaskDTO;
+import org.apache.james.server.task.json.dto.TaskDTOModule;
+import org.apache.james.webadmin.validation.MailboxName;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.collect.ImmutableList;
+
+public class RunRulesOnMailboxTaskDTO implements TaskDTO {
+    private final String type;
+    private final String username;
+    private final String mailboxName;
+    private final ImmutableList<RuleDTO> rules;
+
+    public RunRulesOnMailboxTaskDTO(@JsonProperty("type") String type,
+                                    @JsonProperty("username") String username,
+                                    @JsonProperty("mailboxName") String 
mailboxName,
+                                    @JsonProperty("rules") 
ImmutableList<RuleDTO> rules) {
+        this.type = type;
+        this.username = username;
+        this.mailboxName = mailboxName;
+        this.rules = rules;
+    }
+
+    @Override
+    public String getType() {
+        return type;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public String getMailboxName() {
+        return mailboxName;
+    }
+
+    public ImmutableList<RuleDTO> getRules() {
+        return rules;
+    }
+
+    public static TaskDTOModule<RunRulesOnMailboxTask, 
RunRulesOnMailboxTaskDTO> module(RunRulesOnMailboxService 
runRulesOnMailboxService) {
+        return DTOModule
+            .forDomainObject(RunRulesOnMailboxTask.class)
+            .convertToDTO(RunRulesOnMailboxTaskDTO.class)
+            .toDomainObjectConverter(dto -> 
dto.fromDTO(runRulesOnMailboxService))
+            .toDTOConverter(RunRulesOnMailboxTaskDTO::toDTO)
+            .typeName(RunRulesOnMailboxTask.TASK_TYPE.asString())
+            .withFactory(TaskDTOModule::new);
+    }
+
+    public RunRulesOnMailboxTask fromDTO(RunRulesOnMailboxService 
runRulesOnMailboxService) {
+        return new RunRulesOnMailboxTask(Username.of(username), new 
MailboxName(mailboxName), new Rules(RuleDTO.toRules(rules), Version.INITIAL), 
runRulesOnMailboxService);
+    }
+
+    public static RunRulesOnMailboxTaskDTO toDTO(RunRulesOnMailboxTask 
domainObject, String typeName) {
+        return new RunRulesOnMailboxTaskDTO(typeName, 
domainObject.getUsername().asString(), 
domainObject.getMailboxName().asString(), 
RuleDTO.from(domainObject.getRules().getRules()));
+    }
+}
diff --git 
a/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxRoutesTest.java
 
b/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxRoutesTest.java
new file mode 100644
index 0000000000..43cd0cc357
--- /dev/null
+++ 
b/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxRoutesTest.java
@@ -0,0 +1,480 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.webadmin.data.jmap;
+
+import static io.restassured.RestAssured.given;
+import static io.restassured.http.ContentType.JSON;
+import static org.apache.james.webadmin.Constants.SEPARATOR;
+import static org.apache.james.webadmin.routes.UserMailboxesRoutes.USERS_BASE;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.eclipse.jetty.http.HttpStatus.BAD_REQUEST_400;
+import static org.eclipse.jetty.http.HttpStatus.CREATED_201;
+import static org.eclipse.jetty.http.HttpStatus.NOT_FOUND_404;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+
+import org.apache.james.core.Username;
+import org.apache.james.json.DTOConverter;
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.MessageIdManager;
+import org.apache.james.mailbox.MessageManager;
+import org.apache.james.mailbox.exception.MailboxException;
+import org.apache.james.mailbox.inmemory.InMemoryId;
+import org.apache.james.mailbox.inmemory.InMemoryMailboxManager;
+import org.apache.james.mailbox.inmemory.manager.InMemoryIntegrationResources;
+import org.apache.james.mailbox.model.MailboxId;
+import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.mime4j.dom.Message;
+import org.apache.james.task.Hostname;
+import org.apache.james.task.MemoryTaskManager;
+import org.apache.james.user.api.UsersRepository;
+import org.apache.james.user.api.UsersRepositoryException;
+import org.apache.james.webadmin.WebAdminServer;
+import org.apache.james.webadmin.WebAdminUtils;
+import org.apache.james.webadmin.routes.TasksRoutes;
+import org.apache.james.webadmin.routes.UserMailboxesRoutes;
+import org.apache.james.webadmin.utils.JsonTransformer;
+import org.assertj.core.api.SoftAssertions;
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import com.github.fge.lambdas.Throwing;
+
+import io.restassured.RestAssured;
+
+public class RunRulesOnMailboxRoutesTest {
+    private static final Username USERNAME = Username.of("username");
+    private static final String MAILBOX_NAME = "myMailboxName";
+    private static final String OTHER_MAILBOX_NAME = "myOtherMailboxName";
+    private static final String ERROR_TYPE_NOTFOUND = "notFound";
+    private static final String ERROR_TYPE_INVALIDARGUMENT = "InvalidArgument";
+    private static final String RULE_PAYLOAD = """
+        {
+          "id": "1",
+          "name": "rule 1",
+          "action": {
+            "appendIn": {
+              "mailboxIds": ["%s"]
+            },
+            "important": false,
+            "keyworkds": [],
+            "reject": false,
+            "seen": false
+          },
+          "conditionGroup": {
+            "conditionCombiner": "OR",
+            "conditions": [
+              {
+                "comparator": "contains",
+                "field": "subject",
+                "value": "plop"
+              },
+              {
+                "comparator": "exactly-equals",
+                "field": "from",
+                "value": "[email protected]"
+              }
+            ]
+          }
+        }""";
+
+    private WebAdminServer webAdminServer;
+    private UsersRepository usersRepository;
+    private MemoryTaskManager taskManager;
+    private InMemoryMailboxManager mailboxManager;
+    MessageIdManager messageIdManager;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        InMemoryIntegrationResources resources = 
InMemoryIntegrationResources.builder()
+            .preProvisionnedFakeAuthenticator()
+            .fakeAuthorizator()
+            .inVmEventBus()
+            .defaultAnnotationLimits()
+            .defaultMessageParser()
+            .scanningSearchIndex()
+            .noPreDeletionHooks()
+            .storeQuotaManager()
+            .build();
+
+        mailboxManager = resources.getMailboxManager();
+        messageIdManager = resources.getMessageIdManager();
+
+        usersRepository = mock(UsersRepository.class);
+        when(usersRepository.contains(USERNAME)).thenReturn(true);
+
+        taskManager = new MemoryTaskManager(new Hostname("foo"));
+
+        webAdminServer = WebAdminUtils.createWebAdminServer(
+                new RunRulesOnMailboxRoutes(usersRepository, mailboxManager, 
taskManager, new JsonTransformer(),
+                    new RunRulesOnMailboxService(mailboxManager, new 
InMemoryId.Factory(), messageIdManager)),
+                new TasksRoutes(taskManager, new JsonTransformer(),
+                    
DTOConverter.of(RunRulesOnMailboxTaskAdditionalInformationDTO.SERIALIZATION_MODULE)))
+            .start();
+
+        RestAssured.requestSpecification = 
WebAdminUtils.buildRequestSpecification(webAdminServer)
+            .setBasePath(USERS_BASE + SEPARATOR + USERNAME.asString() + 
SEPARATOR + UserMailboxesRoutes.MAILBOXES)
+            .setUrlEncodingEnabled(false) // no further automatically encoding 
by Rest Assured client. rf: 
https://issues.apache.org/jira/projects/JAMES/issues/JAMES-3936
+            .build();
+    }
+
+    @AfterEach
+    void tearDown() {
+        webAdminServer.destroy();
+        taskManager.stop();
+    }
+
+    @Test
+    void runRulesOnMailboxShouldReturnErrorWhenUserIsNotFound() throws 
UsersRepositoryException {
+        when(usersRepository.contains(USERNAME)).thenReturn(false);
+
+        Map<String, Object> errors = given()
+            .queryParam("action", "triage")
+            .body(RULE_PAYLOAD.formatted("2"))
+            .post(MAILBOX_NAME + "/messages")
+        .then()
+            .statusCode(NOT_FOUND_404)
+            .contentType(JSON)
+            .extract()
+            .body()
+            .jsonPath()
+            .getMap(".");
+
+        assertThat(errors)
+            .containsEntry("statusCode", NOT_FOUND_404)
+            .containsEntry("type", ERROR_TYPE_NOTFOUND)
+            .containsEntry("message", "Invalid argument on user mailboxes")
+            .containsEntry("details", "User does not exist");
+    }
+
+    @Test
+    void runRulesOnMailboxShouldReturnErrorWhenMailboxDoesNotExist() throws 
UsersRepositoryException {
+        Map<String, Object> errors = given()
+            .queryParam("action", "triage")
+            .body(RULE_PAYLOAD.formatted("2"))
+            .post(MAILBOX_NAME + "/messages")
+        .then()
+            .statusCode(NOT_FOUND_404)
+            .contentType(JSON)
+            .extract()
+            .body()
+            .jsonPath()
+            .getMap(".");
+
+        assertThat(errors)
+            .containsEntry("statusCode", NOT_FOUND_404)
+            .containsEntry("type", ERROR_TYPE_NOTFOUND)
+            .containsEntry("message", "Invalid argument on user mailboxes")
+            .containsEntry("details", String.format("Mailbox does not exist. 
#private:%s:%s", USERNAME.asString(), MAILBOX_NAME));
+    }
+
+    @Test
+    void runRulesOnMailboxShouldReturnErrorWhenNoPayload() throws 
MailboxException {
+        MailboxSession systemSession = 
mailboxManager.createSystemSession(USERNAME);
+        MailboxPath mailboxPath = MailboxPath.forUser(USERNAME, MAILBOX_NAME);
+        mailboxManager.createMailbox(mailboxPath, systemSession);
+
+        Map<String, Object> errors = given()
+            .queryParam("action", "triage")
+            .post(MAILBOX_NAME + "/messages")
+        .then()
+            .statusCode(BAD_REQUEST_400)
+            .contentType(JSON)
+            .extract()
+            .body()
+            .jsonPath()
+            .getMap(".");
+
+        assertThat(errors)
+            .containsEntry("statusCode", BAD_REQUEST_400)
+            .containsEntry("type", ERROR_TYPE_INVALIDARGUMENT)
+            .containsEntry("message", "JSON payload of the request is not 
valid");
+    }
+
+    @Test
+    void runRulesOnMailboxShouldReturnErrorWhenBadPayload() throws 
MailboxException {
+        MailboxSession systemSession = 
mailboxManager.createSystemSession(USERNAME);
+        MailboxPath mailboxPath = MailboxPath.forUser(USERNAME, MAILBOX_NAME);
+        mailboxManager.createMailbox(mailboxPath, systemSession);
+
+        Map<String, Object> errors = given()
+            .queryParam("action", "triage")
+            .body("""
+                    {
+                      "id": "1",
+                      "name": "rule 1",
+                      "condition": bad condition",
+                      "action": "bad action"
+                    }""")
+            .post(MAILBOX_NAME + "/messages")
+        .then()
+            .statusCode(BAD_REQUEST_400)
+            .contentType(JSON)
+            .extract()
+            .body()
+            .jsonPath()
+            .getMap(".");
+
+        assertThat(errors)
+            .containsEntry("statusCode", BAD_REQUEST_400)
+            .containsEntry("type", ERROR_TYPE_INVALIDARGUMENT)
+            .containsEntry("message", "JSON payload of the request is not 
valid");
+    }
+
+    @Test
+    void runRulesOnMailboxShouldReturnTaskId() throws MailboxException {
+        MailboxSession systemSession = 
mailboxManager.createSystemSession(USERNAME);
+        MailboxPath mailboxPath = MailboxPath.forUser(USERNAME, MAILBOX_NAME);
+        mailboxManager.createMailbox(mailboxPath, systemSession);
+
+        String taskId = given()
+            .queryParam("action", "triage")
+            .body(RULE_PAYLOAD.formatted("2"))
+            .post(MAILBOX_NAME + "/messages")
+        .then()
+            .statusCode(CREATED_201)
+            .extract()
+            .jsonPath()
+            .get("taskId");
+
+        assertThat(taskId)
+            .isNotEmpty();
+    }
+
+    @Test
+    void runRulesOnMailboxShouldMoveMatchingMessage() throws Exception {
+        MailboxPath mailboxPath = MailboxPath.forUser(USERNAME, MAILBOX_NAME);
+        MailboxPath otherMailboxPath = MailboxPath.forUser(USERNAME, 
OTHER_MAILBOX_NAME);
+        MailboxSession systemSession = 
mailboxManager.createSystemSession(USERNAME);
+
+        mailboxManager.createMailbox(mailboxPath, systemSession);
+        mailboxManager.createMailbox(otherMailboxPath, systemSession);
+
+        mailboxManager.getMailbox(mailboxPath, systemSession)
+            .appendMessage(MessageManager.AppendCommand.builder()
+                    .build(Message.Builder.of()
+                        .setSubject("plop")
+                        .setFrom("[email protected]")
+                        .setBody("body", StandardCharsets.UTF_8)),
+                systemSession);
+
+        MailboxId otherMailboxId = mailboxManager.getMailbox(otherMailboxPath, 
systemSession).getId();
+
+        String taskId = given()
+            .queryParam("action", "triage")
+            .body(RULE_PAYLOAD.formatted(otherMailboxId.serialize()))
+            .post(MAILBOX_NAME + "/messages")
+        .then()
+            .statusCode(CREATED_201)
+            .extract()
+            .jsonPath()
+            .get("taskId");
+
+        given()
+            .basePath(TasksRoutes.BASE)
+        .when()
+            .get(taskId + "/await");
+
+        SoftAssertions.assertSoftly(
+            softly -> {
+                softly.assertThat(Throwing.supplier(() -> 
mailboxManager.getMailbox(mailboxPath, 
systemSession).getMailboxCounters(systemSession).getCount()).get())
+                    .isEqualTo(0);
+                softly.assertThat(Throwing.supplier(() -> 
mailboxManager.getMailbox(otherMailboxPath, 
systemSession).getMailboxCounters(systemSession).getCount()).get())
+                    .isEqualTo(1);
+            }
+        );
+    }
+
+    @Test
+    void runRulesOnMailboxShouldNotMoveNonMatchingMessage() throws Exception {
+        MailboxPath mailboxPath = MailboxPath.forUser(USERNAME, MAILBOX_NAME);
+        MailboxPath otherMailboxPath = MailboxPath.forUser(USERNAME, 
OTHER_MAILBOX_NAME);
+        MailboxSession systemSession = 
mailboxManager.createSystemSession(USERNAME);
+
+        mailboxManager.createMailbox(mailboxPath, systemSession);
+        mailboxManager.createMailbox(otherMailboxPath, systemSession);
+
+        mailboxManager.getMailbox(mailboxPath, systemSession)
+            .appendMessage(MessageManager.AppendCommand.builder()
+                    .build(Message.Builder.of()
+                        .setSubject("hello")
+                        .setFrom("[email protected]")
+                        .setBody("body", StandardCharsets.UTF_8)),
+                systemSession);
+
+        MailboxId otherMailboxId = mailboxManager.getMailbox(otherMailboxPath, 
systemSession).getId();
+
+        String taskId = given()
+            .queryParam("action", "triage")
+            .body(RULE_PAYLOAD.formatted(otherMailboxId.serialize()))
+            .post(MAILBOX_NAME + "/messages")
+        .then()
+            .statusCode(CREATED_201)
+            .extract()
+            .jsonPath()
+            .get("taskId");
+
+        given()
+            .basePath(TasksRoutes.BASE)
+        .when()
+            .get(taskId + "/await");
+
+        SoftAssertions.assertSoftly(
+            softly -> {
+                softly.assertThat(Throwing.supplier(() -> 
mailboxManager.getMailbox(mailboxPath, 
systemSession).getMailboxCounters(systemSession).getCount()).get())
+                    .isEqualTo(1);
+                softly.assertThat(Throwing.supplier(() -> 
mailboxManager.getMailbox(otherMailboxPath, 
systemSession).getMailboxCounters(systemSession).getCount()).get())
+                    .isEqualTo(0);
+            }
+        );
+    }
+
+    @Test
+    void runRulesOnMailboxShouldManageMixedCase() throws Exception {
+        MailboxPath mailboxPath = MailboxPath.forUser(USERNAME, MAILBOX_NAME);
+        MailboxPath otherMailboxPath = MailboxPath.forUser(USERNAME, 
OTHER_MAILBOX_NAME);
+        MailboxSession systemSession = 
mailboxManager.createSystemSession(USERNAME);
+
+        mailboxManager.createMailbox(mailboxPath, systemSession);
+        mailboxManager.createMailbox(otherMailboxPath, systemSession);
+
+        MessageManager messageManager = mailboxManager.getMailbox(mailboxPath, 
systemSession);
+
+        messageManager.appendMessage(
+            MessageManager.AppendCommand.builder()
+                .build(Message.Builder.of()
+                    .setSubject("plop")
+                    .setFrom("[email protected]")
+                    .setBody("body", StandardCharsets.UTF_8)),
+            systemSession);
+
+        messageManager.appendMessage(
+            MessageManager.AppendCommand.builder()
+                .build(Message.Builder.of()
+                    .setSubject("hello")
+                    .setFrom("[email protected]")
+                    .setBody("body", StandardCharsets.UTF_8)),
+            systemSession);
+
+        messageManager.appendMessage(
+            MessageManager.AppendCommand.builder()
+                .build(Message.Builder.of()
+                    .setSubject("hello")
+                    .setFrom("[email protected]")
+                    .setBody("body", StandardCharsets.UTF_8)),
+            systemSession);
+
+        MailboxId otherMailboxId = mailboxManager.getMailbox(otherMailboxPath, 
systemSession).getId();
+
+        String taskId = given()
+            .queryParam("action", "triage")
+            .body(RULE_PAYLOAD.formatted(otherMailboxId.serialize()))
+            .post(MAILBOX_NAME + "/messages")
+        .then()
+            .statusCode(CREATED_201)
+            .extract()
+            .jsonPath()
+            .get("taskId");
+
+        given()
+            .basePath(TasksRoutes.BASE)
+        .when()
+            .get(taskId + "/await");
+
+        SoftAssertions.assertSoftly(
+            softly -> {
+                softly.assertThat(Throwing.supplier(() -> 
mailboxManager.getMailbox(mailboxPath, 
systemSession).getMailboxCounters(systemSession).getCount()).get())
+                    .isEqualTo(1);
+                softly.assertThat(Throwing.supplier(() -> 
mailboxManager.getMailbox(otherMailboxPath, 
systemSession).getMailboxCounters(systemSession).getCount()).get())
+                    .isEqualTo(2);
+            }
+        );
+    }
+
+    @Test
+    void runRulesOnMailboxShouldReturnTaskDetails() throws Exception {
+        MailboxPath mailboxPath = MailboxPath.forUser(USERNAME, MAILBOX_NAME);
+        MailboxPath otherMailboxPath = MailboxPath.forUser(USERNAME, 
OTHER_MAILBOX_NAME);
+        MailboxSession systemSession = 
mailboxManager.createSystemSession(USERNAME);
+
+        mailboxManager.createMailbox(mailboxPath, systemSession);
+        mailboxManager.createMailbox(otherMailboxPath, systemSession);
+
+        MessageManager messageManager = mailboxManager.getMailbox(mailboxPath, 
systemSession);
+
+        messageManager.appendMessage(
+            MessageManager.AppendCommand.builder()
+                .build(Message.Builder.of()
+                    .setSubject("plop")
+                    .setFrom("[email protected]")
+                    .setBody("body", StandardCharsets.UTF_8)),
+            systemSession);
+
+        messageManager.appendMessage(
+            MessageManager.AppendCommand.builder()
+                .build(Message.Builder.of()
+                    .setSubject("hello")
+                    .setFrom("[email protected]")
+                    .setBody("body", StandardCharsets.UTF_8)),
+            systemSession);
+
+        messageManager.appendMessage(
+            MessageManager.AppendCommand.builder()
+                .build(Message.Builder.of()
+                    .setSubject("hello")
+                    .setFrom("[email protected]")
+                    .setBody("body", StandardCharsets.UTF_8)),
+            systemSession);
+
+        MailboxId otherMailboxId = mailboxManager.getMailbox(otherMailboxPath, 
systemSession).getId();
+
+        String taskId = given()
+            .queryParam("action", "triage")
+            .body(RULE_PAYLOAD.formatted(otherMailboxId.serialize()))
+            .post(MAILBOX_NAME + "/messages")
+        .then()
+            .statusCode(CREATED_201)
+            .extract()
+            .jsonPath()
+            .get("taskId");
+
+        given()
+            .basePath(TasksRoutes.BASE)
+        .when()
+            .get(taskId + "/await")
+        .then()
+            .body("status", Matchers.is("completed"))
+            .body("taskId", Matchers.is(notNullValue()))
+            .body("type", 
Matchers.is(RunRulesOnMailboxTask.TASK_TYPE.asString()))
+            .body("startedDate", Matchers.is(notNullValue()))
+            .body("submitDate", Matchers.is(notNullValue()))
+            .body("completedDate", Matchers.is(notNullValue()))
+            .body("additionalInformation.username", 
Matchers.is(USERNAME.asString()))
+            .body("additionalInformation.mailboxName", 
Matchers.is(MAILBOX_NAME))
+            .body("additionalInformation.rulesOnMessagesApplySuccessfully", 
Matchers.is(2))
+            .body("additionalInformation.rulesOnMessagesApplyFailed", 
Matchers.is(0));
+    }
+}
diff --git 
a/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/mailet/filter/RuleMatcher.java
 
b/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxTaskAdditionalInformationDTOTest.java
similarity index 51%
copy from 
server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/mailet/filter/RuleMatcher.java
copy to 
server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxTaskAdditionalInformationDTOTest.java
index 4e59fb4611..0ce8572d5a 100644
--- 
a/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/mailet/filter/RuleMatcher.java
+++ 
b/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxTaskAdditionalInformationDTOTest.java
@@ -17,38 +17,27 @@
  * under the License.                                           *
  ****************************************************************/
 
-package org.apache.james.jmap.mailet.filter;
+package org.apache.james.webadmin.data.jmap;
 
-import java.util.List;
-import java.util.stream.Stream;
+import java.time.Instant;
 
-import org.apache.james.jmap.api.filtering.Rule;
-import org.apache.james.mailbox.exception.MailboxException;
-import org.apache.james.mailbox.model.MessageResult;
-import org.apache.mailet.Mail;
+import org.apache.james.JsonSerializationVerifier;
+import org.apache.james.core.Username;
+import org.apache.james.util.ClassLoaderUtils;
+import org.apache.james.webadmin.validation.MailboxName;
+import org.junit.jupiter.api.Test;
 
-import com.google.common.base.Preconditions;
+public class RunRulesOnMailboxTaskAdditionalInformationDTOTest {
+    private static final Instant INSTANT = 
Instant.parse("2007-12-03T10:15:30.00Z");
 
-class RuleMatcher {
-    private final List<Rule> filteringRules;
+    private static final RunRulesOnMailboxTask.AdditionalInformation 
DOMAIN_OBJECT = new RunRulesOnMailboxTask.AdditionalInformation(
+        Username.of("[email protected]"), new MailboxName("mbx1"), INSTANT, 10, 
9);
 
-    RuleMatcher(List<Rule> filteringRules) {
-        Preconditions.checkNotNull(filteringRules);
-
-        this.filteringRules = filteringRules;
-    }
-
-    Stream<Rule> findApplicableRules(Mail mail) {
-        FilteringHeaders filteringHeaders = new 
FilteringHeaders.MailFilteringHeaders(mail);
-
-        return filteringRules.stream()
-            .filter(rule -> MailMatcher.from(rule).match(filteringHeaders));
-    }
-
-    Stream<Rule> findApplicableRules(MessageResult messageResult) throws 
MailboxException {
-        FilteringHeaders filteringHeaders = new 
FilteringHeaders.MessageResultFilteringHeaders(messageResult);
-
-        return filteringRules.stream()
-            .filter(rule -> MailMatcher.from(rule).match(filteringHeaders));
+    @Test
+    void shouldMatchJsonSerializationContract() throws Exception {
+        
JsonSerializationVerifier.dtoModule(RunRulesOnMailboxTaskAdditionalInformationDTO.SERIALIZATION_MODULE)
+            .bean(DOMAIN_OBJECT)
+            
.json(ClassLoaderUtils.getSystemResourceAsString("json/runRulesOnMailbox.additionalInformation.json"))
+            .verify();
     }
 }
diff --git 
a/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxTaskSerializationTest.java
 
b/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxTaskSerializationTest.java
new file mode 100644
index 0000000000..b95fc5f026
--- /dev/null
+++ 
b/server/protocols/webadmin/webadmin-jmap/src/test/java/org/apache/james/webadmin/data/jmap/RunRulesOnMailboxTaskSerializationTest.java
@@ -0,0 +1,65 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.webadmin.data.jmap;
+
+import static 
org.apache.james.jmap.api.filtering.Rule.Condition.Comparator.CONTAINS;
+import static 
org.apache.james.jmap.api.filtering.Rule.Condition.Comparator.NOT_EXACTLY_EQUALS;
+import static 
org.apache.james.jmap.api.filtering.Rule.Condition.FixedField.FROM;
+import static 
org.apache.james.jmap.api.filtering.Rule.Condition.FixedField.SUBJECT;
+import static org.mockito.Mockito.mock;
+
+import org.apache.james.JsonSerializationVerifier;
+import org.apache.james.core.Username;
+import org.apache.james.jmap.api.filtering.Rule;
+import org.apache.james.jmap.api.filtering.Rules;
+import org.apache.james.jmap.api.filtering.Version;
+import org.apache.james.util.ClassLoaderUtils;
+import org.apache.james.webadmin.validation.MailboxName;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import com.google.common.collect.ImmutableList;
+
+public class RunRulesOnMailboxTaskSerializationTest {
+    private RunRulesOnMailboxService runRulesOnMailboxService;
+    private static final Username USERNAME = Username.of("[email protected]");
+    private static final MailboxName MAILBOX_NAME = new MailboxName("mbx1");
+    private static final Rule RULE = Rule.builder()
+        .id(Rule.Id.of("1"))
+        .name("rule 1")
+            .conditionGroup(Rule.ConditionGroup.of(Rule.ConditionCombiner.AND, 
Rule.Condition.of(SUBJECT, CONTAINS, "plop"),
+                Rule.Condition.of(FROM, NOT_EXACTLY_EQUALS, 
"[email protected]")))
+        
.action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds(ImmutableList.of("mbx2"))))
+        .build();
+    private static final Rules RULES = new Rules(ImmutableList.of(RULE), 
Version.INITIAL);
+
+    @BeforeEach
+    void setUp() {
+        runRulesOnMailboxService = mock(RunRulesOnMailboxService.class);
+    }
+
+    @Test
+    void shouldMatchJsonSerializationContract() throws Exception {
+        
JsonSerializationVerifier.dtoModule(RunRulesOnMailboxTaskDTO.module(runRulesOnMailboxService))
+            .bean(new RunRulesOnMailboxTask(USERNAME, MAILBOX_NAME, RULES, 
runRulesOnMailboxService))
+            
.json(ClassLoaderUtils.getSystemResourceAsString("json/runRulesOnMailbox.task.json"))
+            .verify();
+    }
+}
diff --git 
a/server/protocols/webadmin/webadmin-jmap/src/test/resources/json/runRulesOnMailbox.additionalInformation.json
 
b/server/protocols/webadmin/webadmin-jmap/src/test/resources/json/runRulesOnMailbox.additionalInformation.json
new file mode 100644
index 0000000000..a3f8bd738b
--- /dev/null
+++ 
b/server/protocols/webadmin/webadmin-jmap/src/test/resources/json/runRulesOnMailbox.additionalInformation.json
@@ -0,0 +1,8 @@
+{
+  "mailboxName": "mbx1",
+  "rulesOnMessagesApplyFailed": 9,
+  "rulesOnMessagesApplySuccessfully": 10,
+  "timestamp": "2007-12-03T10:15:30Z",
+  "type": "RunRulesOnMailboxTask",
+  "username": "[email protected]"
+}
diff --git 
a/server/protocols/webadmin/webadmin-jmap/src/test/resources/json/runRulesOnMailbox.task.json
 
b/server/protocols/webadmin/webadmin-jmap/src/test/resources/json/runRulesOnMailbox.task.json
new file mode 100644
index 0000000000..63445e07e9
--- /dev/null
+++ 
b/server/protocols/webadmin/webadmin-jmap/src/test/resources/json/runRulesOnMailbox.task.json
@@ -0,0 +1,35 @@
+{
+  "mailboxName": "mbx1",
+  "type": "RunRulesOnMailboxTask",
+  "username": "[email protected]",
+  "rules": [
+    {
+      "action": {
+        "appendIn": {
+          "mailboxIds": ["mbx2"]
+        },
+        "important": false,
+        "keyworkds": [],
+        "reject": false,
+        "seen": false
+      },
+      "conditionGroup": {
+        "conditionCombiner": "AND",
+        "conditions": [
+          {
+            "comparator": "contains",
+            "field": "subject",
+            "value": "plop"
+          },
+          {
+            "comparator": "not-exactly-equals",
+            "field": "from",
+            "value": "[email protected]"
+          }
+        ]
+      },
+      "id": "1",
+      "name": "rule 1"
+    }
+  ]
+}
\ No newline at end of file
diff --git 
a/server/protocols/webadmin/webadmin-mailbox/src/main/java/org/apache/james/webadmin/service/UserMailboxesService.java
 
b/server/protocols/webadmin/webadmin-mailbox/src/main/java/org/apache/james/webadmin/service/UserMailboxesService.java
index 02e75ac050..e84e22b8cb 100644
--- 
a/server/protocols/webadmin/webadmin-mailbox/src/main/java/org/apache/james/webadmin/service/UserMailboxesService.java
+++ 
b/server/protocols/webadmin/webadmin-mailbox/src/main/java/org/apache/james/webadmin/service/UserMailboxesService.java
@@ -212,5 +212,4 @@ public class UserMailboxesService {
             Minimal, mailboxSession)
             .toStream();
     }
-
 }
diff --git 
a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/UserMailboxesRoutesNoIndexationTest.java
 
b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/UserMailboxesRoutesNoIndexationTest.java
index 71cf5647a2..4480262376 100644
--- 
a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/UserMailboxesRoutesNoIndexationTest.java
+++ 
b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/UserMailboxesRoutesNoIndexationTest.java
@@ -56,7 +56,8 @@ class UserMailboxesRoutesNoIndexationTest {
 
     @BeforeEach
     void setUp() throws Exception {
-        InMemoryMailboxManager mailboxManager = 
InMemoryIntegrationResources.defaultResources().getMailboxManager();
+        InMemoryIntegrationResources memoryResources = 
InMemoryIntegrationResources.defaultResources();
+        InMemoryMailboxManager mailboxManager = 
memoryResources.getMailboxManager();
         UsersRepository usersRepository = mock(UsersRepository.class);
         when(usersRepository.contains(USERNAME)).thenReturn(true);
 
diff --git 
a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/UserRoutesWithMailboxParamTest.java
 
b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/UserRoutesWithMailboxParamTest.java
index 5c5ba294b0..bf6c1d3e4d 100644
--- 
a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/UserRoutesWithMailboxParamTest.java
+++ 
b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/UserRoutesWithMailboxParamTest.java
@@ -78,7 +78,8 @@ public class UserRoutesWithMailboxParamTest {
         SimpleDomainList domainList = new SimpleDomainList();
         domainList.addDomain(DOMAIN);
         usersRepository = MemoryUsersRepository.withVirtualHosting(domainList);
-        mailboxManager = 
InMemoryIntegrationResources.defaultResources().getMailboxManager();
+        InMemoryIntegrationResources memoryResources = 
InMemoryIntegrationResources.defaultResources();
+        mailboxManager = memoryResources.getMailboxManager();
         UserMailboxesService userMailboxService = new 
UserMailboxesService(mailboxManager, usersRepository);
         MemoryRecipientRewriteTable recipientRewriteTable = new 
MemoryRecipientRewriteTable();
         recipientRewriteTable.setDomainList(domainList);


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to