Merge pull request #546 from chibenwa/JAMES-3544-list-buckets

JAMES-3544 Blob store should allow to list bucket
diff --git a/.scalafix.conf b/.scalafix.conf
new file mode 100644
index 0000000..b00dda3
--- /dev/null
+++ b/.scalafix.conf
@@ -0,0 +1,26 @@
+rules = [
+    DisableSyntax,
+    ProcedureSyntax,
+    NoValInForComprehension,
+    LeakingImplicitClassVal
+]
+
+RemoveUnused.imports = true
+RemoveUnused.privates = false
+RemoveUnused.locals = true
+RemoveUnused.patternvars = false
+RemoveUnused.implicits = false
+
+DisableSyntax.noVars = false
+DisableSyntax.noThrows = false
+DisableSyntax.noNulls = false
+DisableSyntax.noReturns = false
+DisableSyntax.noWhileLoops = false
+DisableSyntax.noAsInstanceOf = false
+DisableSyntax.noIsInstanceOf = false
+DisableSyntax.noXml = false
+DisableSyntax.noDefaultArgs = false
+DisableSyntax.noFinalVal = true
+DisableSyntax.noFinalize = true
+DisableSyntax.noValPatterns = false
+DisableSyntax.noUniversalEquality = false
\ No newline at end of file
diff --git a/docs/modules/community/pages/release.adoc b/docs/modules/community/pages/release.adoc
index c66ba2f..f03df3b 100644
--- a/docs/modules/community/pages/release.adoc
+++ b/docs/modules/community/pages/release.adoc
@@ -186,7 +186,7 @@
 # $2: Key footprint to use for signing
 
 sha512sum $1 > $1.sha512
-gpg -u $2 --output $1.asc --detach-sig $1
+gpg -u $2 --output $1.asc --detach-sig --armor $1
 svn add $1
 svn propset svn:mime-type application/octet-stream $1
 svn add $1.sha512
diff --git a/event-sourcing/event-sourcing-core/pom.xml b/event-sourcing/event-sourcing-core/pom.xml
index f843e5a..a7b6287 100644
--- a/event-sourcing/event-sourcing-core/pom.xml
+++ b/event-sourcing/event-sourcing-core/pom.xml
@@ -87,6 +87,13 @@
                 <groupId>net.alchim31.maven</groupId>
                 <artifactId>scala-maven-plugin</artifactId>
             </plugin>
+            <plugin>
+                <groupId>io.github.evis</groupId>
+                <artifactId>scalafix-maven-plugin</artifactId>
+                <configuration>
+                    <config>${project.parent.parent.basedir}/.scalafix.conf</config>
+                </configuration>
+            </plugin>
         </plugins>
     </build>
 </project>
diff --git a/event-sourcing/event-sourcing-pojo/pom.xml b/event-sourcing/event-sourcing-pojo/pom.xml
index a6c5cc6..1d24afb 100644
--- a/event-sourcing/event-sourcing-pojo/pom.xml
+++ b/event-sourcing/event-sourcing-pojo/pom.xml
@@ -52,6 +52,13 @@
                 <groupId>net.alchim31.maven</groupId>
                 <artifactId>scala-maven-plugin</artifactId>
             </plugin>
+            <plugin>
+                <groupId>io.github.evis</groupId>
+                <artifactId>scalafix-maven-plugin</artifactId>
+                <configuration>
+                    <config>${project.parent.parent.basedir}/.scalafix.conf</config>
+                </configuration>
+            </plugin>
         </plugins>
     </build>
 </project>
diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/MailboxManager.java b/mailbox/api/src/main/java/org/apache/james/mailbox/MailboxManager.java
index 6ee804b..b117baf 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/MailboxManager.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/MailboxManager.java
@@ -34,6 +34,7 @@
 import org.apache.james.mailbox.model.MessageId;
 import org.apache.james.mailbox.model.MessageRange;
 import org.apache.james.mailbox.model.MultimailboxesSearchQuery;
+import org.apache.james.mailbox.model.ThreadId;
 import org.apache.james.mailbox.model.search.MailboxQuery;
 import org.reactivestreams.Publisher;
 
@@ -322,7 +323,18 @@
      * @param session
      *            the context for this call, not null
      */
