JAMES-4025 Rewrite some tests of JMAP draft on top of JMAP RFC-8621 (module: spamassassin)
diff --git a/third-party/spamassassin/src/test/java/org/apache/james/spamassassin/SpamAssassinContract.java b/third-party/spamassassin/src/test/java/org/apache/james/spamassassin/SpamAssassinContract.java
index 35fb752..2846980 100644
--- a/third-party/spamassassin/src/test/java/org/apache/james/spamassassin/SpamAssassinContract.java
+++ b/third-party/spamassassin/src/test/java/org/apache/james/spamassassin/SpamAssassinContract.java
@@ -24,32 +24,30 @@
 import static io.restassured.RestAssured.with;
 import static io.restassured.config.EncoderConfig.encoderConfig;
 import static io.restassured.config.RestAssuredConfig.newConfig;
-import static org.apache.james.jmap.HttpJmapAuthentication.authenticateJamesUser;
-import static org.apache.james.jmap.JMAPTestingConstants.ARGUMENTS;
+import static io.restassured.http.ContentType.JSON;
 import static org.apache.james.jmap.JMAPTestingConstants.LOCALHOST_IP;
-import static org.apache.james.jmap.JMAPTestingConstants.NAME;
 import static org.apache.james.jmap.JMAPTestingConstants.calmlyAwait;
+import static org.apache.james.jmap.JmapRFCCommonRequests.ACCEPT_JMAP_RFC_HEADER;
+import static org.apache.james.jmap.JmapRFCCommonRequests.UserCredential;
+import static org.apache.james.jmap.JmapRFCCommonRequests.getMailboxId;
+import static org.apache.james.jmap.JmapRFCCommonRequests.getUserCredential;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.awaitility.Durations.ONE_MINUTE;
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.notNullValue;
 
 import java.nio.charset.StandardCharsets;
 import java.util.List;
-import java.util.Map;
+import java.util.function.Consumer;
 
-import org.apache.http.client.utils.URIBuilder;
 import org.apache.james.GuiceJamesServer;
-import org.apache.james.core.Username;
-import org.apache.james.jmap.AccessToken;
-import org.apache.james.jmap.LocalHostURIBuilder;
+import org.apache.james.jmap.JmapRFCCommonRequests;
 import org.apache.james.jmap.draft.JmapGuiceProbe;
 import org.apache.james.mailbox.Role;
 import org.apache.james.modules.protocols.ImapGuiceProbe;
-import org.apache.james.util.Port;
 import org.apache.james.utils.DataProbeImpl;
 import org.apache.james.utils.SpoolerProbe;
 import org.apache.james.utils.TestIMAPClient;
+import org.hamcrest.Matchers;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -77,6 +75,7 @@
             .setAccept(ContentType.JSON)
             .setConfig(newConfig().encoderConfig(encoderConfig().defaultContentCharset(StandardCharsets.UTF_8)))
             .setPort(jamesServer.getProbe(JmapGuiceProbe.class).getJmapPort().getValue())
+            .addHeader(ACCEPT_JMAP_RFC_HEADER.getName(), ACCEPT_JMAP_RFC_HEADER.getValue())
             .build();
         RestAssured.defaultParser = Parser.JSON;
 
@@ -95,79 +94,71 @@
         spamAssassin.clear(ALICE);
     }
 
-    default AccessToken accessTokenFor(GuiceJamesServer james, String user, String password) {
-        return authenticateJamesUser(baseUri(james), Username.of(user), password);
-    }
-
     @Test
     default void spamShouldBeDeliveredInSpamMailboxWhenSameMessageHasAlreadyBeenMovedToSpam(
         GuiceJamesServer jamesServer,
         SpamAssassinExtension.SpamAssassin spamAssassin) throws Exception {
 
         spamAssassin.train(ALICE);
-        AccessToken aliceAccessToken = accessTokenFor(jamesServer, ALICE, ALICE_PASSWORD);
-        AccessToken bobAccessToken = accessTokenFor(jamesServer, BOB, BOB_PASSWORD);
+        UserCredential aliceCredential = getUserCredential(ALICE, ALICE_PASSWORD);
+        UserCredential bobCredential = getUserCredential(BOB, BOB_PASSWORD);
 
         // Bob is sending a message to Alice
-        given()
-            .header("Authorization", bobAccessToken.asString())
-            .body(setMessageCreate(bobAccessToken))
-            .when()
-            .post("/jmap");
+        bobSendSpamEmailToAlice(bobCredential);
 
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceAccessToken, getInboxId(aliceAccessToken), 1));
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceCredential, getInboxId(aliceCredential), 1));
         calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertEveryListenerGotCalled(jamesServer));
 
         // Alice is moving this message to Spam -> learning in SpamAssassin
-        List<String> messageIds = with()
-            .header("Authorization", aliceAccessToken.asString())
-            .body("[[\"getMessageList\", {\"filter\":{\"inMailboxes\":[\"" + getInboxId(aliceAccessToken) + "\"]}}, \"#0\"]]")
+        String aliceInboxId = JmapRFCCommonRequests.getMailboxId(aliceCredential, Role.INBOX);
+        List<String> msgIds = JmapRFCCommonRequests.listMessageIdsInMailbox(aliceCredential, aliceInboxId);
+
+        String aliceSpamMailboxId = JmapRFCCommonRequests.getMailboxId(aliceCredential, Role.SPAM);
+
+        Consumer<String> moveMessageToSpamMailbox = messageId -> given()
+            .auth().basic(aliceCredential.username().asString(), aliceCredential.password())
+            .body("""
+                {
+                    "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+                    "methodCalls": [
+                        ["Email/set", {
+                            "accountId": "%s",
+                            "update": {
+                                "%s":{
+                                    "mailboxIds": { "%s" : true}
+                                }
+                            }
+                        }, "c1"]]
+                }""".formatted(aliceCredential.accountId(), messageId, aliceSpamMailboxId))
             .when()
             .post("/jmap")
             .then()
             .statusCode(200)
-            .body(NAME, equalTo("messageList"))
-            .body(ARGUMENTS + ".messageIds", hasSize(1))
-            .extract()
-            .path(ARGUMENTS + ".messageIds");
+            .contentType(JSON);
 
