JAMES-3374: Add Sort by ReceivedAt + integration 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/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 a262e44..7792c9a 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
@@ -20,8 +20,8 @@
 package org.apache.james.jmap.rfc8621.contract
 
 import java.nio.charset.StandardCharsets
-import java.time.ZonedDateTime
 import java.time.format.DateTimeFormatter
+import java.time.{Instant, ZonedDateTime}
 import java.util.Date
 import java.util.concurrent.TimeUnit
 import java.util.stream.Stream
@@ -47,6 +47,7 @@
 import org.junit.jupiter.api.{BeforeEach, Test}
 import org.junit.jupiter.params.ParameterizedTest
 import org.junit.jupiter.params.provider.{Arguments, MethodSource}
+import org.threeten.extra.Seconds
 
 object EmailQueryMethodContract {
   def jmapSystemKeywords : Stream[Arguments] = {
@@ -59,6 +60,7 @@
   }
 }
 
+
 trait EmailQueryMethodContract {
 
   private lazy val slowPacedPollInterval = ONE_HUNDRED_MILLISECONDS
@@ -328,6 +330,230 @@
   }
 
   @Test
+  def listMailsShouldBeSortedInAscendingOrderByDefault(server: GuiceJamesServer): Unit = {
+    val message: Message = Message.Builder
+      .of
+      .setSubject("test")
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+    val otherMailboxPath = MailboxPath.forUser(BOB, "other")
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
+    val now: Instant = Instant.now()
+    val requestDateMessage1: Date = Date.from(now)
+    val messageId1 = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withInternalDate(requestDateMessage1).build(message))
+      .getMessageId
+    val requestDateMessage2: Date = Date.from(now.plus(Seconds.of(3)))
+    val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withInternalDate(requestDateMessage2).build(message))
+      .getMessageId
+    val requestDateMessage3: Date = Date.from(now.plus(Seconds.of(6)))
+    val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withInternalDate(requestDateMessage3).build(message))
+      .getMessageId
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "comparator": [{
+         |        "property":"receivedAt"
+         |      }]
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      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].ids")
+      .isEqualTo(s"""["${messageId1.serialize()}", "${messageId2.serialize()}", "${messageId3.serialize()}"]""")
+    }
+  }
+
+  @Test
+  def listMailsShouldBeSortedInAscendingOrder(server: GuiceJamesServer): Unit = {
+    val message: Message = Message.Builder
+      .of
+      .setSubject("test")
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+    val now: Instant = Instant.now()
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+    val otherMailboxPath = MailboxPath.forUser(BOB, "other")
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
+    val requestDateMessage1: Date = Date.from(now)
+    val messageId1 = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withInternalDate(requestDateMessage1).build(message))
+      .getMessageId
+    val requestDateMessage2: Date = Date.from(now.plus(Seconds.of(3)))
+    val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withInternalDate(requestDateMessage2).build(message))
+      .getMessageId
+    val requestDateMessage3: Date = Date.from(now.plus(Seconds.of(6)))
+    val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withInternalDate(requestDateMessage3).build(message))
+      .getMessageId
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "comparator": [{
+         |        "property":"receivedAt",
+         |        "isAscending": true
+         |      }]
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      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].ids")
+      .isEqualTo(s"""["${messageId1.serialize()}", "${messageId2.serialize()}", "${messageId3.serialize()}"]""")
+    }
+  }
+
+  @Test
+  def listMailsShouldBeSortedInDescendingOrder(server: GuiceJamesServer): Unit = {
+    val message: Message = Message.Builder
+      .of
+      .setSubject("test")
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+    val now: Instant = Instant.now()
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+    val otherMailboxPath = MailboxPath.forUser(BOB, "other")
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
+    val requestDateMessage1: Date = Date.from(now)
+    val messageId1 = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withInternalDate(requestDateMessage1).build(message))
+      .getMessageId
+    val requestDateMessage2: Date = Date.from(now.plus(Seconds.of(3)))
+    val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withInternalDate(requestDateMessage2).build(message))
+      .getMessageId
+    val requestDateMessage3: Date = Date.from(now.plus(Seconds.of(6)))
+    val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.builder().withInternalDate(requestDateMessage3).build(message))
+      .getMessageId
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "comparator": [{
+         |        "property":"receivedAt",
+         |        "isAscending": false
+         |      }]
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      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].ids")
+      .isEqualTo(s"""["${messageId3.serialize()}", "${messageId2.serialize()}", "${messageId1.serialize()}"]""")
+    }
+  }
+
+  @Test
+  def listMailsShouldReturnErrorWhenPropertyFieldInComparatorIsOmitted(server: GuiceJamesServer): Unit = {
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+    val otherMailboxPath = MailboxPath.forUser(BOB, "other")
+    val otherMailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "filter": {
+         |        "inMailbox": "${otherMailboxId.serialize()}"
+         |        },
+         |      "comparator": [{
+         |        "isAscending":true
+         |      }]
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      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("""
+         {
+            "type": "invalidArguments",
+            "description": "{\"errors\":[{\"path\":\"obj.comparator[0].property\",\"messages\":[\"error.path.missing\"]}]}"
+          }
+         """)
+    }
+  }
+
+  @Test
   def shouldListMailsInASpecificUserMailboxes(server: GuiceJamesServer): Unit = {
     val message: Message = Message.Builder
       .of
@@ -380,6 +606,55 @@
   }
 
   @Test