-    Publisher<MessageId> search(MultimailboxesSearchQuery expression, MailboxSession session, long limit) throws MailboxException;
+    Publisher<MessageId> search(MultimailboxesSearchQuery expression, MailboxSession session, long limit);
+
+    /**
+     * Returns the list of MessageId of messages belonging to that Thread
+     *
+     * @param threadId
+     *          target Thread
+     * @param session
+     *          the context for this call, not null
+     * @return  the list of MessageId of messages belonging to that Thread
+     */
+    Publisher<MessageId> getThread(ThreadId threadId, MailboxSession session);
 
     /**
      * Does the given mailbox exist?
diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/exception/ThreadNotFoundException.java b/mailbox/api/src/main/java/org/apache/james/mailbox/exception/ThreadNotFoundException.java
new file mode 100644
index 0000000..f6f4c3e
--- /dev/null
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/exception/ThreadNotFoundException.java
@@ -0,0 +1,28 @@
+/******************************************************************
+ * 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.mailbox.exception;
+
+import org.apache.james.mailbox.model.ThreadId;
+
+public class ThreadNotFoundException extends MailboxException {
+    public ThreadNotFoundException(ThreadId threadId) {
+        super("Thread " + threadId.getBaseMessageId().serialize() + " can not be found");
+    }
+}
diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/model/ThreadId.java b/mailbox/api/src/main/java/org/apache/james/mailbox/model/ThreadId.java
index ec49426..919b6ad 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/model/ThreadId.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/model/ThreadId.java
@@ -21,10 +21,26 @@
 
 import java.util.Objects;
 
+import javax.inject.Inject;
+
 import com.google.common.base.MoreObjects;
 
 
 public class ThreadId {
+    public static class Factory {
+        private final MessageId.Factory messageIdFactory;
+
+        @Inject
+        public Factory(MessageId.Factory messageIdFactory) {
+            this.messageIdFactory = messageIdFactory;
+        }
+
+        public ThreadId fromString(String serialized) {
+            MessageId messageId = messageIdFactory.fromString(serialized);
+            return fromBaseMessageId(messageId);
+        }
+    }
+
     public static ThreadId fromBaseMessageId(MessageId baseMessageId) {
         return new ThreadId(baseMessageId);
     }
diff --git a/mailbox/event/json/pom.xml b/mailbox/event/json/pom.xml
index e232ab8..055f8ab 100644
--- a/mailbox/event/json/pom.xml
+++ b/mailbox/event/json/pom.xml
@@ -99,6 +99,13 @@
                 <groupId>net.alchim31.maven</groupId>
                 <artifactId>scala-maven-plugin</artifactId>
             </plugin>
+            <plugin>
+                <groupId>io.github.evis</groupId>
+                <artifactId>scalafix-maven-plugin</artifactId>
+                <configuration>
+                    <config>${project.parent.parent.basedir}/.scalafix.conf</config>
+                </configuration>
+            </plugin>
         </plugins>
     </build>
 
diff --git a/mailbox/scanning-search/src/test/java/org/apache/james/mailbox/store/search/SearchThreadIdGuessingAlgorithmTest.java b/mailbox/scanning-search/src/test/java/org/apache/james/mailbox/store/search/SearchThreadIdGuessingAlgorithmTest.java
index 83ab8c1..dfa79f2 100644
--- a/mailbox/scanning-search/src/test/java/org/apache/james/mailbox/store/search/SearchThreadIdGuessingAlgorithmTest.java
+++ b/mailbox/scanning-search/src/test/java/org/apache/james/mailbox/store/search/SearchThreadIdGuessingAlgorithmTest.java
@@ -19,21 +19,31 @@
 
 package org.apache.james.mailbox.store.search;
 
+import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.inmemory.InMemoryCombinationManagerTestSystem;
+import org.apache.james.mailbox.inmemory.InMemoryMailboxManager;
 import org.apache.james.mailbox.inmemory.InMemoryMessageId;
+import org.apache.james.mailbox.inmemory.mail.InMemoryMapperProvider;
 import org.apache.james.mailbox.inmemory.manager.InMemoryIntegrationResources;
 import org.apache.james.mailbox.model.MessageId;
 import org.apache.james.mailbox.store.CombinationManagerTestSystem;
 import org.apache.james.mailbox.store.ThreadIdGuessingAlgorithmContract;
+import org.apache.james.mailbox.store.mail.MessageMapper;
 import org.apache.james.mailbox.store.mail.SearchThreadIdGuessingAlgorithm;
 import org.apache.james.mailbox.store.mail.ThreadIdGuessingAlgorithm;
+import org.apache.james.mailbox.store.mail.model.MapperProvider;
 
 public class SearchThreadIdGuessingAlgorithmTest extends ThreadIdGuessingAlgorithmContract {
+    private InMemoryMailboxManager mailboxManager;
 
     @Override
     protected CombinationManagerTestSystem createTestingData() {
         InMemoryIntegrationResources resources = InMemoryIntegrationResources.defaultResources();
 
+        mailboxManager = resources.getMailboxManager();
+        eventBus = resources.getEventBus();
+        messageIdFactory = resources.getMessageIdFactory();
+
         return new InMemoryCombinationManagerTestSystem(
             resources.getMailboxManager(),
             resources.getMessageIdManager());
@@ -45,7 +55,22 @@
     }
 
     @Override
+    protected MessageMapper createMessageMapper(MailboxSession mailboxSession) {
+        return mailboxManager.getMapperFactory().createMessageMapper(mailboxSession);
+    }
+
+    @Override
+    protected MapperProvider provideMapper() {
+        return new InMemoryMapperProvider();
+    }
+
+    @Override
     protected MessageId initNewBasedMessageId() {
         return InMemoryMessageId.of(100);
     }
+
+    @Override
+    protected MessageId initOtherBasedMessageId() {
+        return InMemoryMessageId.of(1000);
+    }
 }
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java
index e228e7e..85d47e5 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java
@@ -74,6 +74,7 @@
 import org.apache.james.mailbox.model.MessageRange;
 import org.apache.james.mailbox.model.MultimailboxesSearchQuery;
 import org.apache.james.mailbox.model.QuotaRoot;
+import org.apache.james.mailbox.model.ThreadId;
 import org.apache.james.mailbox.model.UidValidity;
 import org.apache.james.mailbox.model.search.MailboxQuery;
 import org.apache.james.mailbox.model.search.PrefixedWildcard;
@@ -782,14 +783,19 @@
     }
 
     @Override
-    public Flux<MessageId> search(MultimailboxesSearchQuery expression, MailboxSession session, long limit) throws MailboxException {
+    public Flux<MessageId> search(MultimailboxesSearchQuery expression, MailboxSession session, long limit) {
         return getInMailboxIds(expression, session)
             .filter(id -> !expression.getNotInMailboxes().contains(id))
             .collect(Guavate.toImmutableSet())
             .flatMapMany(Throwing.function(ids -> index.search(session, ids, expression.getSearchQuery(), limit)));
     }
 
-    private Flux<MailboxId> getInMailboxIds(MultimailboxesSearchQuery expression, MailboxSession session) throws MailboxException {
+    @Override
+    public Flux<MessageId> getThread(ThreadId threadId, MailboxSession session) {
+        return threadIdGuessingAlgorithm.getMessageIdsInThread(threadId, session);
+    }
+
+    private Flux<MailboxId> getInMailboxIds(MultimailboxesSearchQuery expression, MailboxSession session) {
         if (expression.getInMailboxes().isEmpty()) {
             return accessibleMailboxIds(expression.getNamespace(), Right.Read, session);
         } else {
@@ -799,7 +805,7 @@
         }
     }
 
-    private Flux<Mailbox> filterReadable(ImmutableSet<MailboxId> inMailboxes, MailboxSession session) throws MailboxException {
+    private Flux<Mailbox> filterReadable(ImmutableSet<MailboxId> inMailboxes, MailboxSession session) {
         MailboxMapper mailboxMapper = mailboxSessionMapperFactory.getMailboxMapper(session);
         return Flux.fromIterable(inMailboxes)
             .concatMap(mailboxMapper::findMailboxById)
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/NaiveThreadIdGuessingAlgorithm.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/NaiveThreadIdGuessingAlgorithm.java
index b6334ca..eed7eb2 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/NaiveThreadIdGuessingAlgorithm.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/NaiveThreadIdGuessingAlgorithm.java
@@ -28,6 +28,7 @@
 import org.apache.james.mailbox.store.mail.model.MimeMessageId;
 import org.apache.james.mailbox.store.mail.model.Subject;
 
+import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 
 public class NaiveThreadIdGuessingAlgorithm implements ThreadIdGuessingAlgorithm {
@@ -35,4 +36,9 @@
     public Mono<ThreadId> guessThreadIdReactive(MessageId messageId, Optional<MimeMessageId> thisMimeMessageId, Optional<MimeMessageId> inReplyTo, Optional<List<MimeMessageId>> references, Optional<Subject> subject, MailboxSession session) {
         return Mono.just(ThreadId.fromBaseMessageId(messageId));
     }
+
+    @Override
+    public Flux<MessageId> getMessageIdsInThread(ThreadId threadId, MailboxSession session) {
+        return Flux.just(threadId.getBaseMessageId());
+    }
 }
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/SearchThreadIdGuessingAlgorithm.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/SearchThreadIdGuessingAlgorithm.java
index a229fee..551e737 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/SearchThreadIdGuessingAlgorithm.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/SearchThreadIdGuessingAlgorithm.java
@@ -29,7 +29,7 @@
 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.exception.ThreadNotFoundException;
 import org.apache.james.mailbox.model.FetchGroup;
 import org.apache.james.mailbox.model.MessageId;
 import org.apache.james.mailbox.model.MessageResult;
@@ -56,7 +56,7 @@
     }
 
     @Override
-    public Mono<ThreadId> guessThreadIdReactive(MessageId messageId, Optional<MimeMessageId> mimeMessageId, Optional<MimeMessageId> inReplyTo, Optional<List<MimeMessageId>> references, Optional<Subject> subject, MailboxSession session) throws MailboxException {
+    public Mono<ThreadId> guessThreadIdReactive(MessageId messageId, Optional<MimeMessageId> mimeMessageId, Optional<MimeMessageId> inReplyTo, Optional<List<MimeMessageId>> references, Optional<Subject> subject, MailboxSession session) {
         MultimailboxesSearchQuery expression = buildSearchQuery(mimeMessageId, inReplyTo, references, subject);
 
         return Flux.from(mailboxManager.search(expression, session, 1))
@@ -67,6 +67,21 @@
             .switchIfEmpty(Mono.just(ThreadId.fromBaseMessageId(messageId)));
     }
 
+    @Override
+    public Flux<MessageId> getMessageIdsInThread(ThreadId threadId, MailboxSession session) {
+        SearchQuery searchQuery = SearchQuery.builder()
+            .andCriteria(SearchQuery.threadId(threadId))
+            .sorts(new SearchQuery.Sort(SearchQuery.Sort.SortClause.Arrival, SearchQuery.Sort.Order.NATURAL))
+            .build();
+
+        MultimailboxesSearchQuery expression = MultimailboxesSearchQuery
+            .from(searchQuery)
+            .build();
+
+        return Flux.from(mailboxManager.search(expression, session, Integer.MAX_VALUE))
+            .switchIfEmpty(Mono.error(() -> new ThreadNotFoundException(threadId)));
+    }
+
     private MultimailboxesSearchQuery buildSearchQuery(Optional<MimeMessageId> mimeMessageId, Optional<MimeMessageId> inReplyTo, Optional<List<MimeMessageId>> references, Optional<Subject> subject) {
         Set<MimeMessageId> mimeMessageIds = buildMimeMessageIdSet(mimeMessageId, inReplyTo, references);
 
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/ThreadIdGuessingAlgorithm.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/ThreadIdGuessingAlgorithm.java
index 4461fc9..2b6d91a 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/ThreadIdGuessingAlgorithm.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/ThreadIdGuessingAlgorithm.java
@@ -23,14 +23,16 @@
 import java.util.Optional;
 
 import org.apache.james.mailbox.MailboxSession;
-import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.model.MessageId;
 import org.apache.james.mailbox.model.ThreadId;
 import org.apache.james.mailbox.store.mail.model.MimeMessageId;
 import org.apache.james.mailbox.store.mail.model.Subject;
 
+import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 
 public interface ThreadIdGuessingAlgorithm {
-    Mono<ThreadId> guessThreadIdReactive(MessageId messageId, Optional<MimeMessageId> thisMimeMessageId, Optional<MimeMessageId> inReplyTo, Optional<List<MimeMessageId>> references, Optional<Subject> subject, MailboxSession session) throws MailboxException;
+    Mono<ThreadId> guessThreadIdReactive(MessageId messageId, Optional<MimeMessageId> thisMimeMessageId, Optional<MimeMessageId> inReplyTo, Optional<List<MimeMessageId>> references, Optional<Subject> subject, MailboxSession session);
+
+    Flux<MessageId> getMessageIdsInThread(ThreadId threadId, MailboxSession session);
 }
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/ThreadIdGuessingAlgorithmContract.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/ThreadIdGuessingAlgorithmContract.java
index c28757c..c54ee33 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/ThreadIdGuessingAlgorithmContract.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/ThreadIdGuessingAlgorithmContract.java
@@ -20,22 +20,40 @@
 package org.apache.james.mailbox.store;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 import java.nio.charset.StandardCharsets;
+import java.util.Date;
 import java.util.List;
 import java.util.Optional;
 import java.util.stream.Stream;
 
+import javax.mail.Flags;
+
 import org.apache.james.core.Username;
+import org.apache.james.events.EventBus;
 import org.apache.james.mailbox.MailboxManager;
 import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.MessageManager;
+import org.apache.james.mailbox.events.MailboxIdRegistrationKey;
+import org.apache.james.mailbox.exception.MailboxException;
+import org.apache.james.mailbox.exception.ThreadNotFoundException;
+import org.apache.james.mailbox.model.ByteContent;
+import org.apache.james.mailbox.model.Mailbox;
 import org.apache.james.mailbox.model.MailboxPath;
 import org.apache.james.mailbox.model.MessageId;
+import org.apache.james.mailbox.model.MessageMetaData;
 import org.apache.james.mailbox.model.ThreadId;
+import org.apache.james.mailbox.model.UidValidity;
+import org.apache.james.mailbox.store.event.EventFactory;
+import org.apache.james.mailbox.store.mail.MessageMapper;
 import org.apache.james.mailbox.store.mail.ThreadIdGuessingAlgorithm;
+import org.apache.james.mailbox.store.mail.model.MailboxMessage;
+import org.apache.james.mailbox.store.mail.model.MapperProvider;
 import org.apache.james.mailbox.store.mail.model.MimeMessageId;
 import org.apache.james.mailbox.store.mail.model.Subject;
+import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder;
+import org.apache.james.mailbox.store.mail.model.impl.SimpleMailboxMessage;
 import org.apache.james.mime4j.dom.Message;
 import org.apache.james.mime4j.stream.RawField;
 import org.junit.jupiter.api.BeforeEach;
@@ -44,35 +62,55 @@
 import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
 
+import com.google.common.collect.ImmutableList;
+
+import reactor.core.publisher.Flux;
+
 public abstract class ThreadIdGuessingAlgorithmContract {
     public static final Username USER = Username.of("quan");
+    private static final UidValidity UID_VALIDITY = UidValidity.of(42);
+
+    protected EventBus eventBus;
+    protected MessageId.Factory messageIdFactory;
     private MailboxManager mailboxManager;
     private MessageManager inbox;
+    private MessageMapper messageMapper;
     private ThreadIdGuessingAlgorithm testee;
     private MailboxSession mailboxSession;
     private CombinationManagerTestSystem testingData;
     private MessageId newBasedMessageId;
+    private MessageId otherBasedMessageId;
+    private Mailbox mailbox;
 
     protected abstract CombinationManagerTestSystem createTestingData();
 
     protected abstract ThreadIdGuessingAlgorithm initThreadIdGuessingAlgorithm(CombinationManagerTestSystem testingData);
 
+    protected abstract MessageMapper createMessageMapper(MailboxSession mailboxSession);
+
+    protected abstract MapperProvider provideMapper();
+
     protected abstract MessageId initNewBasedMessageId();
 
+    protected abstract MessageId initOtherBasedMessageId();
+
     @BeforeEach
     void setUp() throws Exception {
         testingData = createTestingData();
         testee = initThreadIdGuessingAlgorithm(testingData);
         newBasedMessageId = initNewBasedMessageId();
+        otherBasedMessageId = initOtherBasedMessageId();
 
         mailboxManager = testingData.getMailboxManager();
         mailboxSession = mailboxManager.createSystemSession(USER);
         mailboxManager.createMailbox(MailboxPath.inbox(USER), mailboxSession);
+        messageMapper = createMessageMapper(mailboxSession);
         inbox = mailboxManager.getMailbox(MailboxPath.inbox(USER), mailboxSession);
+        mailbox = inbox.getMailboxEntity();
     }
 
     @Test
-    void givenNonMailWhenAddAMailThenGuessingThreadIdShouldBasedOnGeneratedMessageId() throws Exception {
+    void givenNonMailWhenAddAMailThenGuessingThreadIdShouldBasedOnGeneratedMessageId() {
         ThreadId threadId = testee.guessThreadIdReactive(newBasedMessageId, Optional.of(new MimeMessageId("abc")), Optional.empty(), Optional.empty(), Optional.of(new Subject("test")), mailboxSession).block();
 
         assertThat(threadId.getBaseMessageId()).isEqualTo(newBasedMessageId);
@@ -202,4 +240,86 @@
         assertThat(threadId.getBaseMessageId()).isEqualTo(newBasedMessageId);
     }
 
+    @Test
+    void givenThreeMailsInAThreadThenGetThreadShouldReturnAListWithThreeMessageIdsSortedByArrivalDate() throws MailboxException {
+        MailboxMessage message1 = createMessage(mailbox, ThreadId.fromBaseMessageId(newBasedMessageId));
+        MailboxMessage message2 = createMessage(mailbox, ThreadId.fromBaseMessageId(newBasedMessageId));
+        MailboxMessage message3 = createMessage(mailbox, ThreadId.fromBaseMessageId(newBasedMessageId));
+
+        appendMessageThenDispatchAddedEvent(mailbox, message1);
+        appendMessageThenDispatchAddedEvent(mailbox, message2);
+        appendMessageThenDispatchAddedEvent(mailbox, message3);
+
+        Flux<MessageId> messageIds = testee.getMessageIdsInThread(ThreadId.fromBaseMessageId(newBasedMessageId), mailboxSession);
+
+        assertThat(messageIds.collectList().block())
+            .isEqualTo(ImmutableList.of(message1.getMessageId(), message2.getMessageId(), message3.getMessageId()));
+    }
+
+    @Test
+    void givenNonMailInAThreadThenGetThreadShouldThrowThreadNotFoundException() {
+        Flux<MessageId> messageIds = testee.getMessageIdsInThread(ThreadId.fromBaseMessageId(newBasedMessageId), mailboxSession);
+
+        assertThatThrownBy(() -> testee.getMessageIdsInThread(ThreadId.fromBaseMessageId(newBasedMessageId), mailboxSession).collectList().block())
+            .getCause()
+            .isInstanceOf(ThreadNotFoundException.class);
+    }
+
+    @Test
+    void givenAMailInAThreadThenGetThreadShouldReturnAListWithOnlyOneMessageIdInThatThread() throws MailboxException {
+        MailboxMessage message1 = createMessage(mailbox, ThreadId.fromBaseMessageId(newBasedMessageId));
+
+        appendMessageThenDispatchAddedEvent(mailbox, message1);
+
+        Flux<MessageId> messageIds = testee.getMessageIdsInThread(ThreadId.fromBaseMessageId(newBasedMessageId), mailboxSession);
+
+        assertThat(messageIds.collectList().block())
+            .containsOnly(message1.getMessageId());
+    }
+
+    @Test
+    void givenTwoDistinctThreadsThenGetThreadShouldNotReturnUnrelatedMails() throws MailboxException {
+        // given message1 and message2 in thread1, message3 in thread2
+        ThreadId threadId1 = ThreadId.fromBaseMessageId(newBasedMessageId);
+        ThreadId threadId2 = ThreadId.fromBaseMessageId(otherBasedMessageId);
+        MailboxMessage message1 = createMessage(mailbox, threadId1);
+        MailboxMessage message2 = createMessage(mailbox, threadId1);
+        MailboxMessage message3 = createMessage(mailbox, threadId2);
+
+        appendMessageThenDispatchAddedEvent(mailbox, message1);
+        appendMessageThenDispatchAddedEvent(mailbox, message2);
+        appendMessageThenDispatchAddedEvent(mailbox, message3);
+
+        // then get thread2 should not return unrelated message1 and message2
+        Flux<MessageId> messageIds = testee.getMessageIdsInThread(ThreadId.fromBaseMessageId(otherBasedMessageId), mailboxSession);
+
+        assertThat(messageIds.collectList().block())
+            .doesNotContain(message1.getMessageId(), message2.getMessageId());
+    }
+
+    private SimpleMailboxMessage createMessage(Mailbox mailbox, ThreadId threadId) {
+        MessageId messageId = messageIdFactory.generate();
+        String content = "Some content";
+        int bodyStart = 16;
+        return new SimpleMailboxMessage(messageId,
+            threadId,
+            new Date(),
+            content.length(),
+            bodyStart,
+            new ByteContent(content.getBytes()),
+            new Flags(),
+            new PropertyBuilder().build(),
+            mailbox.getMailboxId());
+    }
+
+    private void appendMessageThenDispatchAddedEvent(Mailbox mailbox, MailboxMessage mailboxMessage) throws MailboxException {
+        MessageMetaData messageMetaData = messageMapper.add(mailbox, mailboxMessage);
+        eventBus.dispatch(EventFactory.added()
+                .randomEventId()
+                .mailboxSession(mailboxSession)
+                .mailbox(mailbox)
+                .addMetaData(messageMetaData)
+                .build(),
+            new MailboxIdRegistrationKey(mailbox.getMailboxId())).block();
+    }
 }
diff --git a/mdn/pom.xml b/mdn/pom.xml
index 735a372..24b9687 100644
--- a/mdn/pom.xml
+++ b/mdn/pom.xml
@@ -90,6 +90,13 @@
                 <groupId>net.alchim31.maven</groupId>
                 <artifactId>scala-maven-plugin</artifactId>
             </plugin>
+            <plugin>
+                <groupId>io.github.evis</groupId>
+                <artifactId>scalafix-maven-plugin</artifactId>
+                <configuration>
+                    <config>${project.parent.basedir}/.scalafix.conf</config>
+                </configuration>
+            </plugin>
         </plugins>
     </build>
 
diff --git a/pom.xml b/pom.xml
index 2009d47..5ed7565 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1358,6 +1358,12 @@
             </dependency>
             <dependency>
                 <groupId>${james.groupId}</groupId>
+                <artifactId>james-server-data-ldap</artifactId>
+                <version>${project.version}</version>
+                <type>test-jar</type>
+            </dependency>
+            <dependency>
+                <groupId>${james.groupId}</groupId>
                 <artifactId>james-server-data-library</artifactId>
                 <version>${project.version}</version>
             </dependency>
@@ -2819,6 +2825,24 @@
                     <version>2.7.1</version>
                 </plugin>
                 <plugin>
+                    <groupId>io.github.evis</groupId>
+                    <artifactId>scalafix-maven-plugin</artifactId>
+                    <version>0.1.4_0.9.23</version>
+                    <configuration>
+                        <config>${basedir}/.scalafix.conf</config>
+                        <mode>CHECK</mode>
+                    </configuration>
+                    <executions>
+                        <execution>
+                            <id>scala-check-style</id>
+                            <goals>
+                                <goal>scalafix</goal>
+                            </goals>
+                            <phase>compile</phase>
+                        </execution>
+                    </executions>
+                </plugin>
+                <plugin>
                     <groupId>org.scalatest</groupId>
                     <artifactId>scalatest-maven-plugin</artifactId>
                     <version>2.0.0</version>
@@ -2847,7 +2871,15 @@
                             <arg>-unchecked</arg>
                             <arg>-deprecation</arg>
                             <arg>-explaintypes</arg>
+                            <arg>-Ywarn-unused</arg>
                         </args>
+                        <compilerPlugins>
+                            <compilerPlugin>
+                                <groupId>org.scalameta</groupId>
+                                <artifactId>semanticdb-scalac_${scala.version}</artifactId>
+                                <version>4.4.23</version>
+                            </compilerPlugin>
+                        </compilerPlugins>
                     </configuration>
                     <executions>
                         <execution>
diff --git a/server/apps/cassandra-app/pom.xml b/server/apps/cassandra-app/pom.xml
index 51d93b5..11dd2a8 100644
--- a/server/apps/cassandra-app/pom.xml
+++ b/server/apps/cassandra-app/pom.xml
@@ -106,7 +106,6 @@
         <dependency>
             <groupId>${james.groupId}</groupId>
             <artifactId>james-server-data-ldap</artifactId>
-            <version>${project.version}</version>
             <type>test-jar</type>
             <scope>test</scope>
         </dependency>
@@ -126,6 +125,12 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-guice-data-ldap</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
             <artifactId>james-server-guice-elasticsearch</artifactId>
         </dependency>
         <dependency>
diff --git a/server/apps/cassandra-app/src/test/java/org/apache/james/CassandraLdapJamesServerTest.java b/server/apps/cassandra-app/src/test/java/org/apache/james/CassandraLdapJamesServerTest.java
index 0c3a91a..b248f47 100644
--- a/server/apps/cassandra-app/src/test/java/org/apache/james/CassandraLdapJamesServerTest.java
+++ b/server/apps/cassandra-app/src/test/java/org/apache/james/CassandraLdapJamesServerTest.java
@@ -29,6 +29,7 @@
 
 import org.apache.commons.net.imap.IMAPClient;
 import org.apache.james.core.Domain;
+import org.apache.james.data.LdapTestExtension;
 import org.apache.james.data.UsersRepositoryModuleChooser;
 import org.apache.james.modules.TestJMAPServerModule;
 import org.apache.james.modules.protocols.ImapGuiceProbe;
diff --git a/server/apps/cassandra-app/src/test/java/org/apache/james/CassandraLdapJmapJamesServerTest.java b/server/apps/cassandra-app/src/test/java/org/apache/james/CassandraLdapJmapJamesServerTest.java
index 536fabc..6bcb98e 100644
--- a/server/apps/cassandra-app/src/test/java/org/apache/james/CassandraLdapJmapJamesServerTest.java
+++ b/server/apps/cassandra-app/src/test/java/org/apache/james/CassandraLdapJmapJamesServerTest.java
@@ -19,6 +19,7 @@
 
 package org.apache.james;
 
+import org.apache.james.data.LdapTestExtension;
 import org.apache.james.data.UsersRepositoryModuleChooser;
 import org.apache.james.jmap.draft.JmapJamesServerContract;
 import org.apache.james.modules.TestJMAPServerModule;
diff --git a/server/apps/distributed-app/pom.xml b/server/apps/distributed-app/pom.xml
index acfaaf7..9e90b98 100644
--- a/server/apps/distributed-app/pom.xml
+++ b/server/apps/distributed-app/pom.xml
@@ -141,7 +141,6 @@
         <dependency>
             <groupId>${james.groupId}</groupId>
             <artifactId>james-server-data-ldap</artifactId>
-            <version>${project.version}</version>
             <type>test-jar</type>
             <scope>test</scope>
         </dependency>
@@ -161,6 +160,12 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-guice-data-ldap</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
             <artifactId>james-server-guice-distributed</artifactId>
         </dependency>
         <dependency>
diff --git a/server/apps/distributed-app/src/test/java/org/apache/james/CassandraRabbitMQLdapJmapJamesServerTest.java b/server/apps/distributed-app/src/test/java/org/apache/james/CassandraRabbitMQLdapJmapJamesServerTest.java
index 623d15e..40b6f4b 100644
--- a/server/apps/distributed-app/src/test/java/org/apache/james/CassandraRabbitMQLdapJmapJamesServerTest.java
+++ b/server/apps/distributed-app/src/test/java/org/apache/james/CassandraRabbitMQLdapJmapJamesServerTest.java
@@ -25,6 +25,7 @@
 import java.io.IOException;
 
 import org.apache.commons.net.imap.IMAPClient;
+import org.apache.james.data.LdapTestExtension;
 import org.apache.james.data.UsersRepositoryModuleChooser;
 import org.apache.james.jmap.draft.JmapJamesServerContract;
 import org.apache.james.modules.AwsS3BlobStoreExtension;
diff --git a/server/apps/jpa-app/pom.xml b/server/apps/jpa-app/pom.xml
index cc395da..7727845 100644
--- a/server/apps/jpa-app/pom.xml
+++ b/server/apps/jpa-app/pom.xml
@@ -72,6 +72,12 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-data-ldap</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
             <artifactId>james-server-guice-common</artifactId>
         </dependency>
         <dependency>
@@ -82,6 +88,16 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-guice-data-ldap</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-guice-data-ldap</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
             <artifactId>james-server-guice-es-resporter</artifactId>
         </dependency>
         <dependency>
diff --git a/server/apps/jpa-app/src/main/java/org/apache/james/JPAJamesConfiguration.java b/server/apps/jpa-app/src/main/java/org/apache/james/JPAJamesConfiguration.java
new file mode 100644
index 0000000..de35304
--- /dev/null
+++ b/server/apps/jpa-app/src/main/java/org/apache/james/JPAJamesConfiguration.java
@@ -0,0 +1,127 @@
+/****************************************************************
+ * 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;
+
+import java.io.File;
+import java.util.Optional;
+
+import org.apache.james.data.UsersRepositoryModuleChooser;
+import org.apache.james.filesystem.api.FileSystem;
+import org.apache.james.filesystem.api.JamesDirectoriesProvider;
+import org.apache.james.server.core.JamesServerResourceLoader;
+import org.apache.james.server.core.MissingArgumentException;
+import org.apache.james.server.core.configuration.Configuration;
+import org.apache.james.server.core.configuration.FileConfigurationProvider;
+import org.apache.james.server.core.filesystem.FileSystemImpl;
+
+public class JPAJamesConfiguration implements Configuration {
+    public static class Builder {
+        private Optional<String> rootDirectory;
+        private Optional<ConfigurationPath> configurationPath;
+        private Optional<UsersRepositoryModuleChooser.Implementation> usersRepositoryImplementation;
+
+        private Builder() {
+            rootDirectory = Optional.empty();
+            configurationPath = Optional.empty();
+            usersRepositoryImplementation = Optional.empty();
+        }
+
+        public Builder workingDirectory(String path) {
+            rootDirectory = Optional.of(path);
+            return this;
+        }
+
+        public Builder workingDirectory(File file) {
+            rootDirectory = Optional.of(file.getAbsolutePath());
+            return this;
+        }
+
+        public Builder useWorkingDirectoryEnvProperty() {
+            rootDirectory = Optional.ofNullable(System.getProperty(WORKING_DIRECTORY));
+            if (!rootDirectory.isPresent()) {
+                throw new MissingArgumentException("Server needs a working.directory env entry");
+            }
+            return this;
+        }
+
+        public Builder configurationPath(ConfigurationPath path) {
+            configurationPath = Optional.of(path);
+            return this;
+        }
+
+        public Builder configurationFromClasspath() {
+            configurationPath = Optional.of(new ConfigurationPath(FileSystem.CLASSPATH_PROTOCOL));
+            return this;
+        }
+
+        public Builder usersRepository(UsersRepositoryModuleChooser.Implementation implementation) {
+            this.usersRepositoryImplementation = Optional.of(implementation);
+            return this;
+        }
+
+        public JPAJamesConfiguration build() {
+            ConfigurationPath configurationPath = this.configurationPath.orElse(new ConfigurationPath(FileSystem.FILE_PROTOCOL_AND_CONF));
+            JamesServerResourceLoader directories = new JamesServerResourceLoader(rootDirectory
+                .orElseThrow(() -> new MissingArgumentException("Server needs a working.directory env entry")));
+
+            FileSystemImpl fileSystem = new FileSystemImpl(directories);
+
+            FileConfigurationProvider configurationProvider = new FileConfigurationProvider(fileSystem, Basic.builder()
+                .configurationPath(configurationPath)
+                .workingDirectory(directories.getRootDirectory())
+                .build());
+            UsersRepositoryModuleChooser.Implementation usersRepositoryChoice = usersRepositoryImplementation.orElseGet(
+                () -> UsersRepositoryModuleChooser.Implementation.parse(configurationProvider));
+
+            return new JPAJamesConfiguration(
+                configurationPath,
+                directories,
+                usersRepositoryChoice);
+        }
+    }
+
+    public static JPAJamesConfiguration.Builder builder() {
+        return new Builder();
+    }
+
+    private final ConfigurationPath configurationPath;
+    private final JamesDirectoriesProvider directories;
+    private final UsersRepositoryModuleChooser.Implementation usersRepositoryImplementation;
+
+    public JPAJamesConfiguration(ConfigurationPath configurationPath, JamesDirectoriesProvider directories, UsersRepositoryModuleChooser.Implementation usersRepositoryImplementation) {
+        this.configurationPath = configurationPath;
+        this.directories = directories;
+        this.usersRepositoryImplementation = usersRepositoryImplementation;
+    }
+
+    @Override
+    public ConfigurationPath configurationPath() {
+        return configurationPath;
+    }
+
+    @Override
+    public JamesDirectoriesProvider directories() {
+        return directories;
+    }
+
+    public UsersRepositoryModuleChooser.Implementation getUsersRepositoryImplementation() {
+        return usersRepositoryImplementation;
+    }
+}
diff --git a/server/apps/jpa-app/src/main/java/org/apache/james/JPAJamesServerMain.java b/server/apps/jpa-app/src/main/java/org/apache/james/JPAJamesServerMain.java
index 7b33cb6..a840ee6 100644
--- a/server/apps/jpa-app/src/main/java/org/apache/james/JPAJamesServerMain.java
+++ b/server/apps/jpa-app/src/main/java/org/apache/james/JPAJamesServerMain.java
@@ -19,9 +19,11 @@
 
 package org.apache.james;
 
+import org.apache.james.data.UsersRepositoryModuleChooser;
 import org.apache.james.modules.MailboxModule;
 import org.apache.james.modules.MailetProcessingModule;
 import org.apache.james.modules.data.JPADataModule;
+import org.apache.james.modules.data.JPAUsersRepositoryModule;
 import org.apache.james.modules.data.SieveJPARepositoryModules;
 import org.apache.james.modules.mailbox.DefaultEventModule;
 import org.apache.james.modules.mailbox.JPAMailboxModule;
@@ -51,7 +53,6 @@
 import org.apache.james.modules.server.WebAdminReIndexingTaskSerializationModule;
 import org.apache.james.modules.server.WebAdminServerModule;
 import org.apache.james.modules.spamassassin.SpamAssassinListenerModule;
-import org.apache.james.server.core.configuration.Configuration;
 
 import com.google.inject.Module;
 import com.google.inject.util.Modules;
@@ -99,7 +100,7 @@
         new MailetProcessingModule(), JPA_SERVER_MODULE, PROTOCOLS);
 
     public static void main(String[] args) throws Exception {
-        Configuration configuration = Configuration.builder()
+        JPAJamesConfiguration configuration = JPAJamesConfiguration.builder()
             .useWorkingDirectoryEnvProperty()
             .build();
 
@@ -110,9 +111,10 @@
         JamesServerMain.main(server);
     }
 
-    static GuiceJamesServer createServer(Configuration configuration) {
+    static GuiceJamesServer createServer(JPAJamesConfiguration configuration) {
         return GuiceJamesServer.forConfiguration(configuration)
-            .combineWith(JPA_MODULE_AGGREGATE);
+            .combineWith(JPA_MODULE_AGGREGATE)
+            .combineWith(new UsersRepositoryModuleChooser(new JPAUsersRepositoryModule())
+                .chooseModules(configuration.getUsersRepositoryImplementation()));
     }
-
 }
diff --git a/server/apps/jpa-app/src/test/java/org/apache/james/JPAJamesServerTest.java b/server/apps/jpa-app/src/test/java/org/apache/james/JPAJamesServerTest.java
index 9cd0cd6..db1284b 100644
--- a/server/apps/jpa-app/src/test/java/org/apache/james/JPAJamesServerTest.java
+++ b/server/apps/jpa-app/src/test/java/org/apache/james/JPAJamesServerTest.java
@@ -19,6 +19,7 @@
 
 package org.apache.james;
 
+import static org.apache.james.data.UsersRepositoryModuleChooser.Implementation.DEFAULT;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.awaitility.Durations.FIVE_HUNDRED_MILLISECONDS;
 import static org.awaitility.Durations.ONE_MINUTE;
@@ -41,7 +42,12 @@
 class JPAJamesServerTest implements JamesServerContract {
 
     @RegisterExtension
-    static JamesServerExtension jamesServerExtension = new JamesServerBuilder<>(JamesServerBuilder.defaultConfigurationProvider())
+    static JamesServerExtension jamesServerExtension = new JamesServerBuilder<JPAJamesConfiguration>(tmpDir ->
+        JPAJamesConfiguration.builder()
+            .workingDirectory(tmpDir)
+            .configurationFromClasspath()
+            .usersRepository(DEFAULT)
+            .build())
         .server(configuration -> JPAJamesServerMain.createServer(configuration)
             .overrideWith(new TestJPAConfigurationModule()))
         .lifeCycle(JamesServerExtension.Lifecycle.PER_CLASS)
diff --git a/server/apps/jpa-app/src/test/java/org/apache/james/JPAJamesServerWithAuthenticatedDatabaseSqlValidationTest.java b/server/apps/jpa-app/src/test/java/org/apache/james/JPAJamesServerWithAuthenticatedDatabaseSqlValidationTest.java
index d8359e4..95f14b4 100644
--- a/server/apps/jpa-app/src/test/java/org/apache/james/JPAJamesServerWithAuthenticatedDatabaseSqlValidationTest.java
+++ b/server/apps/jpa-app/src/test/java/org/apache/james/JPAJamesServerWithAuthenticatedDatabaseSqlValidationTest.java
@@ -19,12 +19,19 @@
 
 package org.apache.james;
 
+import static org.apache.james.data.UsersRepositoryModuleChooser.Implementation.DEFAULT;
+
 import org.junit.jupiter.api.extension.RegisterExtension;
 
 class JPAJamesServerWithAuthenticatedDatabaseSqlValidationTest extends JPAJamesServerWithSqlValidationTest {
 
     @RegisterExtension
-    static JamesServerExtension jamesServerExtension = new JamesServerBuilder<>(JamesServerBuilder.defaultConfigurationProvider())
+    static JamesServerExtension jamesServerExtension = new JamesServerBuilder<JPAJamesConfiguration>(tmpDir ->
+        JPAJamesConfiguration.builder()
+            .workingDirectory(tmpDir)
+            .configurationFromClasspath()
+            .usersRepository(DEFAULT)
+            .build())
         .server(configuration -> JPAJamesServerMain.createServer(configuration)
             .overrideWith(new TestJPAConfigurationModuleWithSqlValidation.WithDatabaseAuthentication()))
         .lifeCycle(JamesServerExtension.Lifecycle.PER_CLASS)
diff --git a/server/apps/jpa-app/src/test/java/org/apache/james/JPAJamesServerWithNoDatabaseAuthenticaticationSqlValidationTest.java b/server/apps/jpa-app/src/test/java/org/apache/james/JPAJamesServerWithNoDatabaseAuthenticaticationSqlValidationTest.java
index 89f4dc0..2581b10 100644
--- a/server/apps/jpa-app/src/test/java/org/apache/james/JPAJamesServerWithNoDatabaseAuthenticaticationSqlValidationTest.java
+++ b/server/apps/jpa-app/src/test/java/org/apache/james/JPAJamesServerWithNoDatabaseAuthenticaticationSqlValidationTest.java
@@ -19,12 +19,18 @@
 
 package org.apache.james;
 
+import static org.apache.james.data.UsersRepositoryModuleChooser.Implementation.DEFAULT;
+
 import org.junit.jupiter.api.extension.RegisterExtension;
 
 class JPAJamesServerWithNoDatabaseAuthenticaticationSqlValidationTest extends JPAJamesServerWithSqlValidationTest {
-
     @RegisterExtension
-    static JamesServerExtension jamesServerExtension = new JamesServerBuilder<>(JamesServerBuilder.defaultConfigurationProvider())
+    static JamesServerExtension jamesServerExtension = new JamesServerBuilder<JPAJamesConfiguration>(tmpDir ->
+        JPAJamesConfiguration.builder()
+            .workingDirectory(tmpDir)
+            .configurationFromClasspath()
+            .usersRepository(DEFAULT)
+            .build())
         .server(configuration -> JPAJamesServerMain.createServer(configuration)
             .overrideWith(new TestJPAConfigurationModuleWithSqlValidation.NoDatabaseAuthentication()))
         .lifeCycle(JamesServerExtension.Lifecycle.PER_CLASS)
diff --git a/server/apps/jpa-app/src/test/java/org/apache/james/JPAWithLDAPJamesServerTest.java b/server/apps/jpa-app/src/test/java/org/apache/james/JPAWithLDAPJamesServerTest.java
new file mode 100644
index 0000000..8d86124
--- /dev/null
+++ b/server/apps/jpa-app/src/test/java/org/apache/james/JPAWithLDAPJamesServerTest.java
@@ -0,0 +1,57 @@
+/****************************************************************
+ * 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;
+
+import static org.apache.james.MailsShouldBeWellReceived.JAMES_SERVER_HOST;
+import static org.apache.james.data.UsersRepositoryModuleChooser.Implementation.LDAP;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.IOException;
+
+import org.apache.commons.net.imap.IMAPClient;
+import org.apache.james.data.LdapTestExtension;
+import org.apache.james.modules.protocols.ImapGuiceProbe;
+import org.apache.james.user.ldap.DockerLdapSingleton;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+class JPAWithLDAPJamesServerTest {
+    @RegisterExtension
+    static JamesServerExtension jamesServerExtension = new JamesServerBuilder<JPAJamesConfiguration>(tmpDir ->
+        JPAJamesConfiguration.builder()
+            .workingDirectory(tmpDir)
+            .configurationFromClasspath()
+            .usersRepository(LDAP)
+            .build())
+        .server(configuration -> JPAJamesServerMain.createServer(configuration)
+            .overrideWith(new TestJPAConfigurationModule()))
+        .lifeCycle(JamesServerExtension.Lifecycle.PER_CLASS)
+        .extension(new LdapTestExtension())
+        .build();
+
+
+    @Test
+    void userFromLdapShouldLoginViaImapProtocol(GuiceJamesServer server) throws IOException {
+        IMAPClient imapClient = new IMAPClient();
+        imapClient.connect(JAMES_SERVER_HOST, server.getProbe(ImapGuiceProbe.class).getImapPort());
+
+        assertThat(imapClient.login(DockerLdapSingleton.JAMES_USER.asString(), DockerLdapSingleton.PASSWORD)).isTrue();
+    }
+}
diff --git a/server/apps/jpa-app/src/test/java/org/apache/james/JamesCapabilitiesServerTest.java b/server/apps/jpa-app/src/test/java/org/apache/james/JamesCapabilitiesServerTest.java
index 3150e58..c08f88c 100644
--- a/server/apps/jpa-app/src/test/java/org/apache/james/JamesCapabilitiesServerTest.java
+++ b/server/apps/jpa-app/src/test/java/org/apache/james/JamesCapabilitiesServerTest.java
@@ -18,6 +18,7 @@
  ****************************************************************/
 package org.apache.james;
 