-        messageIds.parallelStream()
-            .forEach(messageId -> given()
-                .header("Authorization", aliceAccessToken.asString())
-                .body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"mailboxIds\": [\"" + getSpamId(aliceAccessToken) + "\"] } } }, \"#0\"]]", messageId))
-                .when()
-                .post("/jmap")
-                .then()
-                .statusCode(200)
-                .body(NAME, equalTo("messagesSet"))
-                .body(ARGUMENTS + ".updated", hasSize(1)));
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceAccessToken, getSpamId(aliceAccessToken), 1));
+        msgIds.forEach(moveMessageToSpamMailbox);
+
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceCredential, getSpamId(aliceCredential), 1));
 
         // Bob is sending again the same message to Alice
-        given()
-            .header("Authorization", bobAccessToken.asString())
-            .body(setMessageCreate(bobAccessToken))
-            .when()
-            .post("/jmap");
+        bobSendSpamEmailToAlice(bobCredential);
 
         // This message is delivered in Alice Spam mailbox (she now must have 2 messages in her Spam mailbox)
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceAccessToken, getSpamId(aliceAccessToken), 2));
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceCredential, getSpamId(aliceCredential), 2));
     }
 
     @Test
     default void imapCopiesToSpamMailboxShouldBeConsideredAsSpam(GuiceJamesServer jamesServer,
                                                                  SpamAssassinExtension.SpamAssassin spamAssassin) throws Exception {
         spamAssassin.train(ALICE);
-        AccessToken aliceAccessToken = accessTokenFor(jamesServer, ALICE, ALICE_PASSWORD);
-        AccessToken bobAccessToken = accessTokenFor(jamesServer, BOB, BOB_PASSWORD);
+
+        UserCredential aliceCredential = getUserCredential(ALICE, ALICE_PASSWORD);
+        UserCredential bobCredential = getUserCredential(BOB, BOB_PASSWORD);
 
         // Bob is sending a message to Alice
-        given()
-            .header("Authorization", bobAccessToken.asString())
-            .body(setMessageCreate(bobAccessToken))
-            .when()
-            .post("/jmap");
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceAccessToken, getInboxId(aliceAccessToken), 1));
+        bobSendSpamEmailToAlice(bobCredential);
+
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceCredential, getInboxId(aliceCredential), 1));
         calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertEveryListenerGotCalled(jamesServer));
 
 
@@ -179,33 +170,25 @@
 
             testIMAPClient.copyFirstMessage("Spam");
         }
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceAccessToken, getSpamId(aliceAccessToken), 1));
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceCredential, getSpamId(aliceCredential), 1));
 
         // Bob is sending again the same message to Alice
-        given()
-            .header("Authorization", bobAccessToken.asString())
-            .body(setMessageCreate(bobAccessToken))
-            .when()
-            .post("/jmap");
+        bobSendSpamEmailToAlice(bobCredential);
 
         // This message is delivered in Alice Spam mailbox (she now must have 2 messages in her Spam mailbox)
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceAccessToken, getSpamId(aliceAccessToken), 2));
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceCredential, getSpamId(aliceCredential), 2));
     }
 
     @Test
     default void imapMovesToSpamMailboxShouldBeConsideredAsSpam(GuiceJamesServer jamesServer,
                                                                 SpamAssassinExtension.SpamAssassin spamAssassin) throws Exception {
         spamAssassin.train(ALICE);
-        AccessToken aliceAccessToken = accessTokenFor(jamesServer, ALICE, ALICE_PASSWORD);
-        AccessToken bobAccessToken = accessTokenFor(jamesServer, BOB, BOB_PASSWORD);
+        UserCredential aliceCredential = getUserCredential(ALICE, ALICE_PASSWORD);
+        UserCredential bobCredential = getUserCredential(BOB, BOB_PASSWORD);
 
         // Bob is sending a message to Alice
-        given()
-            .header("Authorization", bobAccessToken.asString())
-            .body(setMessageCreate(bobAccessToken))
-            .when()
-            .post("/jmap");
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceAccessToken, getInboxId(aliceAccessToken), 1));
+        bobSendSpamEmailToAlice(bobCredential);
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceCredential, getInboxId(aliceCredential), 1));
         calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertEveryListenerGotCalled(jamesServer));
 
         try (TestIMAPClient testIMAPClient = new TestIMAPClient()) {
@@ -215,189 +198,141 @@
 
             testIMAPClient.moveFirstMessage("Spam");
         }
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceAccessToken, getSpamId(aliceAccessToken), 1));
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceCredential, getSpamId(aliceCredential), 1));
 
         // Bob is sending again the same message to Alice
-        given()
-            .header("Authorization", bobAccessToken.asString())
-            .body(setMessageCreate(bobAccessToken))
-            .when()
-            .post("/jmap");
+        bobSendSpamEmailToAlice(bobCredential);
 
         // This message is delivered in Alice Spam mailbox (she now must have 2 messages in her Spam mailbox)
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceAccessToken, getSpamId(aliceAccessToken), 2));
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceCredential, getSpamId(aliceCredential), 2));
     }
 
     @Test
     default void spamAssassinShouldForgetMessagesMovedOutOfSpamFolderUsingJMAP(GuiceJamesServer jamesServer,
                                                                                SpamAssassinExtension.SpamAssassin spamAssassin) throws Exception {
         spamAssassin.train(ALICE);
-        AccessToken aliceAccessToken = accessTokenFor(jamesServer, ALICE, ALICE_PASSWORD);
-        AccessToken bobAccessToken = accessTokenFor(jamesServer, BOB, BOB_PASSWORD);
+        UserCredential aliceCredential = getUserCredential(ALICE, ALICE_PASSWORD);
+        UserCredential bobCredential = getUserCredential(BOB, BOB_PASSWORD);
 
         // Bob is sending a message to Alice
-        given()
-            .header("Authorization", bobAccessToken.asString())
-            .body(setMessageCreate(bobAccessToken))
-            .when()
-            .post("/jmap");
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceAccessToken, getInboxId(aliceAccessToken), 1));
+        bobSendSpamEmailToAlice(bobCredential);
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceCredential, getInboxId(aliceCredential), 1));
         calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertEveryListenerGotCalled(jamesServer));
 
         // Alice is moving this message to Spam -> learning in SpamAssassin