+  def listMailsShouldReturnErrorWhenPropertyFieldInComparatorIsInvalid(server: GuiceJamesServer): Unit = {
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+    val otherMailboxPath = MailboxPath.forUser(BOB, "other")
+    val otherMailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "filter": {
+         |        "inMailbox": "${otherMailboxId.serialize()}"
+         |        },
+         |      "comparator": [{
+         |        "property":"unsupported",
+         |        "isAscending":true
+         |      }]
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      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("""
+         {
+            "type": "invalidArguments",
+            "description": "{\"errors\":[{\"path\":\"obj.comparator[0].property\",\"messages\":[\"'unsupported' is not a supported sort property\"]}]}"
+          }
+         """)
+    }
+  }
+
+  @Test
   def shouldReturnIllegalArgumentErrorForAnUnknownSpecificUserMailboxes(server: GuiceJamesServer): Unit = {
     val message: Message = Message.Builder
       .of
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 83c8b0f..8251f96 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
@@ -20,8 +20,8 @@
 package org.apache.james.jmap.json
 
 import javax.inject.Inject
-import org.apache.james.jmap.mail.{EmailQueryRequest, EmailQueryResponse, FilterCondition}
-import org.apache.james.jmap.model._
+import org.apache.james.jmap.mail.{CanCalculateChanges, Collation, Comparator, EmailQueryRequest, EmailQueryResponse, FilterCondition, IsAscending, Limit, Position, QueryState, ReceivedAtSortProperty, SortProperty}
+import org.apache.james.jmap.model.{AccountId, Keyword}
 import org.apache.james.mailbox.model.{MailboxId, MessageId}
 import play.api.libs.json._
 
@@ -49,13 +49,28 @@
     case _ => JsError("Expecting keywords to be represented by a JsString")
   }
   private implicit val filterConditionReads: Reads[FilterCondition] = Json.reads[FilterCondition]
-  private implicit val emailQueryRequestReads: Reads[EmailQueryRequest] = Json.reads[EmailQueryRequest]
-  private implicit val canCalculateChangeWrites: Writes[CanCalculateChange] = Json.valueWrites[CanCalculateChange]
+  private implicit val CanCalculateChangesFormat: Format[CanCalculateChanges] = Json.valueFormat[CanCalculateChanges]
+
   private implicit val queryStateWrites: Writes[QueryState] = Json.valueWrites[QueryState]
   private implicit val positionFormat: Format[Position] = Json.valueFormat[Position]
   private implicit val limitFormat: Format[Limit] = Json.valueFormat[Limit]
   private implicit val messageIdWrites: Writes[MessageId] = id => JsString(id.serialize())
 