+import static org.apache.james.data.UsersRepositoryModuleChooser.Implementation.DEFAULT;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -40,7 +41,12 @@
     }
 
     @RegisterExtension
-    static JamesServerExtension jamesServerExtension = new JamesServerBuilder<>(JamesServerBuilder.defaultConfigurationProvider())
+    static JamesServerExtension jamesServerExtension = new JamesServerBuilder<JPAJamesConfiguration>(tmpDir ->
+        JPAJamesConfiguration.builder()
+            .workingDirectory(tmpDir)
+            .configurationFromClasspath()
+            .usersRepository(DEFAULT)
+            .build())
         .server(configuration -> JPAJamesServerMain.createServer(configuration)
             .overrideWith(new TestJPAConfigurationModule())
             .overrideWith(binder -> binder.bind(MailboxManager.class).toInstance(mailboxManager())))
diff --git a/server/apps/jpa-smtp-app/pom.xml b/server/apps/jpa-smtp-app/pom.xml
index ced7ff6..84c8cee 100644
--- a/server/apps/jpa-smtp-app/pom.xml
+++ b/server/apps/jpa-smtp-app/pom.xml
@@ -69,6 +69,13 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-data-ldap</artifactId>
+            <version>${project.version}</version>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
             <artifactId>james-server-guice-common</artifactId>
         </dependency>
         <dependency>