-        List<String> messageIds = with()
-            .header("Authorization", aliceAccessToken.asString())
-            .body("[[\"getMessageList\", {\"filter\":{\"inMailboxes\":[\"" + getInboxId(aliceAccessToken) + "\"]}}, \"#0\"]]")
+        String aliceInboxId = JmapRFCCommonRequests.getMailboxId(aliceCredential, Role.INBOX);
+        List<String> msgIds = JmapRFCCommonRequests.listMessageIdsInMailbox(aliceCredential, aliceInboxId);
+
+        String aliceSpamMailboxId = JmapRFCCommonRequests.getMailboxId(aliceCredential, Role.SPAM);
+
+        Consumer<String> moveMessageToSpamMailbox = messageId -> given()
+            .auth().basic(aliceCredential.username().asString(), aliceCredential.password())
+            .body("""
+                {
+                    "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+                    "methodCalls": [
+                        ["Email/set", {
+                            "accountId": "%s",
+                            "update": {
+                                "%s":{
+                                    "mailboxIds": { "%s" : true}
+                                }
+                            }
+                        }, "c1"]]
+                }""".formatted(aliceCredential.accountId(), messageId, aliceSpamMailboxId))
             .when()
             .post("/jmap")
             .then()
             .statusCode(200)
-            .body(NAME, equalTo("messageList"))
-            .body(ARGUMENTS + ".messageIds", hasSize(1))
-            .extract()
-            .path(ARGUMENTS + ".messageIds");
+            .contentType(JSON);
 
-        messageIds.parallelStream()
-            .forEach(messageId -> given()
-                .header("Authorization", aliceAccessToken.asString())
-                .body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"mailboxIds\": [\"" + getSpamId(aliceAccessToken) + "\"] } } }, \"#0\"]]", messageId))
-                .when()
-                .post("/jmap")
-                .then()
-                .statusCode(200)
-                .body(NAME, equalTo("messagesSet"))
-                .body(ARGUMENTS + ".updated", hasSize(1)));
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceAccessToken, getSpamId(aliceAccessToken), 1));
+        msgIds.forEach(moveMessageToSpamMailbox);
+
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceCredential, getSpamId(aliceCredential), 1));
 
         // Alice is moving this message out of Spam -> forgetting in SpamAssassin
-        messageIds
-            .forEach(messageId -> given()
-                .header("Authorization", aliceAccessToken.asString())
-                .body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"mailboxIds\": [\"" + getInboxId(aliceAccessToken) + "\"] } } }, \"#0\"]]", messageId))
-                .when()
-                .post("/jmap")
-                .then()
-                .statusCode(200)
-                .body(NAME, equalTo("messagesSet"))
-                .body(ARGUMENTS + ".updated", hasSize(1)));
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceAccessToken, getInboxId(aliceAccessToken), 1));
+        Consumer<String> moveMessageOutOfSpamMailbox = messageId -> given()
+            .auth().basic(aliceCredential.username().asString(), aliceCredential.password())
+            .body("""
+                {
+                    "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+                    "methodCalls": [
+                        ["Email/set", {
+                            "accountId": "%s",
+                            "update": {
+                                "%s":{
+                                    "mailboxIds": { "%s" : true}
+                                }
+                            }
+                        }, "c1"]]
+                }""".formatted(aliceCredential.accountId(), messageId, aliceInboxId))
+            .when()
+            .post("/jmap")
+            .then()
+            .statusCode(200)
+            .contentType(JSON);
+        msgIds.forEach(moveMessageOutOfSpamMailbox);
+
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceCredential, getInboxId(aliceCredential), 1));
 
         // Bob is sending again the same message to Alice
-        given()
-            .header("Authorization", bobAccessToken.asString())
-            .body(setMessageCreate(bobAccessToken))
-            .when()
-            .post("/jmap");
+        bobSendSpamEmailToAlice(bobCredential);
 
         // This message is delivered in Alice INBOX mailbox (she now must have 2 messages in her Inbox mailbox)
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceAccessToken, getInboxId(aliceAccessToken), 2));
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceCredential, getInboxId(aliceCredential), 2));
     }
 
     @Test
     default void movingAMailToTrashShouldNotImpactSpamassassinLearning(GuiceJamesServer jamesServer,
                                                                        SpamAssassinExtension.SpamAssassin spamAssassin) throws Exception {
         spamAssassin.train(ALICE);
-        AccessToken aliceAccessToken = accessTokenFor(jamesServer, ALICE, ALICE_PASSWORD);
-        AccessToken bobAccessToken = accessTokenFor(jamesServer, BOB, BOB_PASSWORD);
+        UserCredential aliceCredential = getUserCredential(ALICE, ALICE_PASSWORD);
+        UserCredential bobCredential = getUserCredential(BOB, BOB_PASSWORD);
 
         // Bob is sending a message to Alice
-        given()
-            .header("Authorization", bobAccessToken.asString())
-            .body(setMessageCreate(bobAccessToken))
-            .when()
-            .post("/jmap");
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceAccessToken, getInboxId(aliceAccessToken), 1));
+        bobSendSpamEmailToAlice(bobCredential);
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceCredential, getInboxId(aliceCredential), 1));
         calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertEveryListenerGotCalled(jamesServer));
 
         // Alice is moving this message to Spam -> learning in SpamAssassin
-        List<String> messageIds = with()
-            .header("Authorization", aliceAccessToken.asString())
-            .body("[[\"getMessageList\", {\"filter\":{\"inMailboxes\":[\"" + getInboxId(aliceAccessToken) + "\"]}}, \"#0\"]]")
-            .when()
-            .post("/jmap")
-            .then()
-            .statusCode(200)
-            .body(NAME, equalTo("messageList"))
-            .body(ARGUMENTS + ".messageIds", hasSize(1))
-            .extract()
-            .path(ARGUMENTS + ".messageIds");
+        String aliceInboxId = JmapRFCCommonRequests.getMailboxId(aliceCredential, Role.INBOX);
+        List<String> msgIds = JmapRFCCommonRequests.listMessageIdsInMailbox(aliceCredential, aliceInboxId);
 