+  private implicit val sortPropertyReads: Reads[SortProperty] = {
+    case JsString("receivedAt") => JsSuccess(ReceivedAtSortProperty)
+    case JsString(others) => JsError(s"'$others' is not a supported sort property")
+    case _ => JsError(s"Expecting a JsString to represent a sort property")
+  }
+  private implicit val sortPropertyWrites: Writes[SortProperty] = {
+    case ReceivedAtSortProperty => JsString("receivedAt")
+  }
+
+  private implicit val isAscendingFormat: Format[IsAscending] = Json.valueFormat[IsAscending]
+  private implicit val collationFormat: Format[Collation] = Json.valueFormat[Collation]
+  private implicit val comparatorFormat: Format[Comparator] = Json.format[Comparator]
+
+  private implicit val emailQueryRequestReads: Reads[EmailQueryRequest] = Json.reads[EmailQueryRequest]
+
   private implicit def emailQueryResponseWrites: OWrites[EmailQueryResponse] = Json.writes[EmailQueryResponse]
 
   def serialize(emailQueryResponse: EmailQueryResponse): JsObject = Json.toJsObject(emailQueryResponse)
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala
index 7e9a012..f5b282e 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala
@@ -19,8 +19,13 @@
 
 package org.apache.james.jmap.mail
 
-import org.apache.james.jmap.model.{AccountId, CanCalculateChange, Keyword, Limit, Position, QueryState, UTCDate}
-import org.apache.james.mailbox.model.{MailboxId, MessageId}
+import org.apache.james.jmap.model.{Keyword, UTCDate}
+import com.google.common.hash.Hashing
+import org.apache.james.jmap.mail.IsAscending.{ASCENDING, DESCENDING}
+import org.apache.james.jmap.model.AccountId
+import org.apache.james.mailbox.model.SearchQuery.Sort.Order.{NATURAL, REVERSE}
+import org.apache.james.mailbox.model.SearchQuery.Sort.SortClause
+import org.apache.james.mailbox.model.{MailboxId, MessageId, SearchQuery}
 
 case class FilterCondition(inMailbox: Option[MailboxId],
                            inMailboxOtherThan: Option[Seq[MailboxId]],
@@ -29,11 +34,65 @@
                            hasKeyword: Option[Keyword],
                            notKeyword: Option[Keyword])
 
-case class EmailQueryRequest(accountId: AccountId, filter: Option[FilterCondition])
+case class EmailQueryRequest(accountId: AccountId, filter: Option[FilterCondition], comparator: Option[Set[Comparator]])
+
+case class Position(value: Int) extends AnyVal
+object Position{
+  val zero: Position = Position(0)
+}
+case class Limit(value: Long) extends AnyVal
+object Limit {
+  val default: Limit = Limit(256L)
+}
+case class QueryState(value: String) extends AnyVal
+
+sealed trait SortProperty {
+  def toSortClause: SortClause
+}
+case object ReceivedAtSortProperty extends SortProperty {
+  override def toSortClause: SortClause = SortClause.Arrival
+}
+
+object IsAscending {
+  val DESCENDING: IsAscending = IsAscending(false)
+  val ASCENDING: IsAscending = IsAscending(true)
+}
+case class IsAscending(sortByASC: Boolean) extends AnyVal {
+  def toSortOrder: SearchQuery.Sort.Order = if (sortByASC) {
+    NATURAL
+  } else {
+    REVERSE
+  }
+}
+
+object Comparator {
+  val default: Comparator = Comparator(ReceivedAtSortProperty, Some(DESCENDING), None)
+}
+
+case class Collation(value: String) extends AnyVal
+
+case class Comparator(property: SortProperty,
+                      isAscending: Option[IsAscending],
+                      collation: Option[Collation]) {
+  def toSort: SearchQuery.Sort = new SearchQuery.Sort(property.toSortClause, isAscending.getOrElse(ASCENDING).toSortOrder)
+}
+
+object QueryState {
+  def forIds(ids: Seq[MessageId]): QueryState = QueryState(
+    Hashing.murmur3_32()
+      .hashUnencodedChars(ids.map(_.serialize()).mkString(" "))
+      .toString)
+}
+
+object CanCalculateChanges {
+  val CANT: CanCalculateChanges = CanCalculateChanges(false)
+}
+
+case class CanCalculateChanges(value: Boolean) extends AnyVal
 
 case class EmailQueryResponse(accountId: AccountId,
                               queryState: QueryState,
-                              canCalculateChanges: CanCalculateChange,
+                              canCalculateChanges: CanCalculateChanges,
                               ids: Seq[MessageId],
                               position: Position,
                               limit: Option[Limit])
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala
index 32cbe3f..ab5ddb8 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala
@@ -18,22 +18,18 @@
  ****************************************************************/
 package org.apache.james.jmap.method
 