@@ -79,6 +86,10 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-guice-data-ldap</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
             <artifactId>james-server-guice-es-resporter</artifactId>
         </dependency>
         <dependency>
diff --git a/server/apps/jpa-smtp-app/src/main/java/org/apache/james/JPAJamesConfiguration.java b/server/apps/jpa-smtp-app/src/main/java/org/apache/james/JPAJamesConfiguration.java
new file mode 100644
index 0000000..9614230
--- /dev/null
+++ b/server/apps/jpa-smtp-app/src/main/java/org/apache/james/JPAJamesConfiguration.java
@@ -0,0 +1,127 @@
+/****************************************************************
+ * 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;
+
+import java.io.File;
+import java.util.Optional;
+
+import org.apache.james.data.UsersRepositoryModuleChooser;
+import org.apache.james.filesystem.api.FileSystem;
+import org.apache.james.filesystem.api.JamesDirectoriesProvider;
+import org.apache.james.server.core.JamesServerResourceLoader;
+import org.apache.james.server.core.MissingArgumentException;
+import org.apache.james.server.core.configuration.Configuration;
+import org.apache.james.server.core.configuration.FileConfigurationProvider;
+import org.apache.james.server.core.filesystem.FileSystemImpl;
+
+public class JPAJamesConfiguration implements Configuration {
+    public static class Builder {
+        private Optional<String> rootDirectory;
+        private Optional<ConfigurationPath> configurationPath;
+        private Optional<UsersRepositoryModuleChooser.Implementation> usersRepositoryImplementation;
+
+        private Builder() {
+            rootDirectory = Optional.empty();
+            configurationPath = Optional.empty();
+            usersRepositoryImplementation = Optional.empty();
+        }
+
+        public Builder workingDirectory(String path) {
+            rootDirectory = Optional.of(path);
+            return this;
+        }
+
+        public Builder workingDirectory(File file) {
+            rootDirectory = Optional.of(file.getAbsolutePath());
+            return this;
+        }
+
+        public Builder useWorkingDirectoryEnvProperty() {
+            rootDirectory = Optional.ofNullable(System.getProperty(WORKING_DIRECTORY));
+            if (!rootDirectory.isPresent()) {
+                throw new MissingArgumentException("Server needs a working.directory env entry");
+            }
+            return this;
+        }
+
+        public Builder configurationPath(ConfigurationPath path) {
+            configurationPath = Optional.of(path);
+            return this;
+        }
+
+        public Builder configurationFromClasspath() {
+            configurationPath = Optional.of(new ConfigurationPath(FileSystem.CLASSPATH_PROTOCOL));
+            return this;
+        }
+
+        public Builder usersRepository(UsersRepositoryModuleChooser.Implementation implementation) {
+            this.usersRepositoryImplementation = Optional.of(implementation);
+            return this;
+        }
+
+        public JPAJamesConfiguration build() {
+            ConfigurationPath configurationPath = this.configurationPath.orElse(new ConfigurationPath(FileSystem.FILE_PROTOCOL_AND_CONF));
+            JamesServerResourceLoader directories = new JamesServerResourceLoader(rootDirectory
+                .orElseThrow(() -> new MissingArgumentException("Server needs a working.directory env entry")));
+
+            FileSystemImpl fileSystem = new FileSystemImpl(directories);
+
+            FileConfigurationProvider configurationProvider = new FileConfigurationProvider(fileSystem, Basic.builder()
+                .configurationPath(configurationPath)
+                .workingDirectory(directories.getRootDirectory())
+                .build());
+            UsersRepositoryModuleChooser.Implementation usersRepositoryChoice = usersRepositoryImplementation.orElseGet(
+                () -> UsersRepositoryModuleChooser.Implementation.parse(configurationProvider));
+
+            return new JPAJamesConfiguration(
+                configurationPath,
+                directories,
+                usersRepositoryChoice);
+        }
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    private final ConfigurationPath configurationPath;
+    private final JamesDirectoriesProvider directories;
+    private final UsersRepositoryModuleChooser.Implementation usersRepositoryImplementation;
+
+    public JPAJamesConfiguration(ConfigurationPath configurationPath, JamesDirectoriesProvider directories, UsersRepositoryModuleChooser.Implementation usersRepositoryImplementation) {
+        this.configurationPath = configurationPath;
+        this.directories = directories;
+        this.usersRepositoryImplementation = usersRepositoryImplementation;
+    }
+
+    @Override
+    public ConfigurationPath configurationPath() {
+        return configurationPath;
+    }
+
+    @Override
+    public JamesDirectoriesProvider directories() {
+        return directories;
+    }
+
+    public UsersRepositoryModuleChooser.Implementation getUsersRepositoryImplementation() {
+        return usersRepositoryImplementation;
+    }
+}
diff --git a/server/apps/jpa-smtp-app/src/main/java/org/apache/james/JPAJamesServerMain.java b/server/apps/jpa-smtp-app/src/main/java/org/apache/james/JPAJamesServerMain.java
index bc6d1fd..a7f684c 100644
--- a/server/apps/jpa-smtp-app/src/main/java/org/apache/james/JPAJamesServerMain.java
+++ b/server/apps/jpa-smtp-app/src/main/java/org/apache/james/JPAJamesServerMain.java
@@ -19,9 +19,11 @@
 
 package org.apache.james;
 
+import org.apache.james.data.UsersRepositoryModuleChooser;
 import org.apache.james.modules.MailetProcessingModule;
 import org.apache.james.modules.data.JPADataModule;
 import org.apache.james.modules.data.JPAEntityManagerModule;
+import org.apache.james.modules.data.JPAUsersRepositoryModule;
 import org.apache.james.modules.protocols.ProtocolHandlerModule;
 import org.apache.james.modules.protocols.SMTPServerModule;
 import org.apache.james.modules.queue.activemq.ActiveMQQueueModule;
@@ -35,7 +37,6 @@
 import org.apache.james.modules.server.RawPostDequeueDecoratorModule;
 import org.apache.james.modules.server.TaskManagerModule;
 import org.apache.james.modules.server.WebAdminServerModule;
-import org.apache.james.server.core.configuration.Configuration;
 
 import com.google.inject.Module;
 import com.google.inject.util.Modules;
@@ -62,7 +63,7 @@
         new ElasticSearchMetricReporterModule());
 
     public static void main(String[] args) throws Exception {
-        Configuration configuration = Configuration.builder()
+        JPAJamesConfiguration configuration = JPAJamesConfiguration.builder()
             .useWorkingDirectoryEnvProperty()
             .build();
 
@@ -72,9 +73,11 @@
         JamesServerMain.main(server);
     }
 
-    public static GuiceJamesServer createServer(Configuration configuration) {
+    public static GuiceJamesServer createServer(JPAJamesConfiguration configuration) {
         return GuiceJamesServer.forConfiguration(configuration)
-            .combineWith(JPA_SERVER_MODULE,  PROTOCOLS, new DKIMMailetModule());
+            .combineWith(JPA_SERVER_MODULE,  PROTOCOLS, new DKIMMailetModule())
+            .combineWith(new UsersRepositoryModuleChooser(new JPAUsersRepositoryModule())
+                .chooseModules(configuration.getUsersRepositoryImplementation()));
     }
 
 }
diff --git a/server/apps/jpa-smtp-app/src/test/java/org/apache/james/JPAJamesServerTest.java b/server/apps/jpa-smtp-app/src/test/java/org/apache/james/JPAJamesServerTest.java
index 542022a..cdbeaae 100644
--- a/server/apps/jpa-smtp-app/src/test/java/org/apache/james/JPAJamesServerTest.java
+++ b/server/apps/jpa-smtp-app/src/test/java/org/apache/james/JPAJamesServerTest.java
@@ -19,6 +19,7 @@
 
 package org.apache.james;
 
+import static org.apache.james.data.UsersRepositoryModuleChooser.Implementation.DEFAULT;
 import static org.assertj.core.api.Assertions.assertThat;
 
 import java.io.IOException;
@@ -34,7 +35,6 @@
 import org.apache.james.mailrepository.jpa.JPAUrl;
 import org.apache.james.modules.protocols.SmtpGuiceProbe;
 import org.apache.james.rrt.jpa.model.JPARecipientRewrite;
-import org.apache.james.server.core.configuration.Configuration;
 import org.apache.james.user.jpa.model.JPAUser;
 import org.junit.After;
 import org.junit.Before;
@@ -56,9 +56,10 @@
     }
 
     private org.apache.james.GuiceJamesServer createJamesServer() throws IOException {
-        Configuration configuration = Configuration.builder()
+        JPAJamesConfiguration configuration = JPAJamesConfiguration.builder()
             .workingDirectory(temporaryFolder.newFolder())
             .configurationFromClasspath()
+            .usersRepository(DEFAULT)
             .build();
 
         return JPAJamesServerMain.createServer(configuration)
diff --git a/server/apps/jpa-smtp-app/src/test/java/org/apache/james/mariadb/JPAMariaDBJamesServerTest.java b/server/apps/jpa-smtp-app/src/test/java/org/apache/james/mariadb/JPAMariaDBJamesServerTest.java
index be9a34b..50df95b 100644
--- a/server/apps/jpa-smtp-app/src/test/java/org/apache/james/mariadb/JPAMariaDBJamesServerTest.java
+++ b/server/apps/jpa-smtp-app/src/test/java/org/apache/james/mariadb/JPAMariaDBJamesServerTest.java
@@ -19,6 +19,7 @@
 
 package org.apache.james.mariadb;
 
+import static org.apache.james.data.UsersRepositoryModuleChooser.Implementation.DEFAULT;
 import static org.assertj.core.api.Assertions.assertThat;
 
 import java.io.IOException;
@@ -28,9 +29,9 @@
 import java.nio.charset.StandardCharsets;
 
 import org.apache.james.GuiceJamesServer;
+import org.apache.james.JPAJamesConfiguration;
 import org.apache.james.JPAJamesServerMain;
 import org.apache.james.modules.protocols.SmtpGuiceProbe;
-import org.apache.james.server.core.configuration.Configuration;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
@@ -62,8 +63,9 @@
     }
 
     private org.apache.james.GuiceJamesServer createJamesServer(String mariaDBUrl) throws IOException {
-        Configuration configuration = Configuration.builder()
+        JPAJamesConfiguration configuration = JPAJamesConfiguration.builder()
             .workingDirectory(temporaryFolder.newFolder())
+            .usersRepository(DEFAULT)
             .configurationFromClasspath()
             .build();
 
diff --git a/server/container/guice/data-ldap/pom.xml b/server/container/guice/data-ldap/pom.xml
index 5bc6f60..752804c 100644
--- a/server/container/guice/data-ldap/pom.xml
+++ b/server/container/guice/data-ldap/pom.xml
@@ -43,10 +43,23 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-data-ldap</artifactId>
+            <version>${project.version}</version>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
             <artifactId>james-server-guice-common</artifactId>
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-guice-common</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
             <artifactId>james-server-testing</artifactId>
             <scope>test</scope>
         </dependency>
diff --git a/server/apps/cassandra-app/src/test/java/org/apache/james/DockerLdapRule.java b/server/container/guice/data-ldap/src/test/java/org/apache/james/data/DockerLdapRule.java
similarity index 96%
rename from server/apps/cassandra-app/src/test/java/org/apache/james/DockerLdapRule.java
rename to server/container/guice/data-ldap/src/test/java/org/apache/james/data/DockerLdapRule.java
index 4ce36af..2cdada9 100644
--- a/server/apps/cassandra-app/src/test/java/org/apache/james/DockerLdapRule.java
+++ b/server/container/guice/data-ldap/src/test/java/org/apache/james/data/DockerLdapRule.java
@@ -17,9 +17,10 @@
  * under the License.                                           *
  ****************************************************************/
 
-package org.apache.james;
+package org.apache.james.data;
 
 import org.apache.commons.configuration2.ex.ConfigurationException;
+import org.apache.james.GuiceModuleTestRule;
 import org.apache.james.user.ldap.DockerLdapSingleton;
 import org.apache.james.user.ldap.LdapRepositoryConfiguration;
 import org.junit.runner.Description;
diff --git a/server/apps/cassandra-app/src/test/java/org/apache/james/LdapTestExtension.java b/server/container/guice/data-ldap/src/test/java/org/apache/james/data/LdapTestExtension.java
similarity index 91%
rename from server/apps/cassandra-app/src/test/java/org/apache/james/LdapTestExtension.java
rename to server/container/guice/data-ldap/src/test/java/org/apache/james/data/LdapTestExtension.java
index a468116..d64312f 100644
--- a/server/apps/cassandra-app/src/test/java/org/apache/james/LdapTestExtension.java
+++ b/server/container/guice/data-ldap/src/test/java/org/apache/james/data/LdapTestExtension.java
@@ -17,8 +17,9 @@
  * under the License.                                           *
  ****************************************************************/
 
-package org.apache.james;
+package org.apache.james.data;
 
+import org.apache.james.GuiceModuleTestExtension;
 import org.junit.jupiter.api.extension.ExtensionContext;
 
 import com.google.inject.Module;
@@ -27,11 +28,11 @@
 
     private DockerLdapRule ldapRule;
 