-        messageIds.parallelStream()
-            .forEach(messageId -> given()
-                .header("Authorization", aliceAccessToken.asString())
-                .body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"mailboxIds\": [\"" + getSpamId(aliceAccessToken) + "\"] } } }, \"#0\"]]", messageId))
-                .when()
-                .post("/jmap")
-                .then()
-                .statusCode(200)
-                .body(NAME, equalTo("messagesSet"))
-                .body(ARGUMENTS + ".updated", hasSize(1)));
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceAccessToken, getSpamId(aliceAccessToken), 1));
+        String aliceSpamMailboxId = JmapRFCCommonRequests.getMailboxId(aliceCredential, Role.SPAM);
+        moveMessagesToNewMailbox(msgIds, aliceSpamMailboxId, aliceCredential);
+
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceCredential, getSpamId(aliceCredential), 1));
 
         // Alice is moving this message to trash
-        messageIds
-            .forEach(messageId -> given()
-                .header("Authorization", aliceAccessToken.asString())
-                .body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"mailboxIds\": [\"" + getTrashId(aliceAccessToken) + "\"] } } }, \"#0\"]]", messageId))
-                .when()
-                .post("/jmap")
-                .then()
-                .statusCode(200)
-                .body(NAME, equalTo("messagesSet"))
-                .body(ARGUMENTS + ".updated", hasSize(1)));
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceAccessToken, getTrashId(aliceAccessToken), 1));
+        moveMessagesToNewMailbox(msgIds, getTrashId(aliceCredential), aliceCredential);
+
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceCredential, getTrashId(aliceCredential), 1));
 
         // Bob is sending again the same message to Alice
-        given()
-            .header("Authorization", bobAccessToken.asString())
-            .body(setMessageCreate(bobAccessToken))
-            .when()
-            .post("/jmap");
+        bobSendSpamEmailToAlice(bobCredential);
 
         // This message is delivered in Alice Spam mailbox (she now must have 1 messages in her Spam mailbox)
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceAccessToken, getSpamId(aliceAccessToken), 1));
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceCredential, getSpamId(aliceCredential), 1));
     }
 
     @Test
     default void spamAssassinShouldForgetMessagesMovedOutOfSpamFolderUsingIMAP(GuiceJamesServer jamesServer,
                                                                                SpamAssassinExtension.SpamAssassin spamAssassin) throws Exception {
         spamAssassin.train(ALICE);
-        AccessToken aliceAccessToken = accessTokenFor(jamesServer, ALICE, ALICE_PASSWORD);
-        AccessToken bobAccessToken = accessTokenFor(jamesServer, BOB, BOB_PASSWORD);
+        UserCredential bobCredential = getUserCredential(BOB, BOB_PASSWORD);
+        UserCredential aliceCredential = getUserCredential(ALICE, ALICE_PASSWORD);
 
         // Bob is sending a message to Alice
-        given()
-            .header("Authorization", bobAccessToken.asString())
-            .body(setMessageCreate(bobAccessToken))
-            .when()
-            .post("/jmap");
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceAccessToken, getInboxId(aliceAccessToken), 1));
+        bobSendSpamEmailToAlice(bobCredential);
+
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceCredential, getInboxId(aliceCredential), 1));
         calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertEveryListenerGotCalled(jamesServer));
 
         // Alice is moving this message to Spam -> learning in SpamAssassin
-        List<String> messageIds = with()
-            .header("Authorization", aliceAccessToken.asString())
-            .body("[[\"getMessageList\", {\"filter\":{\"inMailboxes\":[\"" + getInboxId(aliceAccessToken) + "\"]}}, \"#0\"]]")
-            .when()
-            .post("/jmap")
-            .then()
-            .statusCode(200)
-            .body(NAME, equalTo("messageList"))
-            .body(ARGUMENTS + ".messageIds", hasSize(1))
-            .extract()
-            .path(ARGUMENTS + ".messageIds");
-
-        messageIds.parallelStream()
-            .forEach(messageId -> given()
-                .header("Authorization", aliceAccessToken.asString())
-                .body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"mailboxIds\": [\"" + getSpamId(aliceAccessToken) + "\"] } } }, \"#0\"]]", messageId))
-                .when()
-                .post("/jmap")
-                .then()
-                .statusCode(200)
-                .body(NAME, equalTo("messagesSet"))
-                .body(ARGUMENTS + ".updated", hasSize(1)));
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceAccessToken, getSpamId(aliceAccessToken), 1));
+        List<String> messageIds = JmapRFCCommonRequests.listMessageIdsInMailbox(aliceCredential, JmapRFCCommonRequests.getMailboxId(aliceCredential, Role.INBOX));
+        moveMessagesToNewMailbox(messageIds, getMailboxId(aliceCredential, Role.SPAM), aliceCredential);
+        
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceCredential, getSpamId(aliceCredential), 1));
 
         // Alice is moving this message out of Spam -> forgetting in SpamAssassin
         try (TestIMAPClient testIMAPClient = new TestIMAPClient()) {
@@ -407,59 +342,32 @@
 
             testIMAPClient.moveFirstMessage(TestIMAPClient.INBOX);
         }
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceAccessToken, getInboxId(aliceAccessToken), 1));
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceCredential, getInboxId(aliceCredential), 1));
 
         // Bob is sending again the same message to Alice