-import java.util.Date
-
 import eu.timepit.refined.auto._
 import javax.inject.Inject
 import org.apache.james.jmap.json.{EmailQuerySerializer, ResponseSerializer}
-import org.apache.james.jmap.mail.{EmailQueryRequest, EmailQueryResponse}
+import org.apache.james.jmap.mail.{CanCalculateChanges, Comparator, EmailQueryRequest, EmailQueryResponse, Limit, Position, QueryState}
 import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
 import org.apache.james.jmap.model.DefaultCapabilities.{CORE_CAPABILITY, MAIL_CAPABILITY}
 import org.apache.james.jmap.model.Invocation.{Arguments, MethodName}
-import org.apache.james.jmap.model._
+import org.apache.james.jmap.model.{Capabilities, ErrorCode, Invocation}
 import org.apache.james.jmap.routes.ProcessingContext
-import org.apache.james.mailbox.exception.MailboxNotFoundException
-import org.apache.james.jmap.utils.search.MailboxFilter
 import org.apache.james.jmap.utils.search.MailboxFilter.QueryFilter
-import org.apache.james.mailbox.model.SearchQuery.{Conjunction, ConjunctionCriterion, Criterion, DateComparator, DateOperator, DateResolution, InternalDateCriterion}
-import org.apache.james.mailbox.model.SearchQuery.Sort.SortClause
+import org.apache.james.jmap.utils.search.MailboxFilter
+import org.apache.james.mailbox.exception.MailboxNotFoundException
 import org.apache.james.mailbox.model.{MultimailboxesSearchQuery, SearchQuery}
 import org.apache.james.mailbox.{MailboxManager, MailboxSession}
 import org.apache.james.metrics.api.MetricFactory
@@ -41,7 +37,7 @@
 import play.api.libs.json.{JsError, JsSuccess}
 import reactor.core.scala.publisher.{SFlux, SMono}
 
-import scala.collection.JavaConverters.seqAsJavaListConverter
+import scala.jdk.CollectionConverters._
 
 class EmailQueryMethod @Inject() (serializer: EmailQuerySerializer,
                                   mailboxManager: MailboxManager,
@@ -61,12 +57,13 @@
         .map(invocationResult => (invocationResult, processingContext)))
 
   private def processRequest(mailboxSession: MailboxSession, invocation: Invocation, request: EmailQueryRequest): SMono[Invocation] = {
-    val searchQuery = searchQueryFromRequest(request)
+    val searchQuery: MultimailboxesSearchQuery = searchQueryFromRequest(request)
+
     SFlux.fromPublisher(mailboxManager.search(searchQuery, mailboxSession, Limit.default.value))
       .collectSeq()
       .map(ids => EmailQueryResponse(accountId = request.accountId,
         queryState = QueryState.forIds(ids),
-        canCalculateChanges = CanCalculateChange(false),
+        canCalculateChanges = CanCalculateChanges.CANT,
         ids = ids,
         position = Position.zero,
         limit = Some(Limit.default)))
@@ -74,11 +71,12 @@
   }
 
   private def searchQueryFromRequest(request: EmailQueryRequest): MultimailboxesSearchQuery = {
-    val query = QueryFilter.buildQuery(request)
-    val defaultSort = new SearchQuery.Sort(SortClause.Arrival, SearchQuery.Sort.Order.REVERSE)
-    val querySorted = query.sorts(defaultSort)
+    val comparators: List[Comparator] = request.comparator.getOrElse(Set(Comparator.default)).toList
+    val sortedSearchQuery: SearchQuery = QueryFilter.buildQuery(request)
+      .sorts(comparators.map(_.toSort).asJava)
+      .build()
 
-    MailboxFilter.buildQuery(request, querySorted.build())
+    MailboxFilter.buildQuery(request, sortedSearchQuery)
   }
 
   private def asEmailQueryRequest(arguments: Arguments): SMono[EmailQueryRequest] =