-    LdapTestExtension() {
+    public LdapTestExtension() {
         this(new DockerLdapRule());
     }
 
-    LdapTestExtension(DockerLdapRule ldapRule) {
+    public LdapTestExtension(DockerLdapRule ldapRule) {
         this.ldapRule = ldapRule;
     }
 
diff --git a/server/container/guice/jpa-common/src/main/java/org/apache/james/modules/data/JPADataModule.java b/server/container/guice/jpa-common/src/main/java/org/apache/james/modules/data/JPADataModule.java
index ee79def..4082070 100644
--- a/server/container/guice/jpa-common/src/main/java/org/apache/james/modules/data/JPADataModule.java
+++ b/server/container/guice/jpa-common/src/main/java/org/apache/james/modules/data/JPADataModule.java
@@ -21,10 +21,8 @@
 import com.google.inject.AbstractModule;
 
 public class JPADataModule extends AbstractModule {
-
     @Override
     protected void configure() {
-        install(new JPAUsersRepositoryModule());
         install(new JPADomainListModule());
         install(new JPARecipientRewriteTableModule());
         install(new JPAMailRepositoryModule());
diff --git a/server/container/guice/mailbox/src/main/java/org/apache/james/modules/MailboxProbeImpl.java b/server/container/guice/mailbox/src/main/java/org/apache/james/modules/MailboxProbeImpl.java
index 10755c1..56e31fd 100644
--- a/server/container/guice/mailbox/src/main/java/org/apache/james/modules/MailboxProbeImpl.java
+++ b/server/container/guice/mailbox/src/main/java/org/apache/james/modules/MailboxProbeImpl.java
@@ -173,6 +173,13 @@
         return messageManager.appendMessage(appendCommand, mailboxSession).getId();
     }
 
+    public MessageManager.AppendResult appendMessageAndGetAppendResult(String username, MailboxPath mailboxPath, MessageManager.AppendCommand appendCommand)
+        throws MailboxException {
+        MailboxSession mailboxSession = mailboxManager.createSystemSession(Username.of(username));
+        MessageManager messageManager = mailboxManager.getMailbox(mailboxPath, mailboxSession);
+        return messageManager.appendMessage(appendCommand, mailboxSession);
+    }
+
     @Override
     public Collection<String> listSubscriptions(String user) throws Exception {
         MailboxSession mailboxSession = mailboxManager.createSystemSession(Username.of(user));
diff --git a/server/container/guice/pom.xml b/server/container/guice/pom.xml
index ea45f6f..dea05b8 100644
--- a/server/container/guice/pom.xml
+++ b/server/container/guice/pom.xml
@@ -104,6 +104,12 @@
             </dependency>
             <dependency>
                 <groupId>${james.groupId}</groupId>
+                <artifactId>james-server-guice-data-ldap</artifactId>
+                <version>${project.version}</version>
+                <type>test-jar</type>
+            </dependency>
+            <dependency>
+                <groupId>${james.groupId}</groupId>
                 <artifactId>james-server-guice-distributed</artifactId>
                 <version>${project.version}</version>
             </dependency>
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/mailet/ExtractMDNOriginalJMAPMessageId.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/mailet/ExtractMDNOriginalJMAPMessageId.java
index 31cd912..a8a6baf 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/mailet/ExtractMDNOriginalJMAPMessageId.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/mailet/ExtractMDNOriginalJMAPMessageId.java
@@ -27,7 +27,6 @@
 import org.apache.james.core.MailAddress;
 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.MessageId;
 import org.apache.james.mailbox.model.MultimailboxesSearchQuery;
 import org.apache.james.mailbox.model.SearchQuery;
@@ -106,7 +105,7 @@
                 .from(SearchQuery.of(SearchQuery.mimeMessageID(messageId)))
                 .build();
             return Flux.from(mailboxManager.search(searchByRFC822MessageId, session, limit)).toStream().findFirst();
-        } catch (MailboxException | UsersRepositoryException e) {
+        } catch (UsersRepositoryException e) {
             LOGGER.error("unable to find message with Message-Id: " + messageId, e);
         }
         return Optional.empty();
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/pom.xml b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/pom.xml
index ae1eeb0..3811480 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/pom.xml
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/pom.xml
@@ -83,6 +83,13 @@
                 <groupId>net.alchim31.maven</groupId>
                 <artifactId>scala-maven-plugin</artifactId>
             </plugin>
+            <plugin>
+                <groupId>io.github.evis</groupId>
+                <artifactId>scalafix-maven-plugin</artifactId>
+                <configuration>
+                    <config>${project.parent.parent.parent.basedir}/.scalafix.conf</config>
+                </configuration>
+            </plugin>
         </plugins>
     </build>
 </project>
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailChangesMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailChangesMethodContract.scala
index 59222f7..0ce6bee 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailChangesMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailChangesMethodContract.scala
@@ -210,15 +210,15 @@
       .setBody("testmail", StandardCharsets.UTF_8)
       .build
     val messageId1: MessageId = mailboxProbe.appendMessage(BOB.asString(), path, AppendCommand.from(message)).getMessageId
-    val state1: State = waitForNextState(server, AccountId.fromUsername(BOB), State.INITIAL)
+    waitForNextState(server, AccountId.fromUsername(BOB), State.INITIAL)
     val messageId2: MessageId = mailboxProbe.appendMessage(BOB.asString(), path, AppendCommand.from(message)).getMessageId
-    val state2: State = waitForNextState(server, AccountId.fromUsername(BOB), State.INITIAL)
+    waitForNextState(server, AccountId.fromUsername(BOB), State.INITIAL)
     val messageId3: MessageId = mailboxProbe.appendMessage(BOB.asString(), path, AppendCommand.from(message)).getMessageId
-    val state3: State = waitForNextState(server, AccountId.fromUsername(BOB), State.INITIAL)
+    waitForNextState(server, AccountId.fromUsername(BOB), State.INITIAL)
     val messageId4: MessageId = mailboxProbe.appendMessage(BOB.asString(), path, AppendCommand.from(message)).getMessageId
-    val state4: State = waitForNextState(server, AccountId.fromUsername(BOB), State.INITIAL)
+    waitForNextState(server, AccountId.fromUsername(BOB), State.INITIAL)
     val messageId5: MessageId = mailboxProbe.appendMessage(BOB.asString(), path, AppendCommand.from(message)).getMessageId
-    val state5: State = waitForNextState(server, AccountId.fromUsername(BOB), State.INITIAL)
+    waitForNextState(server, AccountId.fromUsername(BOB), State.INITIAL)
     val messageId6: MessageId = mailboxProbe.appendMessage(BOB.asString(), path, AppendCommand.from(message)).getMessageId
     val state6: State = waitForNextState(server, AccountId.fromUsername(BOB), State.INITIAL)
 
@@ -312,7 +312,7 @@
     val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
     val path: MailboxPath = MailboxPath.forUser(BOB, "mailbox1")
 
-    val mailboxId1 = mailboxProbe.createMailbox(path)
+    mailboxProbe.createMailbox(path)
     val mailboxId2 = mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "mailbox2"))
 
     val message: Message = Message.Builder
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailGetMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailGetMethodContract.scala
index bcfdf8e..8968153 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailGetMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailGetMethodContract.scala
@@ -3198,7 +3198,7 @@
   @Test
   def receivedAtPropertyShouldBeReturned(server: GuiceJamesServer): Unit = {
     val path = MailboxPath.inbox(BOB)
-    val mailboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(path)
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(path)
     val message: Message = Message.Builder
       .of
       .setSubject("test")
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailImportContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailImportContract.scala
index e8716c5..18f00c4 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailImportContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailImportContract.scala
@@ -672,7 +672,7 @@
   @Test
   def importShouldFailWhenBlobNotOwned(server: GuiceJamesServer): Unit = {
     val andrePath = MailboxPath.inbox(ANDRE)
-    val andreId: MailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(andrePath)
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(andrePath)
     val bobPath = MailboxPath.inbox(BOB)
     val bobId: MailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
     val receivedAt = ZonedDateTime.now().minusDays(1)
@@ -743,7 +743,7 @@
   @Test
   def importShouldSucceedWhenBlobDelegated(server: GuiceJamesServer): Unit = {
     val andrePath = MailboxPath.inbox(ANDRE)
-    val andreId: MailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(andrePath)
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(andrePath)
     val bobPath = MailboxPath.inbox(BOB)
     val bobId: MailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
     val receivedAt = ZonedDateTime.now().minusDays(1)
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala
index 2b19d63..dd7efb8 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala
@@ -147,7 +147,7 @@
   def hasAttachmentShouldKeepMessageWithAttachmentWhenTrue(server: GuiceJamesServer): Unit = {
     val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
     mailboxProbe.createMailbox(MailboxPath.inbox(BOB))
-    val messageId1: MessageId = mailboxProbe
+    mailboxProbe
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB),
         AppendCommand.from(
           buildTestMessage))
@@ -459,8 +459,8 @@
   @Test
   def emailInSharedMailboxesShouldNotBeDisplayedWhenNoExtension(server: GuiceJamesServer): Unit = {
     val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
-    val andreInboxId = mailboxProbe.createMailbox(inbox(ANDRE))
-    val messageId1: MessageId = mailboxProbe
+    mailboxProbe.createMailbox(inbox(ANDRE))
+    mailboxProbe
       .appendMessage(ANDRE.asString, inbox(ANDRE),
         AppendCommand.from(
           Message.Builder
@@ -521,7 +521,7 @@
   @Test
   def emailInSharedMailboxesShouldBeDisplayedWhenExtension(server: GuiceJamesServer): Unit = {
     val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
-    val andreInboxId = mailboxProbe.createMailbox(inbox(ANDRE))
+    mailboxProbe.createMailbox(inbox(ANDRE))
     val messageId1: MessageId = mailboxProbe
       .appendMessage(ANDRE.asString, inbox(ANDRE),
         AppendCommand.from(
@@ -585,7 +585,7 @@
   def inMailboxFilterShouldReturnEmptyForSharedMailboxesWhenNoExtension(server: GuiceJamesServer): Unit = {
     val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
     val andreInboxId = mailboxProbe.createMailbox(inbox(ANDRE))
-    val messageId1: MessageId = mailboxProbe
+    mailboxProbe
       .appendMessage(ANDRE.asString, inbox(ANDRE),
         AppendCommand.from(
           Message.Builder
@@ -714,7 +714,7 @@
   def inMailboxOtherThanFilterShouldReturnEmptyForSharedMailboxesWhenNoExtension(server: GuiceJamesServer): Unit = {
     val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
     val andreInboxId = mailboxProbe.createMailbox(inbox(ANDRE))
-    val messageId1: MessageId = mailboxProbe
+    mailboxProbe
       .appendMessage(ANDRE.asString, inbox(ANDRE),
         AppendCommand.from(
           Message.Builder
@@ -775,8 +775,8 @@
   def inMailboxOtherThanFilterShouldAcceptSharedMailboxesWhenExtension(server: GuiceJamesServer): Unit = {
     val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
     val andreInboxId = mailboxProbe.createMailbox(inbox(ANDRE))
-    val bobInboxId = mailboxProbe.createMailbox(inbox(BOB))
-    val messageId1: MessageId = mailboxProbe
+    mailboxProbe.createMailbox(inbox(BOB))
+    mailboxProbe
       .appendMessage(ANDRE.asString, inbox(ANDRE),
         AppendCommand.from(
           Message.Builder
@@ -851,7 +851,7 @@
     server.getProbe(classOf[MailboxProbeImpl]).createMailbox(mailboxPath)
     val requestDate = Date.from(ZonedDateTime.now().minusDays(1).toInstant)
     val messageId1: MessageId = sendMessageToBobInbox(server, message, requestDate)
-    val messageId2: MessageId = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, mailboxPath, AppendCommand.from(
         ClassLoaderUtils.getSystemResourceAsSharedStream("eml/multipart_simple.eml")))
       .getMessageId
@@ -989,7 +989,7 @@
   @Test
   def headerExistsShouldBeCaseInsentive(server: GuiceJamesServer): Unit = {
     val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
-    val bobInboxId = mailboxProbe.createMailbox(inbox(BOB))
+    mailboxProbe.createMailbox(inbox(BOB))
     val messageId1: MessageId = mailboxProbe
       .appendMessage(BOB.asString, inbox(BOB),
         AppendCommand.from(
@@ -1000,7 +1000,7 @@
             .setBody("testmail", StandardCharsets.UTF_8)
             .build))
       .getMessageId
-    val messageId2: MessageId = mailboxProbe
+    mailboxProbe
       .appendMessage(BOB.asString, inbox(BOB),
         AppendCommand.from(
           Message.Builder
@@ -1060,7 +1060,7 @@
   @Test
   def headerShouldAllowToMatchMailWithSpecificHeaderSet(server: GuiceJamesServer): Unit = {
     val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
-    val bobInboxId = mailboxProbe.createMailbox(inbox(BOB))
+    mailboxProbe.createMailbox(inbox(BOB))
     val messageId1: MessageId = mailboxProbe
       .appendMessage(BOB.asString, inbox(BOB),
         AppendCommand.from(
@@ -1071,7 +1071,7 @@
             .setBody("testmail", StandardCharsets.UTF_8)
             .build))
       .getMessageId
-    val messageId2: MessageId = mailboxProbe
+    mailboxProbe
       .appendMessage(BOB.asString, inbox(BOB),
         AppendCommand.from(
           Message.Builder
@@ -1131,7 +1131,7 @@
   @Test
   def headerShouldAllowToMatchMailWithSpecificValueHeaderSet(server: GuiceJamesServer): Unit = {
     val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
-    val bobInboxId = mailboxProbe.createMailbox(inbox(BOB))
+    mailboxProbe.createMailbox(inbox(BOB))
     val messageId1: MessageId = mailboxProbe
       .appendMessage(BOB.asString, inbox(BOB),
         AppendCommand.from(
@@ -1142,7 +1142,7 @@
             .setBody("testmail", StandardCharsets.UTF_8)
             .build))
       .getMessageId
-    val messageId2: MessageId = mailboxProbe
+    mailboxProbe
       .appendMessage(BOB.asString, inbox(BOB),
         AppendCommand.from(
           Message.Builder
@@ -1151,7 +1151,7 @@
             .setBody("testmail", StandardCharsets.UTF_8)
             .build))
       .getMessageId
-    val messageId3: MessageId = mailboxProbe
+    mailboxProbe
       .appendMessage(BOB.asString, inbox(BOB),
         AppendCommand.from(
           Message.Builder
@@ -1212,7 +1212,7 @@
   @Test
   def headerContainsShouldBeCaseInsentive(server: GuiceJamesServer): Unit = {
     val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
-    val bobInboxId = mailboxProbe.createMailbox(inbox(BOB))
+    mailboxProbe.createMailbox(inbox(BOB))
     val messageId1: MessageId = mailboxProbe
       .appendMessage(BOB.asString, inbox(BOB),
         AppendCommand.from(
@@ -1223,7 +1223,7 @@
             .setBody("testmail", StandardCharsets.UTF_8)
             .build))
       .getMessageId
-    val messageId2: MessageId = mailboxProbe
+    mailboxProbe
       .appendMessage(BOB.asString, inbox(BOB),
         AppendCommand.from(
           Message.Builder
@@ -1232,7 +1232,7 @@
             .setBody("testmail", StandardCharsets.UTF_8)
             .build))
       .getMessageId
-    val messageId3: MessageId = mailboxProbe
+    mailboxProbe
       .appendMessage(BOB.asString, inbox(BOB),
         AppendCommand.from(
           Message.Builder
@@ -1293,8 +1293,8 @@
   @Test
   def headerShouldRejectWhenMoreThanTwoItems(server: GuiceJamesServer): Unit = {
     val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
-    val bobInboxId = mailboxProbe.createMailbox(inbox(BOB))
-    val messageId1: MessageId = mailboxProbe
+    mailboxProbe.createMailbox(inbox(BOB))
+    mailboxProbe
       .appendMessage(BOB.asString, inbox(BOB),
         AppendCommand.from(
           Message.Builder
@@ -1304,7 +1304,7 @@
             .setBody("testmail", StandardCharsets.UTF_8)
             .build))
       .getMessageId
-    val messageId2: MessageId = mailboxProbe
+    mailboxProbe
       .appendMessage(BOB.asString, inbox(BOB),
         AppendCommand.from(
           Message.Builder
@@ -1313,7 +1313,7 @@
             .setBody("testmail", StandardCharsets.UTF_8)
             .build))
       .getMessageId
-    val messageId3: MessageId = mailboxProbe
+    mailboxProbe
       .appendMessage(BOB.asString, inbox(BOB),
         AppendCommand.from(
           Message.Builder
@@ -1377,7 +1377,7 @@
           buildTestMessage))
       .getMessageId
 
-    val messageId2: MessageId = mailboxProbe
+    mailboxProbe
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.from(
         ClassLoaderUtils.getSystemResourceAsSharedStream("eml/multipart_simple.eml")))
       .getMessageId
@@ -1435,14 +1435,14 @@
     val afterRequestDate = Date.from(ZonedDateTime.now().toInstant)
     val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
     mailboxProbe.createMailbox(MailboxPath.inbox(BOB))
-    val messageId1: MessageId = mailboxProbe
+    mailboxProbe
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB),
         AppendCommand.builder()
           .withInternalDate(beforeRequestDate)
           .build(buildTestMessage))
       .getMessageId
 
-    val messageId2: MessageId = mailboxProbe
+    mailboxProbe
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder()
           .withInternalDate(beforeRequestDate)
         .build(ClassLoaderUtils.getSystemResourceAsSharedStream("eml/multipart_simple.eml")))
@@ -1455,7 +1455,7 @@
           .build(buildTestMessage))
       .getMessageId
 
-    val messageId4: MessageId = mailboxProbe
+    mailboxProbe
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder()
         .withInternalDate(afterRequestDate)
         .build(ClassLoaderUtils.getSystemResourceAsSharedStream("eml/multipart_simple.eml")))
@@ -2902,17 +2902,17 @@
   @Test
   def minSizeShouldBeInclusive(server: GuiceJamesServer): Unit = {
     val message1: Message = simpleMessage("short")
-    val size1: Int = computeSize(message1)
+    computeSize(message1)
     // One char more than message1
     val message2: Message = simpleMessage("short!")
     val size2: Int = computeSize(message2)
     // One char more than message2
     val message3: Message = simpleMessage("short!!")
-    val size3: Int = computeSize(message3)
+    computeSize(message3)
 
     val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
     mailboxProbe.createMailbox(inbox(BOB))
-    val id1 = mailboxProbe.appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(message1)).getMessageId
+    mailboxProbe.appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(message1)).getMessageId
     val id2 = mailboxProbe.appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(message2)).getMessageId
     val id3 = mailboxProbe.appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(message3)).getMessageId
 
@@ -2959,19 +2959,19 @@
   @Test
   def maxSizeShouldBeExclusive(server: GuiceJamesServer): Unit = {
     val message1: Message = simpleMessage("looooooooooooooong")
-    val size1: Int = computeSize(message1)
+    computeSize(message1)
     // One char more than message3
     val message2: Message = simpleMessage("looooooooooooooong!")
     val size2: Int = computeSize(message2)
     // One char more than message4
     val message3: Message = simpleMessage("looooooooooooooong!!")
-    val size3: Int = computeSize(message3)
+    computeSize(message3)
 
     val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
     mailboxProbe.createMailbox(inbox(BOB))
     val id1 = mailboxProbe.appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(message1)).getMessageId
-    val id2 = mailboxProbe.appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(message2)).getMessageId
-    val id3 = mailboxProbe.appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(message3)).getMessageId
+    mailboxProbe.appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(message2)).getMessageId
+    mailboxProbe.appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(message3)).getMessageId
 
     val request =
       s"""{
@@ -3894,7 +3894,7 @@
           .withInternalDate(Date.from(ZonedDateTime.now().minusDays(2).toInstant))
           .build(message))
         .getMessageId
-    val messageId2: MessageId = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
         .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder()
           .withInternalDate(Date.from(ZonedDateTime.now().minusDays(1).toInstant))
           .build(message))
@@ -4471,13 +4471,13 @@
           .setFrom("user@domain.tld")
           .build))
       .getMessageId
-    val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
         messageBuilder
           .setFrom("other@domain.tld")
           .build))
       .getMessageId
-    val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
         messageBuilder
           .setFrom("yet@other.tld")
@@ -4489,7 +4489,7 @@
           .setFrom("yet@other.tld", "user@domain.tld")
           .build))
       .getMessageId
-    val messageId5 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
         messageBuilder.build))
       .getMessageId
@@ -4549,7 +4549,7 @@
       .of
       .setSubject("test")
       .setBody("testmail", StandardCharsets.UTF_8)
-    val messageId1 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
         messageBuilder
           .setFrom("user@domain.tld")
@@ -4567,7 +4567,7 @@
           .setFrom("display@other.tld")
           .build))
       .getMessageId
-    val messageId4 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
         messageBuilder.build))
       .getMessageId
@@ -4621,13 +4621,13 @@
           .setTo("user@domain.tld")
           .build))
       .getMessageId
-    val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
         messageBuilder
           .setTo("other@domain.tld")
           .build))
       .getMessageId
-    val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
         messageBuilder
           .setTo("yet@other.tld")
@@ -4639,7 +4639,7 @@
           .setTo("yet@other.tld", "user@domain.tld")
           .build))
       .getMessageId
-    val messageId5 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
         messageBuilder.build))
       .getMessageId
@@ -4699,7 +4699,7 @@
       .of
       .setSubject("test")
       .setBody("testmail", StandardCharsets.UTF_8)
-    val messageId1 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
         messageBuilder
           .setTo("user@domain.tld")
@@ -4717,7 +4717,7 @@
           .setTo("display@other.tld")
           .build))
       .getMessageId
-    val messageId4 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
         messageBuilder.build))
       .getMessageId
@@ -4771,13 +4771,13 @@
           .setCc(DefaultAddressParser.DEFAULT.parseMailbox("user@domain.tld"))
           .build))
       .getMessageId
-    val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
         messageBuilder
           .setCc(DefaultAddressParser.DEFAULT.parseMailbox("other@domain.tld"))
           .build))
       .getMessageId
-    val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
         messageBuilder
           .setCc(DefaultAddressParser.DEFAULT.parseMailbox("yet@other.tld"))
@@ -4790,7 +4790,7 @@
             DefaultAddressParser.DEFAULT.parseMailbox("user@domain.tld"))
           .build))
       .getMessageId
-    val messageId5 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
         messageBuilder.build))
       .getMessageId
@@ -4850,7 +4850,7 @@
       .of
       .setSubject("test")
       .setBody("testmail", StandardCharsets.UTF_8)
-    val messageId1 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
         messageBuilder
           .setCc(DefaultAddressParser.DEFAULT.parseMailbox("user@domain.tld"))
@@ -4868,7 +4868,7 @@
           .setCc(DefaultAddressParser.DEFAULT.parseMailbox("display@other.tld"))
           .build))
       .getMessageId
-    val messageId4 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
         messageBuilder.build))
       .getMessageId
@@ -4922,13 +4922,13 @@
           .setBcc(DefaultAddressParser.DEFAULT.parseMailbox("user@domain.tld"))
           .build))
       .getMessageId
-    val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
         messageBuilder
           .setBcc(DefaultAddressParser.DEFAULT.parseMailbox("other@domain.tld"))
           .build))
       .getMessageId
-    val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
         messageBuilder
           .setBcc(DefaultAddressParser.DEFAULT.parseMailbox("yet@other.tld"))
@@ -4941,7 +4941,7 @@
             DefaultAddressParser.DEFAULT.parseMailbox("user@domain.tld"))
           .build))
       .getMessageId
-    val messageId5 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
         messageBuilder.build))
       .getMessageId
@@ -5001,7 +5001,7 @@
       .of
       .setSubject("test")
       .setBody("testmail", StandardCharsets.UTF_8)
-    val messageId1 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
         messageBuilder
           .setBcc(DefaultAddressParser.DEFAULT.parseMailbox("user@domain.tld"))
@@ -5019,7 +5019,7 @@
           .setBcc(DefaultAddressParser.DEFAULT.parseMailbox("display@other.tld"))
           .build))
       .getMessageId
-    val messageId4 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
         messageBuilder.build))
       .getMessageId
@@ -5072,13 +5072,13 @@
           .setSubject("Yet another day in paradise")
           .build))
       .getMessageId
-    val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
         messageBuilder
           .setSubject("Welcome to hell")
           .build))
       .getMessageId
-    val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
         messageBuilder.build))
       .getMessageId
@@ -5131,13 +5131,13 @@
           .setSubject("Yet another day in paradise")
           .build))
       .getMessageId
-    val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
         messageBuilder
           .setSubject("Welcome to hell")
           .build))
       .getMessageId
-    val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(
         messageBuilder.build))
       .getMessageId
@@ -5642,11 +5642,11 @@
   def emailQueryShouldSupportAndOperator(server: GuiceJamesServer): Unit = {
     val message: Message = buildTestMessage
     server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
-    val messageId1 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new Flags("custom")).build(message))
       .getMessageId
 
-    val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new Flags("another_custom")).build(message))
       .getMessageId
 
@@ -5714,7 +5714,7 @@
         .build(message))
       .getMessageId
 
-    val messageId4 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().build(message))
       .getMessageId
 
@@ -5761,19 +5761,19 @@
   def emailQueryShouldSupportNotOperator(server: GuiceJamesServer): Unit = {
     val message: Message = buildTestMessage
     server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
-    val messageId1 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder()
         .withFlags(new Flags("custom"))
         .build(message))
       .getMessageId
 
-    val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder()
         .withFlags(new Flags("another_custom"))
         .build(message))
       .getMessageId
 
-    val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder()
         .withFlags(new FlagsBuilder().add("custom", "another_custom").build())
         .build(message))
@@ -5926,15 +5926,15 @@
   def inMailboxShouldBeRejectedWhenInOperator(server: GuiceJamesServer): Unit = {
     val message: Message = buildTestMessage
     val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
-    val messageId1 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new Flags("custom")).build(message))
       .getMessageId
 
-    val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new Flags("another_custom")).build(message))
       .getMessageId
 
-    val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new FlagsBuilder().add("custom", "another_custom").build()).build(message))
       .getMessageId
 
@@ -5989,15 +5989,15 @@
   def inMailboxOtherThanShouldBeRejectedWhenInOperator(server: GuiceJamesServer): Unit = {
     val message: Message = buildTestMessage
     val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
-    val messageId1 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new Flags("custom")).build(message))
       .getMessageId
 
-    val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new Flags("another_custom")).build(message))
       .getMessageId
 
-    val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new FlagsBuilder().add("custom", "another_custom").build()).build(message))
       .getMessageId
 
@@ -6096,11 +6096,11 @@
   def nestedOperatorsShouldBeSupported(server: GuiceJamesServer): Unit = {
     val message: Message = buildTestMessage
     server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
-    val messageId1 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new Flags("custom")).build(message))
       .getMessageId
 
-    val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withFlags(new Flags("another_custom")).build(message))
       .getMessageId
 
@@ -6197,7 +6197,7 @@
     val message: Message = buildTestMessage
     server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
 
-    val messageId1 = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB),
         AppendCommand.builder()
           .withFlags(new FlagsBuilder().add("custom_1", "custom_2").build())
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala
index 413fdd8..f77c7c1 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala
@@ -1101,7 +1101,7 @@
   @Test
   def createShouldRejectEmptyMailboxIds(server: GuiceJamesServer): Unit = {
     val andrePath = MailboxPath.inbox(ANDRE)
-    val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(andrePath)
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(andrePath)
 
     val request =
       s"""{
@@ -1142,7 +1142,7 @@
   @Test
   def createShouldRejectInvalidMailboxIds(server: GuiceJamesServer): Unit = {
     val andrePath = MailboxPath.inbox(ANDRE)
-    val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(andrePath)
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(andrePath)
 
     val request =
       s"""{
@@ -1185,7 +1185,7 @@
   @Test
   def createShouldRejectNoMailboxIds(server: GuiceJamesServer): Unit = {
     val andrePath = MailboxPath.inbox(ANDRE)
-    val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(andrePath)
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(andrePath)
 
     val request =
       s"""{
@@ -2923,7 +2923,7 @@
     val messageId = responseAsJson
       .\("id")
       .get.asInstanceOf[JsString].value
-    val size = responseAsJson
+    responseAsJson
       .\("size")
       .get.asInstanceOf[JsNumber].value
 
@@ -3255,7 +3255,7 @@
     val payload = "123456789\r\n".getBytes(StandardCharsets.UTF_8)
     val htmlBody: String = "<!DOCTYPE html><html><head><title></title></head><body><div>I have the most <b>brilliant</b> plan. Let me tell you all about it. What we do is, we</div></body></html>"
 
-    val uploadResponse: String = `given`
+    `given`
       .basePath("")
       .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
       .contentType("text/plain")
@@ -5522,7 +5522,7 @@
   @Test
   def invalidPatchPropertyShouldFail(server: GuiceJamesServer): Unit = {
     val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
-    val mailboxId1: MailboxId = mailboxProbe.createMailbox(MailboxPath.inbox(BOB))
+    mailboxProbe.createMailbox(MailboxPath.inbox(BOB))
 
     val messageId: MessageId = mailboxProbe
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB),
@@ -5571,7 +5571,7 @@
   @Test
   def invalidMailboxPartialUpdatePropertyShouldFail(server: GuiceJamesServer): Unit = {
     val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
-    val mailboxId1: MailboxId = mailboxProbe.createMailbox(MailboxPath.inbox(BOB))
+    mailboxProbe.createMailbox(MailboxPath.inbox(BOB))
 
     val messageId: MessageId = mailboxProbe
       .appendMessage(BOB.asString, MailboxPath.inbox(BOB),
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxChangesMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxChangesMethodContract.scala
index 1c1f40a..dad2ea1 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxChangesMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxChangesMethodContract.scala
@@ -1054,7 +1054,7 @@
     val provisioningState: State = provisionSystemMailboxes(server)
 
     val path1 = MailboxPath.forUser(BOB, "mailbox1")
-    val mailboxId1: String = mailboxProbe
+    mailboxProbe
       .createMailbox(path1)
       .serialize
 
@@ -1226,7 +1226,7 @@
       .createMailbox(MailboxPath.forUser(BOB, "mailbox5"))
       .serialize
 
-    val mailboxId6: String = mailboxProbe
+    mailboxProbe
       .createMailbox(MailboxPath.forUser(BOB, "mailbox6"))
       .serialize
 
@@ -1634,7 +1634,7 @@
       .setSubject("test")
       .setBody("testmail", StandardCharsets.UTF_8)
       .build
-    val messageId1: MessageId = mailboxProbe.appendMessage(BOB.asString(), path, AppendCommand.from(message)).getMessageId
+    mailboxProbe.appendMessage(BOB.asString(), path, AppendCommand.from(message)).getMessageId
     val messageId2: MessageId = mailboxProbe.appendMessage(BOB.asString(), path, AppendCommand.from(message)).getMessageId
     val messageId3: MessageId = mailboxProbe.appendMessage(BOB.asString(), path, AppendCommand.from(message)).getMessageId
 
@@ -1977,7 +1977,7 @@
   }
 
   private def provisionSystemMailboxes(server: GuiceJamesServer): State = {
-    val mailboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
     val jmapGuiceProbe: JmapGuiceProbe = server.getProbe(classOf[JmapGuiceProbe])
 
     val request =
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxQueryMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxQueryMethodContract.scala
index 8cd1898..25ad0b2 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxQueryMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxQueryMethodContract.scala
@@ -184,7 +184,7 @@
   @Test
   def queryByRoleShouldNotReturnDelegatedMailboxes(server: GuiceJamesServer): Unit = {
     val andreInbox = MailboxPath.inbox(ANDRE)
-    val andreInboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .createMailbox(andreInbox)
     val bobInboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
       .createMailbox(MailboxPath.inbox(BOB))
@@ -245,7 +245,7 @@
   @Test
   def queryByRoleShouldNotReturnDelegatedMailboxesWhenCaseVariation(server: GuiceJamesServer): Unit = {
     val andreInbox = MailboxPath.inbox(ANDRE)
-    val andreInboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
+    server.getProbe(classOf[MailboxProbeImpl])
       .createMailbox(andreInbox)
     val bobInboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl])
       .createMailbox(MailboxPath.forUser(BOB, "InBoX"))
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxSetMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxSetMethodContract.scala
index 624c428..ea23e11 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxSetMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxSetMethodContract.scala
@@ -7644,7 +7644,7 @@
   @Test
   def updateShouldHandleNotFoundClientId(server: GuiceJamesServer): Unit = {
     val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
-    val mailboxId: MailboxId = mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "mailbox"))
+    mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "mailbox"))
 
     val response = `given`
       .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/ThreadGetContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/ThreadGetContract.scala
index 04cfdd8..d31acd0 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/ThreadGetContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/ThreadGetContract.scala
@@ -19,6 +19,8 @@
 
 package org.apache.james.jmap.rfc8621.contract
 
+import java.nio.charset.StandardCharsets
+
 import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT
 import io.restassured.RestAssured.{`given`, requestSpecification}
 import io.restassured.http.ContentType.JSON
@@ -28,6 +30,11 @@
 import org.apache.james.jmap.core.ResponseObject.SESSION_STATE
 import org.apache.james.jmap.http.UserCredential
 import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, BOB, BOB_PASSWORD, DOMAIN, authScheme, baseRequestSpecBuilder}
+import org.apache.james.mailbox.MessageManager
+import org.apache.james.mailbox.model.MailboxPath
+import org.apache.james.mime4j.dom.Message
+import org.apache.james.mime4j.stream.RawField
+import org.apache.james.modules.MailboxProbeImpl
 import org.apache.james.utils.DataProbeImpl
 import org.junit.jupiter.api.{BeforeEach, Test}
 
@@ -46,7 +53,7 @@
   }
 
   @Test
-  def threadsShouldReturnSuppliedIds(): Unit = {
+  def givenNonMessageThenGetThreadsShouldReturnNotFound(): Unit = {
     val request =
       s"""{
          |  "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
@@ -62,47 +69,6 @@
     val response =  `given`
       .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
       .body(request)
-      .when
-      .post
-      .`then`
-      .statusCode(SC_OK)
-      .contentType(JSON)
-      .extract
-      .body
-      .asString
-
-    assertThatJson(response)
-      .inPath("methodResponses[0][1]")
-      .isEqualTo(
-        s"""{
-          |  "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-          |  "state": "${SESSION_STATE.value}",
-          |  "list": [
-          |      {
-          |          "id": "123456",
-          |          "emailIds": ["123456"]
-          |      }
-          |  ]
-          |}""".stripMargin)
-  }
-
-  @Test
-  def threadsShouldReturnSuppliedIdsWhenSeveralThreads(): Unit = {
-    val request =
-      s"""{
-         |  "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
-         |  "methodCalls": [[
-         |    "Thread/get",
-         |    {
-         |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "ids": ["123456", "789"]
-         |    },
-         |    "c1"]]
-         |}""".stripMargin
-
-    val response =  `given`
-      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(request)
     .when
       .post
     .`then`
@@ -113,22 +79,26 @@
       .asString
 
     assertThatJson(response)
-      .inPath("methodResponses[0][1]")
       .isEqualTo(
         s"""{
-          |  "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-          |  "state": "${SESSION_STATE.value}",
-          |  "list": [
-          |      {
-          |          "id": "123456",
-          |          "emailIds": ["123456"]
-          |      },
-          |      {
-          |          "id": "789",
-          |          "emailIds": ["789"]
-          |      }
-          |  ]
-          |}""".stripMargin)
+           |	"sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |	"methodResponses": [
+           |		[
+           |			"Thread/get",
+           |			{
+           |				"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |				"state": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |				"list": [
+           |
+           |				],
+           |				"notFound": [
+           |					"123456"
+           |				]
+           |			},
+           |			"c1"
+           |		]
+           |	]
+           |}""".stripMargin)
   }
 
   @Test
@@ -172,4 +142,409 @@
           |    ]
           |}""".stripMargin)
   }
+
+  @Test
+  def addRelatedMailsInAThreadThenGetThatThreadShouldReturnExactThreadObjectWithEmailIdsSortedByArrivalDate(server: GuiceJamesServer): Unit = {
+    val bobPath = MailboxPath.inbox(BOB)
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
+
+    // given 3 mails with related Subject and related Mime Message-ID fields
+    val message1: MessageManager.AppendResult = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessageAndGetAppendResult(BOB.asString(), bobPath,
+        MessageManager.AppendCommand.from(Message.Builder.of.setSubject("Test")
+        .setMessageId("Message-ID-1")
+          .setBody("testmail", StandardCharsets.UTF_8)))
+
+    // message2 reply to message1
+    val message2: MessageManager.AppendResult = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessageAndGetAppendResult(BOB.asString(), bobPath,
+        MessageManager.AppendCommand.from(Message.Builder.of.setSubject("Re: Test")
+          .setMessageId("Message-ID-2")
+          .setField(new RawField("In-Reply-To", "Message-ID-1"))
+          .setBody("testmail", StandardCharsets.UTF_8)))
+
+    // message3 related to message1 through Subject and References message1's Message-ID
+    val message3: MessageManager.AppendResult = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessageAndGetAppendResult(BOB.asString(), bobPath,
+        MessageManager.AppendCommand.from(Message.Builder.of.setSubject("Fwd: Re: Test")
+          .setMessageId("Message-ID-3")
+          .setField(new RawField("In-Reply-To", "Random-InReplyTo"))
+          .addField(new RawField("References", "Message-ID-1"))
+          .setBody("testmail", StandardCharsets.UTF_8)))
+
+    val threadId = message1.getThreadId.serialize()
+    val message1Id = message1.getId.getMessageId.serialize()
+    val message2Id = message2.getId.getMessageId.serialize()
+    val message3Id = message3.getId.getMessageId.serialize()
+
+    val request =
+      s"""{
+         |  "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Thread/get",
+         |    {
+         |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "ids": ["$threadId"]
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    val response =  `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(request)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response)
+      .isEqualTo(
+        s"""{
+           |	"sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |	"methodResponses": [
+           |		[
+           |			"Thread/get",
+           |			{
+           |				"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |				"state": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |				"list": [{
+           |					"id": "$threadId",
+           |					"emailIds": ["$message1Id", "$message2Id", "$message3Id"]
+           |				}],
+           |				"notFound": [
+           |
+           |				]
+           |			},
+           |			"c1"
+           |		]
+           |	]
+           |}""".stripMargin)
+  }
+
+  @Test
+  def givenTwoThreadGetThatTwoThreadShouldReturnExactTwoThreadObjectWithEmailIdsSortedByArrivalDate(server: GuiceJamesServer): Unit = {
+    val bobPath = MailboxPath.inbox(BOB)
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
+
+    // given 2 mails with related Subject and related Mime Message-ID fields in threadA
+    val message1: MessageManager.AppendResult = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessageAndGetAppendResult(BOB.asString(), bobPath,
+        MessageManager.AppendCommand.from(Message.Builder.of.setSubject("Test")
+          .setMessageId("Message-ID-1")
+          .setBody("testmail", StandardCharsets.UTF_8)))
+    // message2 reply to message1
+    val message2: MessageManager.AppendResult = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessageAndGetAppendResult(BOB.asString(), bobPath,
+        MessageManager.AppendCommand.from(Message.Builder.of.setSubject("Re: Test")
+          .setMessageId("Message-ID-2")
+          .setField(new RawField("In-Reply-To", "Message-ID-1"))
+          .setBody("testmail", StandardCharsets.UTF_8)))
+    val threadA = message1.getThreadId.serialize()
+
+    // message3 in threadB
+    val message3: MessageManager.AppendResult = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessageAndGetAppendResult(BOB.asString(), bobPath,
+        MessageManager.AppendCommand.from(Message.Builder.of.setSubject("Message3-SubjectLine")
+          .setMessageId("Message-ID-3")
+          .setBody("testmail", StandardCharsets.UTF_8)))
+    val threadB = message3.getThreadId.serialize()
+
+    val message1Id = message1.getId.getMessageId.serialize()
+    val message2Id = message2.getId.getMessageId.serialize()
+    val message3Id = message3.getId.getMessageId.serialize()
+
+    val request =
+      s"""{
+         |  "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Thread/get",
+         |    {
+         |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "ids": ["$threadA", "$threadB"]
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    val response =  `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(request)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response)
+      .isEqualTo(
+        s"""{
+           |	"sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |	"methodResponses": [
+           |		[
+           |			"Thread/get",
+           |			{
+           |				"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |				"state": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |				"list": [{
+           |						"id": "$threadA",
+           |						"emailIds": [
+           |							"$message1Id",
+           |							"$message2Id"
+           |						]
+           |					},
+           |					{
+           |						"id": "$threadB",
+           |						"emailIds": [
+           |							"$message3Id"
+           |						]
+           |					}
+           |				],
+           |				"notFound": [
+           |
+           |				]
+           |			},
+           |			"c1"
+           |		]
+           |	]
+           |}""".stripMargin)
+  }
+
+  @Test
+  def givenOneThreadGetTwoThreadShouldReturnOnlyOneThreadObjectAndNotFound(server: GuiceJamesServer): Unit = {
+    val bobPath = MailboxPath.inbox(BOB)
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
+
+    // given 2 mails with related Subject and related Mime Message-ID fields in threadA
+    val message1: MessageManager.AppendResult = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessageAndGetAppendResult(BOB.asString(), bobPath,
+        MessageManager.AppendCommand.from(Message.Builder.of.setSubject("Test")
+          .setMessageId("Message-ID-1")
+          .setBody("testmail", StandardCharsets.UTF_8)))
+    // message2 reply to message1
+    val message2: MessageManager.AppendResult = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessageAndGetAppendResult(BOB.asString(), bobPath,
+        MessageManager.AppendCommand.from(Message.Builder.of.setSubject("Re: Test")
+          .setMessageId("Message-ID-2")
+          .setField(new RawField("In-Reply-To", "Message-ID-1"))
+          .setBody("testmail", StandardCharsets.UTF_8)))
+    val threadA = message1.getThreadId.serialize()
+
+    val message1Id = message1.getId.getMessageId.serialize()
+    val message2Id = message2.getId.getMessageId.serialize()
+
+    val request =
+      s"""{
+         |  "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Thread/get",
+         |    {
+         |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "ids": ["$threadA", "nonExistThread"]
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    val response =  `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(request)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response)
+      .isEqualTo(
+        s"""{
+           |	"sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |	"methodResponses": [
+           |		[
+           |			"Thread/get",
+           |			{
+           |				"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |				"state": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |				"list": [{
+           |					"id": "$threadA",
+           |					"emailIds": [
+           |						"$message1Id",
+           |						"$message2Id"
+           |					]
+           |				}],
+           |				"notFound": [
+           |					"nonExistThread"
+           |				]
+           |			},
+           |			"c1"
+           |		]
+           |	]
+           |}""".stripMargin)
+  }
+
+  @Test
+  def addThreeMailsWithRelatedSubjectButNonIdenticalMimeMessageIDThenGetThatThreadShouldNotReturnUnrelatedMails(server: GuiceJamesServer): Unit = {
+    val bobPath = MailboxPath.inbox(BOB)
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
+
+    val message1: MessageManager.AppendResult = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessageAndGetAppendResult(BOB.asString(), bobPath,
+        MessageManager.AppendCommand.from(Message.Builder.of.setSubject("Test")
+          .setMessageId("Message-ID-1")
+          .setBody("testmail", StandardCharsets.UTF_8)))
+
+    // message2 have related subject with message1 but non identical Mime Message-ID
+    val message2: MessageManager.AppendResult = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessageAndGetAppendResult(BOB.asString(), bobPath,
+        MessageManager.AppendCommand.from(Message.Builder.of.setSubject("Re: Test")
+          .setMessageId("Message-ID-2")
+          .setField(new RawField("In-Reply-To", "Random-InReplyTo"))
+          .setBody("testmail", StandardCharsets.UTF_8)))
+
+    // message3 have related subject with message1 but non identical Mime Message-ID
+    val message3: MessageManager.AppendResult = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessageAndGetAppendResult(BOB.asString(), bobPath,
+        MessageManager.AppendCommand.from(Message.Builder.of.setSubject("Fwd: Re: Test")
+          .setMessageId("Message-ID-3")
+          .setField(new RawField("In-Reply-To", "Another-Random-InReplyTo"))
+          .addField(new RawField("References", "Random-References"))
+          .setBody("testmail", StandardCharsets.UTF_8)))
+
+    val threadId1 = message1.getThreadId.serialize()
+    val message1Id = message1.getId.getMessageId.serialize()
+
+    val request =
+      s"""{
+         |  "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Thread/get",
+         |    {
+         |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "ids": ["$threadId1"]
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    val response =  `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(request)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response)
+      .isEqualTo(
+        s"""{
+           |	"sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |	"methodResponses": [
+           |		[
+           |			"Thread/get",
+           |			{
+           |				"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |				"state": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |				"list": [{
+           |					"id": "$threadId1",
+           |					"emailIds": ["$message1Id"]
+           |				}],
+           |				"notFound": [
+           |
+           |				]
+           |			},
+           |			"c1"
+           |		]
+           |	]
+           |}""".stripMargin)
+  }
+
+  @Test
+  def addThreeMailsWithIdenticalMimeMessageIDButNonRelatedSubjectThenGetThatThreadShouldNotReturnUnrelatedMails(server: GuiceJamesServer): Unit = {
+    val bobPath = MailboxPath.inbox(BOB)
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
+
+    val message1: MessageManager.AppendResult = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessageAndGetAppendResult(BOB.asString(), bobPath,
+        MessageManager.AppendCommand.from(Message.Builder.of.setSubject("Test1")
+          .setMessageId("Message-ID-1")
+          .setBody("testmail", StandardCharsets.UTF_8)))
+
+    // message2 have identical Mime Message-ID with message1 through In-Reply-To field but have non related subject
+    val message2: MessageManager.AppendResult = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessageAndGetAppendResult(BOB.asString(), bobPath,
+        MessageManager.AppendCommand.from(Message.Builder.of.setSubject("Test2")
+          .setMessageId("Message-ID-2")
+          .setField(new RawField("In-Reply-To", "Message-ID-1"))
+          .setBody("testmail", StandardCharsets.UTF_8)))
+
+    // message2 have identical Mime Message-ID with message1 through References field but have non related subject
+    val message3: MessageManager.AppendResult = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessageAndGetAppendResult(BOB.asString(), bobPath,
+        MessageManager.AppendCommand.from(Message.Builder.of.setSubject("Test3")
+          .setMessageId("Message-ID-3")
+          .setField(new RawField("In-Reply-To", "Random-InReplyTo"))
+          .addField(new RawField("References", "Message-ID-1"))
+          .setBody("testmail", StandardCharsets.UTF_8)))
+
+    val threadId1 = message1.getThreadId.serialize()
+    val message1Id = message1.getId.getMessageId.serialize()
+
+    val request =
+      s"""{
+         |  "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Thread/get",
+         |    {
+         |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "ids": ["$threadId1"]
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    val response =  `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(request)
+      .when
+      .post
+      .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response)
+      .isEqualTo(
+        s"""{
+           |	"sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |	"methodResponses": [
+           |		[
+           |			"Thread/get",
+           |			{
+           |				"accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |				"state": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+           |				"list": [{
+           |					"id": "$threadId1",
+           |					"emailIds": ["$message1Id"]
+           |				}],
+           |				"notFound": [
+           |
+           |				]
+           |			},
+           |			"c1"
+           |		]
+           |	]
+           |}""".stripMargin)
+  }
+
 }
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/WebSocketContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/WebSocketContract.scala
index 0223d8a..45ae356 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/WebSocketContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/WebSocketContract.scala
@@ -1069,7 +1069,7 @@
   def pushEnableRequestWithPushStateShouldReturnServerState(server: GuiceJamesServer): Unit = {
     val bobPath = MailboxPath.inbox(BOB)
     val accountId: AccountId = AccountId.fromUsername(BOB)
-    val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
 
     Thread.sleep(100)
 
diff --git a/server/protocols/jmap-rfc-8621/pom.xml b/server/protocols/jmap-rfc-8621/pom.xml
index 526270c..42d6fb0 100644
--- a/server/protocols/jmap-rfc-8621/pom.xml
+++ b/server/protocols/jmap-rfc-8621/pom.xml
@@ -189,6 +189,13 @@
                 <groupId>org.scalatest</groupId>
                 <artifactId>scalatest-maven-plugin</artifactId>
             </plugin>
+            <plugin>
+                <groupId>io.github.evis</groupId>
+                <artifactId>scalafix-maven-plugin</artifactId>
+                <configuration>
+                    <config>${project.parent.parent.basedir}/.scalafix.conf</config>
+                </configuration>
+            </plugin>
         </plugins>
     </build>
 </project>
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/UserProvisioning.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/UserProvisioning.scala
index 140ecfb..37cdd28 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/UserProvisioning.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/UserProvisioning.scala
@@ -25,7 +25,6 @@
 import org.apache.james.core.Username
 import org.apache.james.mailbox.MailboxSession
 import org.apache.james.metrics.api.MetricFactory
-import org.apache.james.metrics.api.TimeMetric.ExecutionResult.DEFAULT_100_MS_THRESHOLD
 import org.apache.james.user.api.{AlreadyExistInUsersRepositoryException, UsersRepository, UsersRepositoryException}
 import reactor.core.scala.publisher.SMono
 
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailGetSerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailGetSerializer.scala
index 2223077..dbe8136 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailGetSerializer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailGetSerializer.scala
@@ -29,7 +29,6 @@
 import play.api.libs.functional.syntax._
 import play.api.libs.json._
 
-import scala.language.implicitConversions
 
 object EmailBodyPartToSerialize {
   def from(part: EmailBodyPart): EmailBodyPartToSerialize = EmailBodyPartToSerialize(
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailQuerySerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailQuerySerializer.scala
index 11ff719..c41e255 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailQuerySerializer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailQuerySerializer.scala
@@ -25,7 +25,6 @@
 import org.apache.james.mailbox.model.{MailboxId, MessageId}
 import play.api.libs.json._
 
-import scala.language.implicitConversions
 import scala.util.Try
 
 class EmailQuerySerializer @Inject()(mailboxIdFactory: MailboxId.Factory) {
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/ThreadSerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/ThreadSerializer.scala
index 9f4d74b..ca5da3e 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/ThreadSerializer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/ThreadSerializer.scala
@@ -20,16 +20,21 @@
 package org.apache.james.jmap.json
 
 import org.apache.james.jmap.core.UuidState
-import org.apache.james.jmap.mail.{Thread, ThreadChangesRequest, ThreadChangesResponse, ThreadGetRequest, ThreadGetResponse}
-import play.api.libs.json.{JsObject, JsResult, JsValue, Json, OWrites, Reads, Writes}
+import org.apache.james.jmap.mail.{Thread, ThreadChangesRequest, ThreadChangesResponse, ThreadGetRequest, ThreadGetResponse, ThreadNotFound, UnparsedThreadId}
+import org.apache.james.mailbox.model.MessageId
+import play.api.libs.json.{JsObject, JsResult, JsString, JsValue, Json, OWrites, Reads, Writes}
 
 import scala.language.implicitConversions
 
 object ThreadSerializer {
+  private implicit val messageIdWrites: Writes[MessageId] = messageId => JsString(messageId.serialize())
+  private implicit val unparsedThreadIdReads: Reads[UnparsedThreadId] = Json.valueReads[UnparsedThreadId]
   private implicit val threadGetReads: Reads[ThreadGetRequest] = Json.reads[ThreadGetRequest]
   private implicit val threadChangesReads: Reads[ThreadChangesRequest] = Json.reads[ThreadChangesRequest]
   private implicit val threadWrites: OWrites[Thread] = Json.writes[Thread]
   private implicit val stateWrites: Writes[UuidState] = Json.valueWrites[UuidState]
+  private implicit val unparsedThreadIdWrites: Writes[UnparsedThreadId] = Json.valueWrites[UnparsedThreadId]
+  private implicit val threadNotFoundWrites: Writes[ThreadNotFound] = Json.valueWrites[ThreadNotFound]
   private implicit val threadGetWrites: OWrites[ThreadGetResponse] = Json.writes[ThreadGetResponse]
   private implicit val changesResponseWrites: OWrites[ThreadChangesResponse] = Json.writes[ThreadChangesResponse]
 
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Thread.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Thread.scala
index e39c20d..e6619a5 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Thread.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Thread.scala
@@ -23,15 +23,23 @@
 import org.apache.james.jmap.core.UnsignedInt.UnsignedInt
 import org.apache.james.jmap.core.{AccountId, UuidState}
 import org.apache.james.jmap.method.WithAccountId
+import org.apache.james.mailbox.model.MessageId
 
-case class Thread(id: Id, emailIds: List[Id])
+case class Thread(id: Id, emailIds: List[MessageId])
 
 case class ThreadGetRequest(accountId: AccountId,
-                            ids: List[Id]) extends WithAccountId
+                            ids: List[UnparsedThreadId]) extends WithAccountId
 
 case class ThreadGetResponse(accountId: AccountId,
                              state: UuidState,
-                             list: List[Thread])
+                             list: List[Thread],
+                             notFound: ThreadNotFound)
+
+case class ThreadNotFound(value: Set[UnparsedThreadId]) {
+  def merge(other: ThreadNotFound): ThreadNotFound = ThreadNotFound(this.value ++ other.value)
+}
+
+case class UnparsedThreadId(id: Id)
 
 case class ThreadChangesRequest(accountId: AccountId,
                                 sinceState: UuidState,
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/ThreadGetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/ThreadGetMethod.scala
index 1c3c53e..7b91276 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/ThreadGetMethod.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/ThreadGetMethod.scala
@@ -22,31 +22,59 @@
 import eu.timepit.refined.auto._
 import javax.inject.Inject
 import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE, JMAP_MAIL}
-import org.apache.james.jmap.core.Id.Id
 import org.apache.james.jmap.core.Invocation.{Arguments, MethodName}
-import org.apache.james.jmap.core.{Invocation, UuidState}
+import org.apache.james.jmap.core.{AccountId, Invocation, UuidState}
 import org.apache.james.jmap.json.{ResponseSerializer, ThreadSerializer}
-import org.apache.james.jmap.mail.{Thread, ThreadGetRequest, ThreadGetResponse}
+import org.apache.james.jmap.mail.{Thread, ThreadGetRequest, ThreadGetResponse, ThreadNotFound, UnparsedThreadId}
 import org.apache.james.jmap.routes.SessionSupplier
-import org.apache.james.mailbox.MailboxSession
+import org.apache.james.mailbox.model.{ThreadId => JavaThreadId}
+import org.apache.james.mailbox.{MailboxManager, MailboxSession}
 import org.apache.james.metrics.api.MetricFactory
 import play.api.libs.json.{JsError, JsSuccess}
-import reactor.core.scala.publisher.SMono
+import reactor.core.scala.publisher.{SFlux, SMono}
+
+import scala.util.Try
+
+object ThreadGetResult {
+  def empty: ThreadGetResult = ThreadGetResult(Set.empty, ThreadNotFound(Set.empty))
+
+  def merge(result1: ThreadGetResult, result2: ThreadGetResult): ThreadGetResult = result1.merge(result2)
+
+  def found(thread: Thread): ThreadGetResult =
+    ThreadGetResult(Set(thread), ThreadNotFound(Set.empty))
+
+  def notFound(unparsedThreadId: UnparsedThreadId): ThreadGetResult =
+    ThreadGetResult(Set.empty, ThreadNotFound(Set(unparsedThreadId)))
+}
+
+case class ThreadGetResult(threads: Set[Thread], notFound: ThreadNotFound) {
+  def merge(other: ThreadGetResult): ThreadGetResult =
+    ThreadGetResult(this.threads ++ other.threads, this.notFound.merge(other.notFound))
+
+  def asResponse(accountId: AccountId): ThreadGetResponse =
+    ThreadGetResponse(
+      accountId = accountId,
+      state = UuidState.INSTANCE,
+      list = threads.toList,
+      notFound = notFound)
+}
 
 class ThreadGetMethod @Inject()(val metricFactory: MetricFactory,
-                                          val sessionSupplier: SessionSupplier) extends MethodRequiringAccountId[ThreadGetRequest] {
+                                val sessionSupplier: SessionSupplier,
+                                val threadIdFactory: JavaThreadId.Factory,
+                                val mailboxManager: MailboxManager) extends MethodRequiringAccountId[ThreadGetRequest] {
   override val methodName: MethodName = MethodName("Thread/get")
   override val requiredCapabilities: Set[CapabilityIdentifier] = Set(JMAP_CORE, JMAP_MAIL)
 
   override def doProcess(capabilities: Set[CapabilityIdentifier], invocation: InvocationWithContext, mailboxSession: MailboxSession, request: ThreadGetRequest): SMono[InvocationWithContext] = {
-    val response = ThreadGetResponse(accountId = request.accountId,
-      state = UuidState.INSTANCE,
-      list = retrieveThreads(request.ids))
-    SMono.just(InvocationWithContext(invocation = Invocation(
-      methodName = methodName,
-      arguments = Arguments(ThreadSerializer.serialize(response)),
-      methodCallId = invocation.invocation.methodCallId),
-      processingContext = invocation.processingContext))
+    getThreadResponse(request, mailboxSession)
+      .reduce(ThreadGetResult.empty)(ThreadGetResult.merge)
+      .map(threadGetResult => threadGetResult.asResponse(request.accountId))
+      .map(threadGetResponse => Invocation(
+        methodName = methodName,
+        arguments = Arguments(ThreadSerializer.serialize(threadGetResponse)),
+        methodCallId = invocation.invocation.methodCallId))
+      .map(InvocationWithContext(_, invocation.processingContext))
   }
 
   override def getRequest(mailboxSession: MailboxSession, invocation: Invocation): Either[IllegalArgumentException, ThreadGetRequest] =
@@ -55,7 +83,18 @@
       case errors: JsError => Left(new IllegalArgumentException(ResponseSerializer.serialize(errors).toString))
     }
 
-  // Naive implementation
-  private def retrieveThreads(ids: List[Id]): List[Thread] =
-    ids.map(id => Thread(id = id, emailIds = List(id)))
+  private def getThreadResponse(threadGetRequest: ThreadGetRequest,
+                                mailboxSession: MailboxSession): SFlux[ThreadGetResult] = {
+    SFlux.fromIterable(threadGetRequest.ids)
+      .flatMap(unparsedThreadId => {
+        Try(threadIdFactory.fromString(unparsedThreadId.id.toString()))
+          .fold(e => SFlux.just(ThreadGetResult.notFound(unparsedThreadId)),
+            threadId => SFlux.fromPublisher(mailboxManager.getThread(threadId, mailboxSession))
+              .collectSeq()
+              .map(seq => Thread(id = unparsedThreadId.id, emailIds = seq.toList))
+              .map(ThreadGetResult.found)
+              .onErrorResume((_ => SMono.just(ThreadGetResult.notFound(unparsedThreadId)))))
+      })
+  }
+
 }
diff --git a/src/adr/0048-cleanup-jmap-uploads.md b/src/adr/0048-cleanup-jmap-uploads.md
new file mode 100644
index 0000000..62ad15d
--- /dev/null
+++ b/src/adr/0048-cleanup-jmap-uploads.md
@@ -0,0 +1,87 @@
+# 48. Cleanup of JMAP uploads
+
+Date: 2021-07-21
+
+## Status
+
+Accepted (lazy consensus).
+
+Not yet implemented.
+
+## Context
+
+JMAP allows users to upload binary content called blobs to be later referenced via method calls. This includes but is not
+limited to `Email/set` for specifying the blobId of attachments and `Email/import`.
+
+The [specification](https://jmap.io/spec-core.html#binary-data) strongly encourages enforcing the cleanup of these uploads:
+
+```
+A blob that is not referenced by a JMAP object (e.g., as a message attachment) MAY be deleted by the server to free up 
+resources. Uploads (see below) are initially unreferenced blobs.
+
+[...] An unreferenced blob MUST NOT be deleted for at least 1 hour from the time of upload; if reuploaded, the same 
+blobId MAY be returned, but this SHOULD reset the expiry time.
+```
+
+Deleting such uploads in a timely manner is important as:
+
+ - It enables freeing server resources.
+ - failing to do so may compromise privacy: content the user have uploaded and long forgotten might still be accessible
+ in the underlying data-store. Failing to delete uploads in a timely fashion may jeopardize for instance GDPR compliance.
+ 
+Today, uploads are stored along side email attachments. This means:
+ - We can hardly apply a specific lifecycle that cleans up uploads, as distinguishing attachment from uploads is not 
+ trivial.
+ - We currently have a complex right resolution system on attachment, handling both the upload case (were the attachment
+ is linked to a user) and the 'true' attachment case (linked to a message, those who can access the message can access 
+ the attachment). This leads to sub-optimal code (slow).
+
+## Decision
+
+We need to create a separate interface `UploadRepository` in `data-jmap` to store uploads for each user. We would provide a memory 
+implementation as well as a distributed implementation of it.
+
+The distributed implementation would host metadata of the upload in Cassandra, and the content using the BlobStore API,
+so object storage.
+
+This `UploadRepository` would be used by JMAP RFC-8620 to back uploads (instead of the attachment manager), we will 
+provide a `BlobResolver` to enable interactions with the uploaded blob. Similarly, we will use the `UploadRepository` to
+back uploads of JMAP draft.
+
+We will implement cleanup of the distributed `UploadRepository`. This will be done via:
+ - TTLs on the Cassandra metadata.
+ - Organisation of the blobs in time ranged buckets, only the two most recent buckets are kept.
+ - A WebAdmin endpoint would allow to plan a CRON triggering the cleanup.
+
+## Consequences
+
+Upon migrating to the `UploadRepository`, previous uploads will not be carried over. No migration plan is provided as 
+the impact is minimal. Upload prior this change will never be cleaned up. This is acceptable as JMAP implementations are
+marked as experimental.
+
+We can clean up attachment storage within the `mailbox-api` and its implementation:
+ - Drop `attachmentOwners` cassandra table
+ - Remove `getOwners` `storeAttachmentForOwner` methods in the Attachment mapper
+ - Rename `storeAttachmentsForMessage*` -> `storeAttachments*` in attachment mapper
+ - Simplify resolution logic for `StoreAttachmentManager` (looking message ownership is then enough)
+ - Fusion of `attachmentMessageId` and `attachmentV2` table, `attachmentMessageId` to be dropped in next release, 
+ `attachmentV2` can be altered to add the referencing `messageId`, and a migration task will be provided to populate it.
+ In the meantime a fallback strategy can be supplied: If the messageId cell is null we should default to reading the 
+ (old) `attachmentMessageId` table.
+ 
+## Alternatives
+
+[JMAP blob draft](https://datatracker.ietf.org/doc/draft-ietf-jmap-blob/) had been proposed to have the clients explicitly
+delete its uploads once the blob had been used to create other entities, as this extension introduce a mean to delete 
+blobs.
+
+However, relying on clients to enforce effective deletion seems brittle as:
+ - In case of client failures (or malicious client), no mechanisms would ensure effective deletion
+ - The main JMAP specification does not mandate nor encourage clients to clean up their uploads using the blob extension
+ and as such interoperability issues would arise.
+
+## References
+
+ - [JIRA](https://issues.apache.org/jira/browse/JAMES-3544)
+ - [PR of this ADR](https://github.com/apache/james-project/pull/544)
+ - [Thread on server-dev mailing list](https://www.mail-archive.com/server-dev@james.apache.org/msg70591.html)
\ No newline at end of file
diff --git a/src/site/xdoc/download.xml b/src/site/xdoc/download.xml
index cc0244e..e9a1f9d 100644
--- a/src/site/xdoc/download.xml
+++ b/src/site/xdoc/download.xml
@@ -164,7 +164,7 @@
           [<a href="https://downloads.apache.org/james/server/3.6.0/james-project-3.6.0-source-release.zip.asc">PGP</a>]</li>
 
         <li>Binary (ZIP Format) for Spring wiring:
-          <a href="https://www.apache.org/dyn/closer.lua/james/server/3.6.0/james-server-app-3.6.0-app.zip">apache-james-3.6.0-app.zip</a>
+          <a href="https://www.apache.org/dyn/closer.lua/james/server/3.6.0/james-server-app-3.6.0-app.zip">james-server-app-3.6.0-app.zip</a>
           [<a href="https://downloads.apache.org/james/server/3.6.0/james-server-app-3.6.0-app.zip.sha512">SHA-512</a>]
           [<a href="https://downloads.apache.org/james/server/3.6.0/james-server-app-3.6.0-app.zip.asc">PGP</a>]
         </li>
@@ -580,27 +580,6 @@
   
   </section>
 
-  <section name="Apache HUPA">
-  
-    <p>Apache HUPA 0.0.2 is the latest stable version:</p>
-    <ul>
-
-      <li>Source (ZIP Format): <a href="https://www.apache.org/dyn/closer.lua/james/0.0.2/hupa-parent-0.0.2-source-release.zip">hupa-parent-0.0.2-source-release.zip</a>
-        [<a href="https://downloads.apache.org/james/hupa/0.0.2/hupa-parent-0.0.2-source-release.zip.sha512">SHA-512</a>]
-        [<a href="https://downloads.apache.org/james/hupa/0.0.2/hupa-parent-0.0.2-source-release.zip.asc">PGP</a>]
-      </li>
-    
-    <li>Binary (Java WAR): <a href="https://www.apache.org/dyn/closer.lua/james/0.0.2/hupa-0.0.2.war">hupa-0.0.2.war</a>
-      [<a href="https://downloads.apache.org/james/hupa/0.0.2/hupa-0.0.2.war.sha512">SHA-512</a>]
-      [<a href="https://downloads.apache.org/james/hupa/0.0.2/hupa-0.0.2.war.asc">PGP</a>]
-    </li>
-    
-    <li>Jars (including source and javadocs) for the modules are distributed through the standard 
-    <a href='https://maven.apache.org'>Maven</a> repositories on <a href="https://repo1.maven.org/maven2/org/apache/james/hupa">https://repo1.maven.org/maven2/org/apache/james/hupa</a>.</li>
-    </ul>
-  
-  </section>
-
 </body>
 
 </document>
diff --git a/upgrade-instructions.md b/upgrade-instructions.md
index f32ffb4..97bed20 100644
--- a/upgrade-instructions.md
+++ b/upgrade-instructions.md
@@ -15,8 +15,33 @@
 Changes to apply between 3.6.x and 3.7.x will be reported here.
 
 Change list:
-
+ - [Adding the threadId to the ElasticSearch index](#adding-the-threadid-to-the-elasticsearch-index)
  - [Rework message denormalization](#rework-message-denormalization)
+ - [Adding threadId column to message metadata tables](#adding-threadid-column-to-message-metadata-tables)
+
+### Adding the threadId to the ElasticSearch index
+
+Date 22/07/2021
+
+JIRA: https://issues.apache.org/jira/browse/JAMES-3516
+
+Concerned product: Distributed James
+
+Add threadId to James document mapping to enable thread query search.
+
+We already have this field as part of newly created mappings, but we need to explicitly add this field to existing indices by doing:
+```
+curl -X PUT \
+  http://ip:ESport/mailbox_v1/_mapping \
+  -H 'Content-Type: application/json' \
+  -d '{
+    "properties": {
+        "threadId": {
+            "type": "keyword"
+        }
+    }
+}'
+```
 
 ### Rework message denormalization