-        given()
-            .header("Authorization", bobAccessToken.asString())
-            .body(setMessageCreate(bobAccessToken))
-            .when()
-            .post("/jmap");
+        bobSendSpamEmailToAlice(bobCredential);
 
         // This message is delivered in Alice INBOX mailbox (she now must have 2 messages in her Inbox mailbox)
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceAccessToken, getInboxId(aliceAccessToken), 2));
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceCredential, getInboxId(aliceCredential), 2));
     }
 
     @Test
     default void expungingSpamMessageShouldNotImpactSpamAssassinState(GuiceJamesServer jamesServer,
                                                                       SpamAssassinExtension.SpamAssassin spamAssassin) throws Exception {
         spamAssassin.train(ALICE);
-        AccessToken aliceAccessToken = accessTokenFor(jamesServer, ALICE, ALICE_PASSWORD);
-        AccessToken bobAccessToken = accessTokenFor(jamesServer, BOB, BOB_PASSWORD);
+        UserCredential bobCredential = getUserCredential(BOB, BOB_PASSWORD);
+        UserCredential aliceCredential = getUserCredential(ALICE, ALICE_PASSWORD);
 
         // Bob is sending a message to Alice
-        given()
-            .header("Authorization", bobAccessToken.asString())
-            .body(setMessageCreate(bobAccessToken))
-            .when()
-            .post("/jmap");
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceAccessToken, getInboxId(aliceAccessToken), 1));
+        bobSendSpamEmailToAlice(bobCredential);
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceCredential, getInboxId(aliceCredential), 1));
         calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertEveryListenerGotCalled(jamesServer));
 
         // Alice is moving this message to Spam -> learning in SpamAssassin
-        List<String> messageIds = with()
-            .header("Authorization", aliceAccessToken.asString())
-            .body("[[\"getMessageList\", {\"filter\":{\"inMailboxes\":[\"" + getInboxId(aliceAccessToken) + "\"]}}, \"#0\"]]")
-            .when()
-            .post("/jmap")
-            .then()
-            .statusCode(200)
-            .body(NAME, equalTo("messageList"))
-            .body(ARGUMENTS + ".messageIds", hasSize(1))
-            .extract()
-            .path(ARGUMENTS + ".messageIds");
+        List<String> messageIds = JmapRFCCommonRequests.listMessageIdsInMailbox(aliceCredential, JmapRFCCommonRequests.getMailboxId(aliceCredential, Role.INBOX));
+        moveMessagesToNewMailbox(messageIds, getMailboxId(aliceCredential, Role.SPAM), aliceCredential);
 
-        messageIds
-            .forEach(messageId -> given()
-                .header("Authorization", aliceAccessToken.asString())
-                .body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"mailboxIds\": [\"" + getSpamId(aliceAccessToken) + "\"] } } }, \"#0\"]]", messageId))
-                .when()
-                .post("/jmap")
-                .then()
-                .statusCode(200)
-                .body(NAME, equalTo("messagesSet"))
-                .body(ARGUMENTS + ".updated", hasSize(1)));
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceAccessToken, getSpamId(aliceAccessToken), 1));
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceCredential, getSpamId(aliceCredential), 1));
 
         // Alice is deleting this message
         try (TestIMAPClient testIMAPClient = new TestIMAPClient()) {
@@ -470,222 +378,210 @@
             testIMAPClient.setFlagsForAllMessagesInMailbox("\\Deleted");
             testIMAPClient.expunge();
         }
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceAccessToken, getSpamId(aliceAccessToken), 0));
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceCredential, getSpamId(aliceCredential), 0));
 
         // Bob is sending again the same message to Alice
-        given()
-            .header("Authorization", bobAccessToken.asString())
-            .body(setMessageCreate(bobAccessToken))
-            .when()
-            .post("/jmap");
+        bobSendSpamEmailToAlice(bobCredential);
 
         // This message is delivered in Alice SPAM mailbox (she now must have 1 messages in her Spam mailbox)
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceAccessToken, getSpamId(aliceAccessToken), 1));
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceCredential, getSpamId(aliceCredential), 1));
     }
 
     @Test
     default void deletingSpamMessageShouldNotImpactSpamAssassinState(GuiceJamesServer jamesServer,
                                                                      SpamAssassinExtension.SpamAssassin spamAssassin) throws Exception {
         spamAssassin.train(ALICE);
-        AccessToken aliceAccessToken = accessTokenFor(jamesServer, ALICE, ALICE_PASSWORD);
-        AccessToken bobAccessToken = accessTokenFor(jamesServer, BOB, BOB_PASSWORD);
+        UserCredential bobCredential = getUserCredential(BOB, BOB_PASSWORD);
+        UserCredential aliceCredential = getUserCredential(ALICE, ALICE_PASSWORD);
 
         // Bob is sending a message to Alice
-        given()
-            .header("Authorization", bobAccessToken.asString())
-            .body(setMessageCreate(bobAccessToken))
-            .when()
-            .post("/jmap");
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceAccessToken, getInboxId(aliceAccessToken), 1));
+        bobSendSpamEmailToAlice(bobCredential);
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceCredential, getInboxId(aliceCredential), 1));
         calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertEveryListenerGotCalled(jamesServer));
 
         // Alice is moving this message to Spam -> learning in SpamAssassin
-        List<String> messageIds = with()
-            .header("Authorization", aliceAccessToken.asString())
-            .body("[[\"getMessageList\", {\"filter\":{\"inMailboxes\":[\"" + getInboxId(aliceAccessToken) + "\"]}}, \"#0\"]]")
-            .when()
-            .post("/jmap")
-            .then()
-            .statusCode(200)
-            .body(NAME, equalTo("messageList"))
-            .body(ARGUMENTS + ".messageIds", hasSize(1))
-            .extract()
-            .path(ARGUMENTS + ".messageIds");
+        List<String> messageIds = JmapRFCCommonRequests.listMessageIdsInMailbox(aliceCredential, JmapRFCCommonRequests.getMailboxId(aliceCredential, Role.INBOX));
+        moveMessagesToNewMailbox(messageIds, getMailboxId(aliceCredential, Role.SPAM), aliceCredential);
 
-        messageIds.parallelStream()
-            .forEach(messageId -> given()
-                .header("Authorization", aliceAccessToken.asString())
-                .body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"mailboxIds\": [\"" + getSpamId(aliceAccessToken) + "\"] } } }, \"#0\"]]", messageId))
-                .when()
-                .post("/jmap")
-                .then()
-                .statusCode(200)
-                .body(NAME, equalTo("messagesSet"))
-                .body(ARGUMENTS + ".updated", hasSize(1)));
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceAccessToken, getSpamId(aliceAccessToken), 1));
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceCredential, getSpamId(aliceCredential), 1));
 
         // Alice is deleting this message
-        messageIds
-            .forEach(messageId -> given()
-                .header("Authorization", aliceAccessToken.asString())
-                .body(String.format("[[\"setMessages\", {\"destroy\": [\"%s\"] }, \"#0\"]]", messageId))
-                .when()
-                .post("/jmap")
-                .then()
-                .statusCode(200)
-                .body(NAME, equalTo("messagesSet"))
-                .body(ARGUMENTS + ".destroyed", hasSize(1)));
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceAccessToken, getSpamId(aliceAccessToken), 0));
+        JmapRFCCommonRequests.deleteMessages(aliceCredential, messageIds);
+
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceCredential, getSpamId(aliceCredential), 0));
 
         // Bob is sending again the same message to Alice
-        given()
-            .header("Authorization", bobAccessToken.asString())
-            .body(setMessageCreate(bobAccessToken))
-            .when()
-            .post("/jmap");
+        bobSendSpamEmailToAlice(bobCredential);
 
         // This message is delivered in Alice SPAM mailbox (she now must have 1 messages in her Spam mailbox)
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceAccessToken, getSpamId(aliceAccessToken), 1));
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceCredential, getSpamId(aliceCredential), 1));
     }
 
-    default String setMessageCreate(AccessToken accessToken) {
-        return "[" +
-            "  [" +
-            "    \"setMessages\"," +
-            "    {" +
-            "      \"create\": { \"creationId1337\" : {" +
-            "        \"from\": { \"email\": \"" + BOB + "\"}," +
-            "        \"to\": [{ \"name\": \"recipient\", \"email\": \"" + ALICE + "\"}]," +
-            "        \"subject\": \"Happy News\"," +
-            "        \"textBody\": \"This is a SPAM!!!\r\n\r\n\"," +
-            "        \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" +
-            "      }}" +
-            "    }," +
-            "    \"#0\"" +
-            "  ]" +
-            "]";
+    default void bobSendSpamEmailToAlice(UserCredential bobCredential) {
+        String bobOutboxId = JmapRFCCommonRequests.getOutboxId(bobCredential);
+        String requestBody =
+            "{" +
+                "    \"using\": [\"urn:ietf:params:jmap:core\", \"urn:ietf:params:jmap:mail\", \"urn:ietf:params:jmap:submission\"]," +
+                "    \"methodCalls\": [" +
+                "        [\"Email/set\", {" +
+                "            \"accountId\": \"" + bobCredential.accountId() + "\"," +
+                "            \"create\": {" +
+                "                \"e1526\": {" +
+                "                    \"mailboxIds\": { \"" + bobOutboxId + "\": true }," +
+                "                    \"subject\": \"Happy News\"," +
+                "                    \"textBody\": [{" +
+                "                        \"partId\": \"a49d\"," +
+                "                        \"type\": \"text/plain\"" +
+                "                    }]," +
+                "                    \"bodyValues\": {" +
+                "                        \"a49d\": {" +
+                "                            \"value\": \"This is a SPAM!!!\"" +
+                "                        }" +
+                "                    }," +
+                "                    \"to\": [{" +
+                "                        \"email\": \"" + ALICE + "\"" +
+                "                    }]," +
+                "                    \"from\": [{" +
+                "                        \"email\": \"" + BOB + "\"" +
+                "                    }]" +
+                "                }" +
+                "            }" +
+                "        }, \"c1\"]," +
+                "        [\"Email/get\", {" +
+                "            \"accountId\": \"" + bobCredential.accountId() + "\"," +
+                "            \"ids\": [\"#e1526\"]," +
+                "            \"properties\": [\"sentAt\"]" +
+                "        }, \"c2\"]," +
+                "        [\"EmailSubmission/set\", {" +
+                "            \"accountId\": \"" + bobCredential.accountId() + "\"," +
+                "            \"create\": {" +
+                "                \"k1490\": {" +
+                "                    \"emailId\": \"#e1526\"," +
+                "                    \"envelope\": {" +
+                "                        \"mailFrom\": {\"email\": \"" + BOB + "\"}," +
+                "                        \"rcptTo\": [{" +
+                "                            \"email\": \"" + ALICE + "\"" +
+                "                        }]" +
+                "                    }" +
+                "                }" +
+                "            }" +
+                "        }, \"c3\"]" +
+                "    ]" +
+                "}";
+
+        with()
+            .auth().basic(bobCredential.username().asString(), bobCredential.password())
+            .body(requestBody)
+            .post("/jmap")
+        .then()
+            .statusCode(200)
+            .contentType(JSON)
+            .body("methodResponses[2][1].created", Matchers.is(notNullValue()));
+
     }
 
     @Test
     default void spamShouldBeDeliveredInSpamMailboxOrInboxWhenMultipleRecipientsConfigurations(GuiceJamesServer jamesServer,
                                                                                                SpamAssassinExtension.SpamAssassin spamAssassin) throws Exception {
         spamAssassin.train(ALICE);
-        AccessToken aliceAccessToken = accessTokenFor(jamesServer, ALICE, ALICE_PASSWORD);
-        AccessToken bobAccessToken = accessTokenFor(jamesServer, BOB, BOB_PASSWORD);
-        AccessToken paulAccessToken = accessTokenFor(jamesServer, PAUL, PAUL_PASSWORD);
+        UserCredential aliceCredential = getUserCredential(ALICE, ALICE_PASSWORD);
+        UserCredential bobCredential = getUserCredential(BOB, BOB_PASSWORD);
+        UserCredential paulCredential = getUserCredential(PAUL, PAUL_PASSWORD);
 
         // Bob is sending a message to Alice & Paul
-        given()
-            .header("Authorization", bobAccessToken.asString())
-            .body(setMessageCreateToMultipleRecipients(bobAccessToken))
-            .when()
-            .post("/jmap");
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceAccessToken, getInboxId(aliceAccessToken), 1));
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(paulAccessToken, getInboxId(paulAccessToken), 1));
+        String bobSendSpamEmailToAliceAndPaulRequest =
+            "{" +
+                "    \"using\": [\"urn:ietf:params:jmap:core\", \"urn:ietf:params:jmap:mail\", \"urn:ietf:params:jmap:submission\"]," +
+                "    \"methodCalls\": [" +
+                "        [\"Email/set\", {" +
+                "            \"accountId\": \"" + bobCredential.accountId() + "\"," +
+                "            \"create\": {" +
+                "                \"e1526\": {" +
+                "                    \"mailboxIds\": { \"" + JmapRFCCommonRequests.getOutboxId(bobCredential) + "\": true }," +
+                "                    \"subject\": \"Happy News\"," +
+                "                    \"textBody\": [{" +
+                "                        \"partId\": \"a49d\"," +
+                "                        \"type\": \"text/plain\"" +
+                "                    }]," +
+                "                    \"bodyValues\": {" +
+                "                        \"a49d\": {" +
+                "                            \"value\": \"This is a SPAM!!!\"" +
+                "                        }" +
+                "                    }," +
+                "                    \"to\": [{\"email\": \"" + ALICE + "\"}, {\"email\": \"" + PAUL + "\"}]," +
+                "                    \"from\": [{" +
+                "                        \"email\": \"" + BOB + "\"" +
+                "                    }]" +
+                "                }" +
+                "            }" +
+                "        }, \"c1\"]," +
+                "        [\"Email/get\", {" +
+                "            \"accountId\": \"" + bobCredential.accountId() + "\"," +
+                "            \"ids\": [\"#e1526\"]," +
+                "            \"properties\": [\"sentAt\"]" +
+                "        }, \"c2\"]," +
+                "        [\"EmailSubmission/set\", {" +
+                "            \"accountId\": \"" + bobCredential.accountId() + "\"," +
+                "            \"create\": {" +
+                "                \"k1490\": {" +
+                "                    \"emailId\": \"#e1526\"," +
+                "                    \"envelope\": {" +
+                "                        \"mailFrom\": {\"email\": \"" + BOB + "\"}," +
+                "                        \"rcptTo\": [{\"email\": \"" + ALICE + "\"}, {\"email\": \"" + PAUL + "\"}]" +
+                "                    }" +
+                "                }" +
+                "            }" +
+                "        }, \"c3\"]" +
+                "    ]" +
+                "}";
+
+        Consumer<String> bobSendAndEmail = requestBody -> with()
+            .auth().basic(bobCredential.username().asString(), bobCredential.password())
+            .body(requestBody)
+            .post("/jmap")
+            .then()
+            .statusCode(200)
+            .contentType(JSON)
+            .body("methodResponses[2][1].created", Matchers.is(notNullValue()));
+        
+        bobSendAndEmail.accept(bobSendSpamEmailToAliceAndPaulRequest);
+   
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceCredential, getInboxId(aliceCredential), 1));
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(paulCredential, getInboxId(paulCredential), 1));
 
         // Alice is moving this message to Spam -> learning in SpamAssassin
-        List<String> messageIds = with()
-            .header("Authorization", aliceAccessToken.asString())
-            .body("[[\"getMessageList\", {\"filter\":{\"inMailboxes\":[\"" + getInboxId(aliceAccessToken) + "\"]}}, \"#0\"]]")
-            .when()
-            .post("/jmap")
-            .then()
-            .statusCode(200)
-            .body(NAME, equalTo("messageList"))
-            .body(ARGUMENTS + ".messageIds", hasSize(1))
-            .extract()
-            .path(ARGUMENTS + ".messageIds");
-
-        messageIds.parallelStream()
-            .forEach(messageId -> given()
-                .header("Authorization", aliceAccessToken.asString())
-                .body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"mailboxIds\": [\"" + getSpamId(aliceAccessToken) + "\"] } } }, \"#0\"]]", messageId))
-                .when()
-                .post("/jmap")
-                .then()
-                .statusCode(200)
-                .body(NAME, equalTo("messagesSet"))
-                .body(ARGUMENTS + ".updated", hasSize(1)));
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceAccessToken, getSpamId(aliceAccessToken), 1));
+        String aliceInboxId = JmapRFCCommonRequests.getMailboxId(aliceCredential, Role.INBOX);
+        List<String> msgIds = JmapRFCCommonRequests.listMessageIdsInMailbox(aliceCredential, aliceInboxId);
+        moveMessagesToNewMailbox(msgIds, JmapRFCCommonRequests.getMailboxId(aliceCredential, Role.SPAM), aliceCredential);
+        
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceCredential, getSpamId(aliceCredential), 1));
 
         // Bob is sending again the same message to Alice & Paul
-        given()
-            .header("Authorization", bobAccessToken.asString())
-            .body(setMessageCreateToMultipleRecipients(bobAccessToken))
-            .when()
-            .post("/jmap");
+        bobSendAndEmail.accept(bobSendSpamEmailToAliceAndPaulRequest);
 
         // This message is delivered in Alice Spam mailbox (she now must have 2 messages in her Spam mailbox)
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceAccessToken, getSpamId(aliceAccessToken), 2));
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceAccessToken, getInboxId(aliceAccessToken), 0));
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceCredential, getSpamId(aliceCredential), 2));
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(aliceCredential, getInboxId(aliceCredential), 0));
         // This message is delivered in Paul Inbox (he now must have 2 messages in his Inbox)
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(paulAccessToken, getInboxId(paulAccessToken), 2));
-        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(paulAccessToken, getSpamId(paulAccessToken), 0));
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(paulCredential, getInboxId(paulCredential), 2));
+        calmlyAwait.atMost(ONE_MINUTE).untilAsserted(() -> assertMessagesFoundInMailbox(paulCredential, getSpamId(paulCredential), 0));
     }
 
-    default String setMessageCreateToMultipleRecipients(AccessToken accessToken) {
-        return "[" +
-            "  [" +
-            "    \"setMessages\"," +
-            "    {" +
-            "      \"create\": { \"creationId1337\" : {" +
-            "        \"from\": { \"email\": \"" + BOB + "\"}," +
-            "        \"to\": [{ \"name\": \"alice\", \"email\": \"" + ALICE + "\"}, " +
-            "                 { \"name\": \"paul\", \"email\": \"" + PAUL + "\"}]," +
-            "        \"subject\": \"Happy News\"," +
-            "        \"textBody\": \"This is a SPAM!!!\r\n\r\n\"," +
-            "        \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" +
-            "      }}" +
-            "    }," +
-            "    \"#0\"" +
-            "  ]" +
-            "]";
+    default void assertMessagesFoundInMailbox(UserCredential userCredential, String mailboxId, int expectedNumberOfMessages) {
+        assertThat(JmapRFCCommonRequests.listMessageIdsInMailbox(userCredential, mailboxId))
+            .hasSize(expectedNumberOfMessages);
     }
 
-    default void assertMessagesFoundInMailbox(AccessToken accessToken, String mailboxId, int expectedNumberOfMessages) {
-        with()
-            .header("Authorization", accessToken.asString())
-            .body("[[\"getMessageList\", {\"filter\":{\"inMailboxes\":[\"" + mailboxId + "\"]}}, \"#0\"]]")
-            .when()
-            .post("/jmap")
-            .then()
-            .statusCode(200)
-            .body(NAME, equalTo("messageList"))
-            .body(ARGUMENTS + ".messageIds", hasSize(expectedNumberOfMessages));
+    default String getInboxId(UserCredential userCredential) {
+        return JmapRFCCommonRequests.getMailboxId(userCredential, Role.INBOX);
     }
 
-    default String getMailboxId(AccessToken accessToken, Role role) {
-        return getAllMailboxesIds(accessToken).stream()
-            .filter(x -> x.get("role").equalsIgnoreCase(role.serialize()))
-            .map(x -> x.get("id"))
-            .findFirst().get();
+    default String getSpamId(UserCredential userCredential) {
+        return JmapRFCCommonRequests.getMailboxId(userCredential,Role.SPAM);
     }
 
-    default List<Map<String, String>> getAllMailboxesIds(AccessToken accessToken) {
-        return with()
-            .header("Authorization", accessToken.asString())
-            .body("[[\"getMailboxes\", {\"properties\": [\"role\", \"id\"]}, \"#0\"]]")
-            .post("/jmap")
-            .andReturn()
-            .body()
-            .jsonPath()
-            .getList(ARGUMENTS + ".list");
-    }
-
-    default String getInboxId(AccessToken accessToken) {
-        return getMailboxId(accessToken, Role.INBOX);
-    }
-
-    default String getOutboxId(AccessToken accessToken) {
-        return getMailboxId(accessToken, Role.OUTBOX);
-    }
-
-    default String getSpamId(AccessToken accessToken) {
-        return getMailboxId(accessToken, Role.SPAM);
-    }
-
-    default String getTrashId(AccessToken accessToken) {
-        return getMailboxId(accessToken, Role.TRASH);
+    default String getTrashId(UserCredential userCredential) {
+        return JmapRFCCommonRequests.getMailboxId(userCredential,Role.TRASH);
     }
 
     default void assertEveryListenerGotCalled(GuiceJamesServer jamesServer) {
@@ -694,8 +590,28 @@
             .isTrue();
     }
 
-    private URIBuilder baseUri(GuiceJamesServer jamesServer) {
-        return LocalHostURIBuilder.baseUri(
-            Port.of(jamesServer.getProbe(JmapGuiceProbe.class).getJmapPort().getValue()));
+    private void moveMessagesToNewMailbox(List<String> messageIds, String newMailboxId, UserCredential userCredential) {
+        Consumer<String> moveMessagesToNewMailbox = messageId -> given()
+            .auth().basic(userCredential.username().asString(), userCredential.password())
+            .body("""
+                {
+                    "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+                    "methodCalls": [
+                        ["Email/set", {
+                            "accountId": "%s",
+                            "update": {
+                                "%s":{
+                                    "mailboxIds": { "%s" : true}
+                                }
+                            }
+                        }, "c1"]]
+                }""".formatted(userCredential.accountId(), messageId, newMailboxId))
+            .when()
+            .post("/jmap")
+            .then()
+            .statusCode(200)
+            .contentType(JSON);
+
+        messageIds.forEach(moveMessagesToNewMailbox);
     }
 }
diff --git a/third-party/spamassassin/src/test/java/org/apache/james/spamassassin/module/SpamAssassinTestModule.java b/third-party/spamassassin/src/test/java/org/apache/james/spamassassin/module/SpamAssassinTestModule.java
index 204bdb2..dc47244 100644
--- a/third-party/spamassassin/src/test/java/org/apache/james/spamassassin/module/SpamAssassinTestModule.java
+++ b/third-party/spamassassin/src/test/java/org/apache/james/spamassassin/module/SpamAssassinTestModule.java
@@ -21,19 +21,16 @@
 
 import jakarta.inject.Singleton;
 
-import org.apache.james.mailetcontainer.impl.MailetConfigImpl;
+import org.apache.james.jmap.event.PopulateEmailQueryViewListener;
 import org.apache.james.modules.mailbox.ListenerConfiguration;
 import org.apache.james.modules.mailbox.ListenersConfiguration;
-import org.apache.james.spamassassin.SpamAssassin;
 import org.apache.james.spamassassin.SpamAssassinConfiguration;
 import org.apache.james.spamassassin.SpamAssassinExtension;
 import org.apache.james.spamassassin.SpamAssassinListener;
 import org.apache.james.util.Host;
-import org.apache.james.utils.MailetConfigurationOverride;
 
 import com.google.inject.AbstractModule;
 import com.google.inject.Provides;
-import com.google.inject.multibindings.Multibinder;
 
 public class SpamAssassinTestModule extends AbstractModule {
 
@@ -47,7 +44,8 @@
     protected void configure() {
         bind(ListenersConfiguration.class)
             .toInstance(ListenersConfiguration.of(
-                ListenerConfiguration.forClass(SpamAssassinListener.class.getCanonicalName())));
+                ListenerConfiguration.forClass(SpamAssassinListener.class.getCanonicalName()),
+                ListenerConfiguration.forClass(PopulateEmailQueryViewListener.class.getCanonicalName())));
     }
 
     @Provides