You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2022/12/12 06:13:44 UTC

[james-project] 01/08: JAMES-3754 Add saveDate field to MailboxMessage and associated Cassandra tables

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

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

commit 439c961a4c413475686633e5bf66c61acee60915
Author: Quan Tran <hq...@linagora.com>
AuthorDate: Tue Nov 29 16:23:53 2022 +0700

    JAMES-3754 Add saveDate field to MailboxMessage and associated Cassandra tables
    
    Add saveDate column to`messageIdTable` and `imapUidTable`
---
 .../apache/james/mailbox/MailboxManagerTest.java   | 20 +++++++
 mailbox/cassandra/pom.xml                          |  5 ++
 .../mailbox/cassandra/CassandraMailboxManager.java |  9 ++--
 .../mailbox/cassandra/CassandraMessageManager.java |  7 ++-
 .../cassandra/mail/CassandraMessageIdDAO.java      |  5 ++
 .../mail/CassandraMessageIdToImapUidDAO.java       |  8 +++
 .../cassandra/mail/CassandraMessageMetadata.java   | 34 ++++++++++--
 .../cassandra/modules/CassandraMessageModule.java  |  2 +
 .../cassandra/table/CassandraMessageIdTable.java   |  2 +
 .../cassandra/CassandraMailboxManagerProvider.java | 12 +++--
 .../cassandra/CassandraTestSystemFixture.java      |  5 +-
 .../CassandraMailboxManagerAttachmentTest.java     |  6 ++-
 .../cassandra/mail/CassandraMessageDAOV3Test.java  | 18 ++++---
 .../cassandra/mail/CassandraMessageIdDAOTest.java  | 52 ++++++++++++++++++
 .../mail/CassandraMessageIdToImapUidDAOTest.java   | 51 ++++++++++++++++++
 mailbox/jpa/pom.xml                                |  5 ++
 .../model/openjpa/AbstractJPAMailboxMessage.java   |  6 +++
 .../mailbox/jpa/openjpa/OpenJPAMailboxManager.java |  9 ++--
 .../mailbox/jpa/openjpa/OpenJPAMessageFactory.java |  2 +-
 .../mailbox/jpa/openjpa/OpenJPAMessageManager.java |  6 ++-
 .../james/mailbox/jpa/JPAMailboxManagerTest.java   |  6 +++
 .../mailbox/jpa/JpaMailboxManagerProvider.java     |  5 +-
 mailbox/memory/pom.xml                             |  5 ++
 .../mailbox/inmemory/InMemoryMailboxManager.java   |  9 ++--
 .../mailbox/inmemory/InMemoryMessageManager.java   |  7 ++-
 .../manager/InMemoryIntegrationResources.java      |  6 ++-
 mailbox/store/pom.xml                              |  5 ++
 .../apache/james/mailbox/store/MessageFactory.java |  7 +--
 .../apache/james/mailbox/store/MessageStorer.java  | 34 +++++++-----
 .../james/mailbox/store/StoreMailboxManager.java   | 13 +++--
 .../james/mailbox/store/mail/MessageMapper.java    |  1 +
 .../store/mail/model/DelegatingMailboxMessage.java |  1 +
 .../mailbox/store/mail/model/MailboxMessage.java   |  7 +++
 .../mail/model/impl/SimpleMailboxMessage.java      | 34 +++++++++---
 .../apache/james/mailbox/store/MessageBuilder.java |  4 +-
 .../mailbox/store/StoreMailboxManagerTest.java     |  7 ++-
 .../model/MessageWithAttachmentMapperTest.java     |  3 +-
 .../mail/model/impl/SimpleMailboxMessageTest.java  | 63 +++++++++++++++++++++-
 mpt/impl/imap-mailbox/cassandra/pom.xml            |  5 ++
 .../cassandra/host/CassandraHostSystem.java        |  6 ++-
 mpt/impl/imap-mailbox/jpa/pom.xml                  |  5 ++
 .../mpt/imapmailbox/jpa/host/JPAHostSystem.java    |  5 +-
 42 files changed, 435 insertions(+), 67 deletions(-)

diff --git a/mailbox/api/src/test/java/org/apache/james/mailbox/MailboxManagerTest.java b/mailbox/api/src/test/java/org/apache/james/mailbox/MailboxManagerTest.java
index ed642c8c1f..033d9b6bfe 100644
--- a/mailbox/api/src/test/java/org/apache/james/mailbox/MailboxManagerTest.java
+++ b/mailbox/api/src/test/java/org/apache/james/mailbox/MailboxManagerTest.java
@@ -2873,6 +2873,26 @@ public abstract class MailboxManagerTest<T extends MailboxManager> {
         }
     }
 
+    @Nested
+    class SaveDateTests {
+        private MessageManager inboxManager;
+
+        @BeforeEach
+        void setUp() throws Exception {
+            session = mailboxManager.createSystemSession(USER_1);
+            MailboxPath inbox = MailboxPath.inbox(session);
+            mailboxManager.createMailbox(inbox, session).get();
+            inboxManager = mailboxManager.getMailbox(inbox, session);
+        }
+
+        @Test
+        void shouldSetSaveDateWhenAppendMessage() throws Exception {
+            ComposedMessageId composeId1 = inboxManager.appendMessage(AppendCommand.builder().build(message), session).getId();
+            MessageResult messageResult = inboxManager.getMessages(MessageRange.one(composeId1.getUid()), FetchGroup.MINIMAL, session).next();
+            assertThat(messageResult.getSaveDate()).isPresent();
+        }
+    }
+
     @Nested
     class RightTests {
 
diff --git a/mailbox/cassandra/pom.xml b/mailbox/cassandra/pom.xml
index 290df88663..4b4bd0dc9a 100644
--- a/mailbox/cassandra/pom.xml
+++ b/mailbox/cassandra/pom.xml
@@ -129,6 +129,11 @@
             <artifactId>james-server-task-memory</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-testing</artifactId>
+            <scope>runtime</scope>
+        </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
             <artifactId>james-server-util</artifactId>
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxManager.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxManager.java
index a81735072b..5ca039320a 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxManager.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxManager.java
@@ -19,6 +19,7 @@
 
 package org.apache.james.mailbox.cassandra;
 
+import java.time.Clock;
 import java.util.EnumSet;
 
 import javax.inject.Inject;
@@ -66,7 +67,7 @@ public class CassandraMailboxManager extends StoreMailboxManager {
                                    StoreMailboxAnnotationManager annotationManager, StoreRightManager storeRightManager,
                                    QuotaComponents quotaComponents, MessageSearchIndex index,
                                    MailboxManagerConfiguration configuration,
-                                   PreDeletionHooks preDeletionHooks, ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm) {
+                                   PreDeletionHooks preDeletionHooks, ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm, Clock clock) {
         super(mapperFactory,
             sessionProvider,
             locker,
@@ -79,7 +80,8 @@ public class CassandraMailboxManager extends StoreMailboxManager {
             index,
             configuration,
             preDeletionHooks,
-            threadIdGuessingAlgorithm);
+            threadIdGuessingAlgorithm,
+            clock);
         this.locker = locker;
         this.mapperFactory = mapperFactory;
     }
@@ -108,7 +110,8 @@ public class CassandraMailboxManager extends StoreMailboxManager {
             configuration.getBatchSizes(),
             getStoreRightManager(),
             getPreDeletionHooks(),
-            getThreadIdGuessingAlgorithm());
+            getThreadIdGuessingAlgorithm(),
+            getClock());
     }
 
     @Override
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMessageManager.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMessageManager.java
index cbbe0f4b36..0c02d5d6f0 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMessageManager.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMessageManager.java
@@ -19,6 +19,8 @@
 
 package org.apache.james.mailbox.cassandra;
 
+import java.time.Clock;
+
 import javax.mail.Flags;
 
 import org.apache.james.events.EventBus;
@@ -49,10 +51,11 @@ public class CassandraMessageManager extends StoreMessageManager {
                             BatchSizes batchSizes,
                             StoreRightManager storeRightManager,
                             PreDeletionHooks preDeletionHooks,
-                            ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm) {
+                            ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm,
+                            Clock clock) {
         super(CassandraMailboxManager.MESSAGE_CAPABILITIES, mapperFactory, index, eventBus, locker, mailbox,
             quotaManager, quotaRootResolver, batchSizes, storeRightManager,
-            preDeletionHooks, new MessageStorer.WithAttachment(mapperFactory, messageIdFactory, new MessageFactory.StoreMessageFactory(), mapperFactory, messageParser, threadIdGuessingAlgorithm));
+            preDeletionHooks, new MessageStorer.WithAttachment(mapperFactory, messageIdFactory, new MessageFactory.StoreMessageFactory(), mapperFactory, messageParser, threadIdGuessingAlgorithm, clock));
     }
 
     /**
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdDAO.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdDAO.java
index 4d4177811a..540a33939b 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdDAO.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdDAO.java
@@ -26,6 +26,7 @@ import static com.datastax.oss.driver.api.querybuilder.relation.Relation.column;
 import static com.datastax.oss.driver.api.querybuilder.update.Assignment.append;
 import static com.datastax.oss.driver.api.querybuilder.update.Assignment.remove;
 import static com.datastax.oss.driver.api.querybuilder.update.Assignment.setColumn;
+import static org.apache.james.mailbox.cassandra.table.CassandraMessageIdTable.SAVE_DATE;
 import static org.apache.james.mailbox.cassandra.table.CassandraMessageIdTable.TABLE_NAME;
 import static org.apache.james.mailbox.cassandra.table.CassandraMessageIdTable.THREAD_ID;
 import static org.apache.james.mailbox.cassandra.table.CassandraMessageIds.IMAP_UID;
@@ -171,6 +172,7 @@ public class CassandraMessageIdDAO {
                 setColumn(SEEN, bindMarker(SEEN)),
                 setColumn(USER, bindMarker(USER)),
                 setColumn(INTERNAL_DATE, bindMarker(INTERNAL_DATE)),
+                setColumn(SAVE_DATE, bindMarker(SAVE_DATE)),
                 setColumn(BODY_START_OCTET, bindMarker(BODY_START_OCTET)),
                 setColumn(FULL_CONTENT_OCTETS, bindMarker(FULL_CONTENT_OCTETS)),
                 setColumn(HEADER_CONTENT, bindMarker(HEADER_CONTENT)),
@@ -342,6 +344,7 @@ public class CassandraMessageIdDAO {
             .setBoolean(SEEN, flags.contains(Flag.SEEN))
             .setBoolean(USER, flags.contains(Flag.USER))
             .setInstant(INTERNAL_DATE, metadata.getInternalDate().get().toInstant())
+            .setInstant(SAVE_DATE, metadata.getSaveDate().map(Date::toInstant).orElse(null))
             .setInt(BODY_START_OCTET, Math.toIntExact(metadata.getBodyStartOctet().get()))
             .setLong(FULL_CONTENT_OCTETS, metadata.getSize().get())
             .setString(HEADER_CONTENT, metadata.getHeaderContent().get().asString())
@@ -566,6 +569,8 @@ public class CassandraMessageIdDAO {
             .bodyStartOctet(row.get(BODY_START_OCTET, Integer.class))
             .internalDate(Optional.ofNullable(row.get(INTERNAL_DATE, TypeCodecs.TIMESTAMP))
                 .map(Date::from))
+            .saveDate(Optional.ofNullable(row.get(SAVE_DATE, TypeCodecs.TIMESTAMP))
+                .map(Date::from))
             .size(row.get(FULL_CONTENT_OCTETS, Long.class))
             .headerContent(Optional.ofNullable(row.get(HEADER_CONTENT, TypeCodecs.TEXT))
                 .map(blobIdFactory::from))
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdToImapUidDAO.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdToImapUidDAO.java
index 19d450e1ac..315d7cf535 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdToImapUidDAO.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdToImapUidDAO.java
@@ -26,6 +26,7 @@ import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.selectFrom;
 import static com.datastax.oss.driver.api.querybuilder.relation.Relation.column;
 import static com.datastax.oss.driver.api.querybuilder.update.Assignment.setColumn;
 import static org.apache.james.backends.cassandra.init.configuration.JamesExecutionProfiles.ConsistencyChoice.STRONG;
+import static org.apache.james.mailbox.cassandra.table.CassandraMessageIdTable.SAVE_DATE;
 import static org.apache.james.mailbox.cassandra.table.CassandraMessageIds.IMAP_UID;
 import static org.apache.james.mailbox.cassandra.table.CassandraMessageIds.MAILBOX_ID;
 import static org.apache.james.mailbox.cassandra.table.CassandraMessageIds.MESSAGE_ID;
@@ -144,6 +145,7 @@ public class CassandraMessageIdToImapUidDAO {
             .value(USER, bindMarker(USER))
             .value(USER_FLAGS, bindMarker(USER_FLAGS))
             .value(INTERNAL_DATE, bindMarker(INTERNAL_DATE))
+            .value(SAVE_DATE, bindMarker(SAVE_DATE))
             .value(BODY_START_OCTET, bindMarker(BODY_START_OCTET))
             .value(FULL_CONTENT_OCTETS, bindMarker(FULL_CONTENT_OCTETS))
             .value(HEADER_CONTENT, bindMarker(HEADER_CONTENT));
@@ -161,6 +163,7 @@ public class CassandraMessageIdToImapUidDAO {
                     setColumn(SEEN, bindMarker(SEEN)),
                     setColumn(USER, bindMarker(USER)),
                     setColumn(INTERNAL_DATE, bindMarker(INTERNAL_DATE)),
+                    setColumn(SAVE_DATE, bindMarker(SAVE_DATE)),
                     setColumn(BODY_START_OCTET, bindMarker(BODY_START_OCTET)),
                     setColumn(FULL_CONTENT_OCTETS, bindMarker(FULL_CONTENT_OCTETS)),
                     setColumn(HEADER_CONTENT, bindMarker(HEADER_CONTENT)))
@@ -187,6 +190,7 @@ public class CassandraMessageIdToImapUidDAO {
             .value(USER, bindMarker(USER))
             .value(USER_FLAGS, bindMarker(USER_FLAGS))
             .value(INTERNAL_DATE, bindMarker(INTERNAL_DATE))
+            .value(SAVE_DATE, bindMarker(SAVE_DATE))
             .value(BODY_START_OCTET, bindMarker(BODY_START_OCTET))
             .value(FULL_CONTENT_OCTETS, bindMarker(FULL_CONTENT_OCTETS))
             .value(HEADER_CONTENT, bindMarker(HEADER_CONTENT));
@@ -267,6 +271,7 @@ public class CassandraMessageIdToImapUidDAO {
             .setBoolean(SEEN, flags.contains(Flag.SEEN))
             .setBoolean(USER, flags.contains(Flag.USER))
             .setInstant(INTERNAL_DATE, metadata.getInternalDate().get().toInstant())
+            .setInstant(SAVE_DATE, metadata.getSaveDate().map(Date::toInstant).orElse(null))
             .setInt(BODY_START_OCTET, Math.toIntExact(metadata.getBodyStartOctet().get()))
             .setLong(FULL_CONTENT_OCTETS, metadata.getSize().get())
             .setString(HEADER_CONTENT, metadata.getHeaderContent().get().asString())
@@ -290,6 +295,7 @@ public class CassandraMessageIdToImapUidDAO {
             .setBoolean(USER, flags.contains(Flag.USER))
             .setSet(USER_FLAGS, ImmutableSet.copyOf(flags.getUserFlags()), String.class)
             .setInstant(INTERNAL_DATE, metadata.getInternalDate().get().toInstant())
+            .setInstant(SAVE_DATE, metadata.getSaveDate().map(Date::toInstant).orElse(null))
             .setInt(BODY_START_OCTET, Math.toIntExact(metadata.getBodyStartOctet().get()))
             .setLong(FULL_CONTENT_OCTETS, metadata.getSize().get())
             .setString(HEADER_CONTENT, metadata.getHeaderContent().get().asString()));
@@ -398,6 +404,8 @@ public class CassandraMessageIdToImapUidDAO {
             .bodyStartOctet(row.get(BODY_START_OCTET, Integer.class))
             .internalDate(Optional.ofNullable(row.get(INTERNAL_DATE, TypeCodecs.TIMESTAMP))
                 .map(Date::from))
+            .saveDate(Optional.ofNullable(row.get(SAVE_DATE, TypeCodecs.TIMESTAMP))
+                .map(Date::from))
             .size(row.get(FULL_CONTENT_OCTETS, Long.class))
             .headerContent(Optional.ofNullable(row.getString(HEADER_CONTENT))
                 .map(blobIdFactory::from))
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMetadata.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMetadata.java
index 16ede5de7c..0e34163bf5 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMetadata.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMetadata.java
@@ -53,12 +53,14 @@ public class CassandraMessageMetadata {
     public static class Builder {
         private ComposedMessageIdWithMetaData composedMessageId;
         private Optional<Date> internalDate;
+        private Optional<Date> saveDate;
         private Optional<Long> bodyStartOctet;
         private Optional<Long> size;
         private Optional<BlobId> headerContent;
 
         public Builder() {
             this.internalDate = Optional.empty();
+            this.saveDate = Optional.empty();
             this.size = Optional.empty();
             this.headerContent = Optional.empty();
             this.bodyStartOctet = Optional.empty();
@@ -79,6 +81,16 @@ public class CassandraMessageMetadata {
             return this;
         }
 
+        public Builder saveDate(Date date) {
+            this.saveDate = Optional.ofNullable(date);
+            return this;
+        }
+
+        public Builder saveDate(Optional<Date> date) {
+            this.saveDate = date;
+            return this;
+        }
+
         public Builder bodyStartOctet(Long bodyStartOctet) {
             this.bodyStartOctet = Optional.ofNullable(bodyStartOctet);
             return this;
@@ -113,7 +125,7 @@ public class CassandraMessageMetadata {
         public CassandraMessageMetadata build() {
             Preconditions.checkState(composedMessageId != null, "'composedMessageId' is compulsory");
 
-            return new CassandraMessageMetadata(composedMessageId, internalDate, bodyStartOctet, size, headerContent);
+            return new CassandraMessageMetadata(composedMessageId, internalDate, saveDate, bodyStartOctet, size, headerContent);
         }
     }
 
@@ -266,6 +278,11 @@ public class CassandraMessageMetadata {
             return delegate.getAttachments();
         }
 
+        @Override
+        public Optional<Date> getSaveDate() {
+            return delegate.getSaveDate();
+        }
+
         public BlobId getHeaderBlobId() {
             return headerBlobId;
         }
@@ -289,6 +306,7 @@ public class CassandraMessageMetadata {
                 .threadId(mailboxMessage.getThreadId())
                 .build(),
             Optional.of(mailboxMessage.getInternalDate()),
+            mailboxMessage.getSaveDate(),
             Optional.of(mailboxMessage.getHeaderOctets()),
             Optional.of(mailboxMessage.getFullContentOctets()),
             Optional.of(headerBlobId));
@@ -303,13 +321,15 @@ public class CassandraMessageMetadata {
 
     private final ComposedMessageIdWithMetaData composedMessageId;
     private final Optional<Date> internalDate;
+    private final Optional<Date> saveDate;
     private final Optional<Long> bodyStartOctet;
     private final Optional<Long> size;
     private final Optional<BlobId> headerContent;
 
-    public CassandraMessageMetadata(ComposedMessageIdWithMetaData composedMessageId, Optional<Date> internalDate, Optional<Long> bodyStartOctet, Optional<Long> size, Optional<BlobId> headerContent) {
+    public CassandraMessageMetadata(ComposedMessageIdWithMetaData composedMessageId, Optional<Date> internalDate, Optional<Date> saveDate, Optional<Long> bodyStartOctet, Optional<Long> size, Optional<BlobId> headerContent) {
         this.composedMessageId = composedMessageId;
         this.internalDate = internalDate;
+        this.saveDate = saveDate;
         this.bodyStartOctet = bodyStartOctet;
         this.size = size;
         this.headerContent = headerContent;
@@ -337,6 +357,7 @@ public class CassandraMessageMetadata {
                 .size(size.get())
                 .bodyStartOctet(Math.toIntExact(bodyStartOctet.get()))
                 .internalDate(internalDate.get())
+                .saveDate(saveDate)
                 .properties(new PropertyBuilder())
                 .build(),
             getHeaderContent().get());
@@ -362,9 +383,14 @@ public class CassandraMessageMetadata {
         return size;
     }
 
+    public Optional<Date> getSaveDate() {
+        return saveDate;
+    }
+
     public CassandraMessageMetadata withMailboxId(MailboxId mailboxId) {
         return builder()
             .internalDate(internalDate)
+            .saveDate(saveDate)
             .size(size)
             .bodyStartOctet(bodyStartOctet)
             .headerContent(headerContent)
@@ -385,6 +411,7 @@ public class CassandraMessageMetadata {
             CassandraMessageMetadata other = (CassandraMessageMetadata) obj;
             return Objects.equal(this.composedMessageId, other.composedMessageId)
                 && Objects.equal(this.internalDate, other.internalDate)
+                && Objects.equal(this.saveDate, other.saveDate)
                 && Objects.equal(this.bodyStartOctet, other.bodyStartOctet)
                 && Objects.equal(this.size, other.size)
                 && Objects.equal(this.headerContent, other.headerContent);
@@ -394,7 +421,7 @@ public class CassandraMessageMetadata {
 
     @Override
     public int hashCode() {
-        return Objects.hashCode(composedMessageId, internalDate, bodyStartOctet, size, headerContent);
+        return Objects.hashCode(composedMessageId, internalDate, saveDate, bodyStartOctet, size, headerContent);
     }
 
     @Override
@@ -403,6 +430,7 @@ public class CassandraMessageMetadata {
             .toStringHelper(this)
             .add("composedMessageId", composedMessageId)
             .add("internalDate", internalDate)
+            .add("saveDate", saveDate)
             .add("bodyStartOctet", bodyStartOctet)
             .add("size", size)
             .add("headerContent", headerContent)
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraMessageModule.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraMessageModule.java
index 96304e5af3..a667a31f66 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraMessageModule.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraMessageModule.java
@@ -67,6 +67,7 @@ public interface CassandraMessageModule {
             .withColumn(Flag.USER, BOOLEAN)
             .withColumn(Flag.USER_FLAGS, setOf(TEXT))
             .withColumn(CassandraMessageV3Table.INTERNAL_DATE, TIMESTAMP)
+            .withColumn(CassandraMessageIdTable.SAVE_DATE, TIMESTAMP)
             .withColumn(CassandraMessageV3Table.BODY_START_OCTET, INT)
             .withColumn(CassandraMessageV3Table.FULL_CONTENT_OCTETS, BIGINT)
             .withColumn(CassandraMessageV3Table.HEADER_CONTENT, TEXT))
@@ -91,6 +92,7 @@ public interface CassandraMessageModule {
             .withColumn(Flag.USER, BOOLEAN)
             .withColumn(Flag.USER_FLAGS, setOf(TEXT))
             .withColumn(CassandraMessageV3Table.INTERNAL_DATE, TIMESTAMP)
+            .withColumn(CassandraMessageIdTable.SAVE_DATE, TIMESTAMP)
             .withColumn(CassandraMessageV3Table.BODY_START_OCTET, INT)
             .withColumn(CassandraMessageV3Table.FULL_CONTENT_OCTETS, BIGINT)
             .withColumn(CassandraMessageV3Table.HEADER_CONTENT, TEXT))
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraMessageIdTable.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraMessageIdTable.java
index 6ab2d26bd2..15ba47fcba 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraMessageIdTable.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraMessageIdTable.java
@@ -27,4 +27,6 @@ public interface CassandraMessageIdTable {
     CqlIdentifier MOD_SEQ = CqlIdentifier.fromCql("modSeq");
 
     CqlIdentifier THREAD_ID = CqlIdentifier.fromCql("threadId");
+
+    CqlIdentifier SAVE_DATE = CqlIdentifier.fromCql("save_date");
 }
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraMailboxManagerProvider.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraMailboxManagerProvider.java
index 376cec8f73..f3b669ec97 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraMailboxManagerProvider.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraMailboxManagerProvider.java
@@ -19,6 +19,9 @@
 
 package org.apache.james.mailbox.cassandra;
 
+import java.time.Clock;
+import java.time.Instant;
+
 import org.apache.james.backends.cassandra.CassandraCluster;
 import org.apache.james.backends.cassandra.init.configuration.CassandraConfiguration;
 import org.apache.james.events.EventBusTestFixture;
@@ -56,6 +59,7 @@ import org.apache.james.mailbox.store.quota.StoreQuotaManager;
 import org.apache.james.mailbox.store.search.MessageSearchIndex;
 import org.apache.james.mailbox.store.search.SimpleMessageSearchIndex;
 import org.apache.james.metrics.tests.RecordingMetricFactory;
+import org.apache.james.utils.UpdatableTickingClock;
 
 import com.datastax.oss.driver.api.core.CqlSession;
 
@@ -81,6 +85,7 @@ public class CassandraMailboxManagerProvider {
                                                                 MailboxManagerConfiguration mailboxManagerConfiguration) {
         CassandraMessageId.Factory messageIdFactory = new CassandraMessageId.Factory();
         ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm = new NaiveThreadIdGuessingAlgorithm();
+        UpdatableTickingClock clock = new UpdatableTickingClock(Instant.now());
 
         CassandraMailboxSessionMapperFactory mapperFactory = TestCassandraMailboxSessionMapperFactory.forTests(
             cassandra,
@@ -88,7 +93,7 @@ public class CassandraMailboxManagerProvider {
             cassandraConfiguration);
 
         return provideMailboxManager(cassandra.getConf(), preDeletionHooks, mapperFactory,
-            mailboxManagerConfiguration, messageIdFactory, threadIdGuessingAlgorithm);
+            mailboxManagerConfiguration, messageIdFactory, threadIdGuessingAlgorithm, clock);
     }
 
     private static CassandraMailboxManager provideMailboxManager(CqlSession session,
@@ -96,7 +101,8 @@ public class CassandraMailboxManagerProvider {
                                                                  CassandraMailboxSessionMapperFactory mapperFactory,
                                                                  MailboxManagerConfiguration mailboxManagerConfiguration,
                                                                  MessageId.Factory messageIdFactory,
-                                                                 ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm) {
+                                                                 ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm,
+                                                                 Clock clock) {
         MailboxACLResolver aclResolver = new UnionMailboxACLResolver();
         MessageParser messageParser = new MessageParser();
         InVMEventBus eventBus = new InVMEventBus(new InVmEventDelivery(new RecordingMetricFactory()), EventBusTestFixture.RETRY_BACKOFF_CONFIGURATION, new MemoryEventDeadLetters());
@@ -122,7 +128,7 @@ public class CassandraMailboxManagerProvider {
 
         CassandraMailboxManager manager = new CassandraMailboxManager(mapperFactory, sessionProvider, new NoMailboxPathLocker(),
             messageParser, messageIdFactory, eventBus, annotationManager, storeRightManager,
-            quotaComponents, index, mailboxManagerConfiguration, preDeletionHooks, threadIdGuessingAlgorithm);
+            quotaComponents, index, mailboxManagerConfiguration, preDeletionHooks, threadIdGuessingAlgorithm, clock);
 
         eventBus.register(quotaUpdater);
         eventBus.register(new MailboxAnnotationListener(mapperFactory, sessionProvider));
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraTestSystemFixture.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraTestSystemFixture.java
index a31836e8b2..774959e2ca 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraTestSystemFixture.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraTestSystemFixture.java
@@ -21,6 +21,8 @@ package org.apache.james.mailbox.cassandra;
 
 import static org.mockito.Mockito.mock;
 
+import java.time.Instant;
+
 import org.apache.james.backends.cassandra.CassandraCluster;
 import org.apache.james.events.EventBus;
 import org.apache.james.events.EventBusTestFixture;
@@ -56,6 +58,7 @@ import org.apache.james.mailbox.store.quota.QuotaComponents;
 import org.apache.james.mailbox.store.search.MessageSearchIndex;
 import org.apache.james.mailbox.store.search.SimpleMessageSearchIndex;
 import org.apache.james.metrics.tests.RecordingMetricFactory;
+import org.apache.james.utils.UpdatableTickingClock;
 
 public class CassandraTestSystemFixture {
 
@@ -78,7 +81,7 @@ public class CassandraTestSystemFixture {
         CassandraMailboxManager cassandraMailboxManager = new CassandraMailboxManager(mapperFactory, sessionProvider,
             new NoMailboxPathLocker(), new MessageParser(), new CassandraMessageId.Factory(),
             eventBus, annotationManager, storeRightManager, quotaComponents, index, MailboxManagerConfiguration.DEFAULT, PreDeletionHooks.NO_PRE_DELETION_HOOK,
-            new NaiveThreadIdGuessingAlgorithm());
+            new NaiveThreadIdGuessingAlgorithm(), new UpdatableTickingClock(Instant.now()));
 
         eventBus.register(new MailboxAnnotationListener(mapperFactory, sessionProvider));
         eventBus.register(mapperFactory.deleteMessageListener());
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxManagerAttachmentTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxManagerAttachmentTest.java
index 273f354e8e..a0e3b7e50c 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxManagerAttachmentTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxManagerAttachmentTest.java
@@ -23,6 +23,7 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import java.io.InputStream;
+import java.time.Instant;
 
 import org.apache.james.backends.cassandra.CassandraClusterExtension;
 import org.apache.james.events.EventBusTestFixture;
@@ -55,6 +56,7 @@ import org.apache.james.mailbox.store.quota.QuotaComponents;
 import org.apache.james.mailbox.store.search.MessageSearchIndex;
 import org.apache.james.mailbox.store.search.SimpleMessageSearchIndex;
 import org.apache.james.metrics.tests.RecordingMetricFactory;
+import org.apache.james.utils.UpdatableTickingClock;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
@@ -92,13 +94,13 @@ class CassandraMailboxManagerAttachmentTest extends AbstractMailboxManagerAttach
 
         mailboxManager = new CassandraMailboxManager(mailboxSessionMapperFactory, sessionProvider, new NoMailboxPathLocker(), new MessageParser(),
             messageIdFactory, eventBus, annotationManager, storeRightManager, quotaComponents,
-            index, MailboxManagerConfiguration.DEFAULT, PreDeletionHooks.NO_PRE_DELETION_HOOK, threadIdGuessingAlgorithm);
+            index, MailboxManagerConfiguration.DEFAULT, PreDeletionHooks.NO_PRE_DELETION_HOOK, threadIdGuessingAlgorithm, new UpdatableTickingClock(Instant.now()));
         MessageParser failingMessageParser = mock(MessageParser.class);
         when(failingMessageParser.retrieveAttachments(any(InputStream.class)))
             .thenThrow(new RuntimeException("Message parser set to fail"));
         parseFailingMailboxManager = new CassandraMailboxManager(mailboxSessionMapperFactory, sessionProvider,
             new NoMailboxPathLocker(), failingMessageParser, messageIdFactory,
-            eventBus, annotationManager, storeRightManager, quotaComponents, index, MailboxManagerConfiguration.DEFAULT, PreDeletionHooks.NO_PRE_DELETION_HOOK, threadIdGuessingAlgorithm);
+            eventBus, annotationManager, storeRightManager, quotaComponents, index, MailboxManagerConfiguration.DEFAULT, PreDeletionHooks.NO_PRE_DELETION_HOOK, threadIdGuessingAlgorithm, new UpdatableTickingClock(Instant.now()));
     }
 
     @Override
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOV3Test.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOV3Test.java
index 9b6f6fd2ce..23f64fa854 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOV3Test.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOV3Test.java
@@ -18,12 +18,14 @@
  ****************************************************************/
 package org.apache.james.mailbox.cassandra.mail;
 
+import static org.apache.james.mailbox.store.mail.model.MailboxMessage.EMPTY_SAVE_DATE;
 import static org.assertj.core.api.Assertions.assertThat;
 
 import java.nio.charset.StandardCharsets;
 import java.util.Collection;
 import java.util.Date;
 import java.util.List;
+import java.util.Optional;
 
 import javax.mail.Flags;
 
@@ -112,7 +114,7 @@ class CassandraMessageDAOV3Test {
 
     @Test
     void saveShouldSaveNullValueForTextualLineCountAsZero() throws Exception {
-        message = createMessage(messageId, threadId, CONTENT, BODY_START, new PropertyBuilder(), NO_ATTACHMENT);
+        message = createMessage(messageId, threadId, CONTENT, BODY_START, new PropertyBuilder(), NO_ATTACHMENT, EMPTY_SAVE_DATE);
 
         testee.save(message).block();
 
@@ -128,7 +130,7 @@ class CassandraMessageDAOV3Test {
         long textualLineCount = 10L;
         PropertyBuilder propertyBuilder = new PropertyBuilder();
         propertyBuilder.setTextualLineCount(textualLineCount);
-        message = createMessage(messageId, threadId, CONTENT, BODY_START, propertyBuilder, NO_ATTACHMENT);
+        message = createMessage(messageId, threadId, CONTENT, BODY_START, propertyBuilder, NO_ATTACHMENT, EMPTY_SAVE_DATE);
 
         testee.save(message).block();
 
@@ -140,7 +142,7 @@ class CassandraMessageDAOV3Test {
 
     @Test
     void saveShouldStoreMessageWithFullContent() throws Exception {
-        message = createMessage(messageId, threadId, CONTENT, BODY_START, new PropertyBuilder(), NO_ATTACHMENT);
+        message = createMessage(messageId, threadId, CONTENT, BODY_START, new PropertyBuilder(), NO_ATTACHMENT, EMPTY_SAVE_DATE);
 
         testee.save(message).block();
 
@@ -153,7 +155,7 @@ class CassandraMessageDAOV3Test {
 
     @Test
     void saveShouldStoreMessageWithHeaderContent() throws Exception {
-        message = createMessage(messageId, threadId,  CONTENT, BODY_START, new PropertyBuilder(), NO_ATTACHMENT);
+        message = createMessage(messageId, threadId,  CONTENT, BODY_START, new PropertyBuilder(), NO_ATTACHMENT, EMPTY_SAVE_DATE);
 
         testee.save(message).block();
 
@@ -172,8 +174,8 @@ class CassandraMessageDAOV3Test {
 
     @Test
     void blobReferencesShouldReturnAllBlobs() throws Exception {
-        message = createMessage(messageId, threadId,  CONTENT, BODY_START, new PropertyBuilder(), NO_ATTACHMENT);
-        MailboxMessage message2 = createMessage(messageId2, threadId,  CONTENT_2, BODY_START, new PropertyBuilder(), NO_ATTACHMENT);
+        message = createMessage(messageId, threadId,  CONTENT, BODY_START, new PropertyBuilder(), NO_ATTACHMENT, EMPTY_SAVE_DATE);
+        MailboxMessage message2 = createMessage(messageId2, threadId,  CONTENT_2, BODY_START, new PropertyBuilder(), NO_ATTACHMENT, EMPTY_SAVE_DATE);
         testee.save(message).block();
         testee.save(message2).block();
 
@@ -181,13 +183,15 @@ class CassandraMessageDAOV3Test {
             .hasSize(4);
     }
 
-    private SimpleMailboxMessage createMessage(MessageId messageId, ThreadId threadId, String content, int bodyStart, PropertyBuilder propertyBuilder, Collection<MessageAttachmentMetadata> attachments) {
+    private SimpleMailboxMessage createMessage(MessageId messageId, ThreadId threadId, String content, int bodyStart, PropertyBuilder propertyBuilder,
+                                               Collection<MessageAttachmentMetadata> attachments, Optional<Date> saveDate) {
         return SimpleMailboxMessage.builder()
             .messageId(messageId)
             .threadId(threadId)
             .mailboxId(MAILBOX_ID)
             .uid(messageUid)
             .internalDate(new Date())
+            .saveDate(saveDate)
             .bodyStartOctet(bodyStart)
             .size(content.length())
             .content(new ByteContent(content.getBytes(StandardCharsets.UTF_8)))
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdDAOTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdDAOTest.java
index 6f76a71fef..a3f65698d1 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdDAOTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdDAOTest.java
@@ -207,6 +207,58 @@ class CassandraMessageIdDAOTest {
         assertThat(message.get().getComposedMessageId()).isEqualTo(composedMessageIdWithMetaData);
     }
 
+    @Test
+    void shouldHandleNullSaveDateWell() {
+        CassandraMessageId messageId = messageIdFactory.generate();
+        CassandraId mailboxId = CassandraId.timeBased();
+        MessageUid messageUid = MessageUid.of(1);
+
+        ComposedMessageIdWithMetaData composedMessageIdWithMetaData = ComposedMessageIdWithMetaData.builder()
+            .composedMessageId(new ComposedMessageId(mailboxId, messageId, messageUid))
+            .flags(new Flags())
+            .modSeq(ModSeq.of(1))
+            .threadId(ThreadId.fromBaseMessageId(messageId))
+            .build();
+        testee.insert(CassandraMessageMetadata.builder()
+                .ids(composedMessageIdWithMetaData)
+                .internalDate(new Date())
+                .bodyStartOctet(18L)
+                .size(36L)
+                .headerContent(Optional.of(HEADER_BLOB_ID_1))
+                .build())
+            .block();
+
+        Optional<CassandraMessageMetadata> message = testee.retrieve(mailboxId, messageUid).block();
+        assertThat(message.get().getSaveDate()).isEmpty();
+    }
+
+    @Test
+    void shouldHandleSaveDateWell() {
+        CassandraMessageId messageId = messageIdFactory.generate();
+        CassandraId mailboxId = CassandraId.timeBased();
+        MessageUid messageUid = MessageUid.of(1);
+        Optional<Date> saveDate = Optional.of(new Date());
+
+        ComposedMessageIdWithMetaData composedMessageIdWithMetaData = ComposedMessageIdWithMetaData.builder()
+            .composedMessageId(new ComposedMessageId(mailboxId, messageId, messageUid))
+            .flags(new Flags())
+            .modSeq(ModSeq.of(1))
+            .threadId(ThreadId.fromBaseMessageId(messageId))
+            .build();
+        testee.insert(CassandraMessageMetadata.builder()
+                .ids(composedMessageIdWithMetaData)
+                .internalDate(new Date())
+                .saveDate(saveDate)
+                .bodyStartOctet(18L)
+                .size(36L)
+                .headerContent(Optional.of(HEADER_BLOB_ID_1))
+                .build())
+            .block();
+
+        Optional<CassandraMessageMetadata> message = testee.retrieve(mailboxId, messageUid).block();
+        assertThat(message.get().getSaveDate()).isEqualTo(saveDate);
+    }
+
     @Test
     void updateShouldUpdateModSeq() {
         CassandraMessageId messageId = messageIdFactory.generate();
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdToImapUidDAOTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdToImapUidDAOTest.java
index 0891106753..857ac74ae1 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdToImapUidDAOTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdToImapUidDAOTest.java
@@ -181,6 +181,57 @@ class CassandraMessageIdToImapUidDAOTest {
             .containsOnly(expectedComposedMessageId);
     }
 
+    @Test
+    void shouldHandleNullSaveDateWell() {
+        CassandraMessageId messageId = CassandraMessageId.Factory.of(Uuids.timeBased());
+        CassandraId mailboxId = CassandraId.timeBased();
+        MessageUid messageUid = MessageUid.of(1);
+
+        testee.insert(CassandraMessageMetadata.builder()
+                .ids(ComposedMessageIdWithMetaData.builder()
+                    .composedMessageId(new ComposedMessageId(mailboxId, messageId, messageUid))
+                    .flags(new Flags())
+                    .modSeq(ModSeq.of(1))
+                    .threadId(ThreadId.fromBaseMessageId(messageId))
+                    .build())
+                .internalDate(new Date())
+                .saveDate(Optional.empty())
+                .bodyStartOctet(18L)
+                .size(36L)
+                .headerContent(Optional.of(HEADER_BLOB_ID_1))
+                .build())
+            .block();
+
+        List<CassandraMessageMetadata> messages = testee.retrieve(messageId, Optional.empty()).collectList().block();
+        assertThat(messages.get(0).getSaveDate()).isEmpty();
+    }
+
+    @Test
+    void shouldHandleSaveDateWell() {
+        CassandraMessageId messageId = CassandraMessageId.Factory.of(Uuids.timeBased());
+        CassandraId mailboxId = CassandraId.timeBased();
+        MessageUid messageUid = MessageUid.of(1);
+        Optional<Date> saveDate = Optional.of(new Date());
+
+        testee.insert(CassandraMessageMetadata.builder()
+                .ids(ComposedMessageIdWithMetaData.builder()
+                    .composedMessageId(new ComposedMessageId(mailboxId, messageId, messageUid))
+                    .flags(new Flags())
+                    .modSeq(ModSeq.of(1))
+                    .threadId(ThreadId.fromBaseMessageId(messageId))
+                    .build())
+                .internalDate(new Date())
+                .saveDate(saveDate)
+                .bodyStartOctet(18L)
+                .size(36L)
+                .headerContent(Optional.of(HEADER_BLOB_ID_1))
+                .build())
+            .block();
+
+        List<CassandraMessageMetadata> messages = testee.retrieve(messageId, Optional.empty()).collectList().block();
+        assertThat(messages.get(0).getSaveDate()).isEqualTo(saveDate);
+    }
+
     @Test
     void updateShouldReturnTrueWhenOldModSeqMatches() {
         CassandraMessageId messageId = CassandraMessageId.Factory.of(Uuids.timeBased());
diff --git a/mailbox/jpa/pom.xml b/mailbox/jpa/pom.xml
index ee8e93a90f..8fbaf2ad5d 100644
--- a/mailbox/jpa/pom.xml
+++ b/mailbox/jpa/pom.xml
@@ -89,6 +89,11 @@
             <artifactId>james-server-data-jpa</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-testing</artifactId>
+            <scope>runtime</scope>
+        </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
             <artifactId>james-server-util</artifactId>
diff --git a/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/AbstractJPAMailboxMessage.java b/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/AbstractJPAMailboxMessage.java
index cae6c2554c..879171416d 100644
--- a/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/AbstractJPAMailboxMessage.java
+++ b/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/AbstractJPAMailboxMessage.java
@@ -25,6 +25,7 @@ import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
+import java.util.Optional;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import javax.mail.Flags;
@@ -500,6 +501,11 @@ public abstract class AbstractJPAMailboxMessage implements MailboxMessage {
         return new ThreadId(getMessageId());
     }
 
+    @Override
+    public Optional<Date> getSaveDate() {
+        return Optional.empty();
+    }
+
     public String toString() {
         return "message("
                 + "mailboxId = " + this.getMailboxId() + TOSTRING_SEPARATOR
diff --git a/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMailboxManager.java b/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMailboxManager.java
index 606d8a71e2..5346770f52 100644
--- a/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMailboxManager.java
+++ b/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMailboxManager.java
@@ -19,6 +19,7 @@
 
 package org.apache.james.mailbox.jpa.openjpa;
 
+import java.time.Clock;
 import java.util.EnumSet;
 
 import javax.inject.Inject;
@@ -61,11 +62,12 @@ public class OpenJPAMailboxManager extends StoreMailboxManager {
                                  StoreRightManager storeRightManager,
                                  QuotaComponents quotaComponents,
                                  MessageSearchIndex index,
-                                 ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm) {
+                                 ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm,
+                                 Clock clock) {
         super(mapperFactory, sessionProvider, new JVMMailboxPathLocker(),
             messageParser, messageIdFactory, annotationManager,
             eventBus, storeRightManager, quotaComponents,
-            index, MailboxManagerConfiguration.DEFAULT, PreDeletionHooks.NO_PRE_DELETION_HOOK, threadIdGuessingAlgorithm);
+            index, MailboxManagerConfiguration.DEFAULT, PreDeletionHooks.NO_PRE_DELETION_HOOK, threadIdGuessingAlgorithm, clock);
     }
 
     @Override
@@ -80,7 +82,8 @@ public class OpenJPAMailboxManager extends StoreMailboxManager {
             getMessageIdFactory(),
             configuration.getBatchSizes(),
             getStoreRightManager(),
-            getThreadIdGuessingAlgorithm());
+            getThreadIdGuessingAlgorithm(),
+            getClock());
     }
 
     @Override
diff --git a/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMessageFactory.java b/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMessageFactory.java
index b296258ced..79b08c492e 100644
--- a/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMessageFactory.java
+++ b/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMessageFactory.java
@@ -52,7 +52,7 @@ public class OpenJPAMessageFactory implements MessageFactory<AbstractJPAMailboxM
     }
 
     @Override
-    public AbstractJPAMailboxMessage createMessage(MessageId messageId, ThreadId threadId, Mailbox mailbox, Date internalDate, int size, int bodyStartOctet, Content content, Flags flags, PropertyBuilder propertyBuilder, List<MessageAttachmentMetadata> attachments) throws MailboxException {
+    public AbstractJPAMailboxMessage createMessage(MessageId messageId, ThreadId threadId, Mailbox mailbox, Date internalDate, Date saveDate, int size, int bodyStartOctet, Content content, Flags flags, PropertyBuilder propertyBuilder, List<MessageAttachmentMetadata> attachments) throws MailboxException {
         switch (feature) {
             case Streaming:
                 return new JPAStreamingMailboxMessage(JPAMailbox.from(mailbox), internalDate, size, flags, content,
diff --git a/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMessageManager.java b/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMessageManager.java
index 21dfb88926..567e8d5ac6 100644
--- a/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMessageManager.java
+++ b/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMessageManager.java
@@ -19,6 +19,8 @@
 
 package org.apache.james.mailbox.jpa.openjpa;
 
+import java.time.Clock;
+
 import javax.mail.Flags;
 
 import org.apache.james.events.EventBus;
@@ -48,10 +50,10 @@ public class OpenJPAMessageManager extends StoreMessageManager {
                                  MailboxPathLocker locker, Mailbox mailbox,
                                  QuotaManager quotaManager, QuotaRootResolver quotaRootResolver,
                                  MessageId.Factory messageIdFactory, BatchSizes batchSizes,
-                                 StoreRightManager storeRightManager, ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm) {
+                                 StoreRightManager storeRightManager, ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm, Clock clock) {
         super(StoreMailboxManager.DEFAULT_NO_MESSAGE_CAPABILITIES, mapperFactory, index, eventBus, locker, mailbox,
             quotaManager, quotaRootResolver, batchSizes, storeRightManager, PreDeletionHooks.NO_PRE_DELETION_HOOK,
-            new MessageStorer.WithoutAttachment(mapperFactory, messageIdFactory, new OpenJPAMessageFactory(OpenJPAMessageFactory.AdvancedFeature.None), threadIdGuessingAlgorithm));
+            new MessageStorer.WithoutAttachment(mapperFactory, messageIdFactory, new OpenJPAMessageFactory(OpenJPAMessageFactory.AdvancedFeature.None), threadIdGuessingAlgorithm, clock));
     }
 
     /**
diff --git a/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JPAMailboxManagerTest.java b/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JPAMailboxManagerTest.java
index bab657d713..b31ce31433 100644
--- a/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JPAMailboxManagerTest.java
+++ b/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JPAMailboxManagerTest.java
@@ -67,6 +67,12 @@ class JPAMailboxManagerTest extends MailboxManagerTest<OpenJPAMailboxManager> {
 
     }
 
+    @Nested
+    @Disabled("JPA does not support saveDate.")
+    class SaveDateTests {
+
+    }
+
     @Override
     protected EventBus retrieveEventBus(OpenJPAMailboxManager mailboxManager) {
         return mailboxManager.getEventBus();
diff --git a/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JpaMailboxManagerProvider.java b/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JpaMailboxManagerProvider.java
index 1aafb340d2..b951986ab2 100644
--- a/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JpaMailboxManagerProvider.java
+++ b/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JpaMailboxManagerProvider.java
@@ -19,6 +19,8 @@
 
 package org.apache.james.mailbox.jpa;
 
+import java.time.Instant;
+
 import javax.persistence.EntityManagerFactory;
 
 import org.apache.james.backends.jpa.JpaTestCluster;
@@ -44,6 +46,7 @@ import org.apache.james.mailbox.store.quota.QuotaComponents;
 import org.apache.james.mailbox.store.search.MessageSearchIndex;
 import org.apache.james.mailbox.store.search.SimpleMessageSearchIndex;
 import org.apache.james.metrics.tests.RecordingMetricFactory;
+import org.apache.james.utils.UpdatableTickingClock;
 
 public class JpaMailboxManagerProvider {
 
@@ -71,6 +74,6 @@ public class JpaMailboxManagerProvider {
         return new OpenJPAMailboxManager(mf, sessionProvider,
             messageParser, new DefaultMessageId.Factory(),
             eventBus, annotationManager,
-            storeRightManager, quotaComponents, index, new NaiveThreadIdGuessingAlgorithm());
+            storeRightManager, quotaComponents, index, new NaiveThreadIdGuessingAlgorithm(), new UpdatableTickingClock(Instant.now()));
     }
 }
diff --git a/mailbox/memory/pom.xml b/mailbox/memory/pom.xml
index ab6466cb74..13443d537f 100644
--- a/mailbox/memory/pom.xml
+++ b/mailbox/memory/pom.xml
@@ -74,6 +74,11 @@
             <artifactId>james-server-data-memory</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-testing</artifactId>
+            <scope>runtime</scope>
+        </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
             <artifactId>james-server-util</artifactId>
diff --git a/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMailboxManager.java b/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMailboxManager.java
index bf27e7767d..f7380c749f 100644
--- a/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMailboxManager.java
+++ b/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMailboxManager.java
@@ -19,6 +19,7 @@
 
 package org.apache.james.mailbox.inmemory;
 
+import java.time.Clock;
 import java.util.EnumSet;
 
 import javax.inject.Inject;
@@ -60,10 +61,11 @@ public class InMemoryMailboxManager extends StoreMailboxManager {
                                   QuotaComponents quotaComponents,
                                   MessageSearchIndex searchIndex,
                                   PreDeletionHooks preDeletionHooks,
-                                  ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm) {
+                                  ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm,
+                                  Clock clock) {
         super(mailboxSessionMapperFactory, sessionProvider, locker, messageParser, messageIdFactory,
             annotationManager, eventBus, storeRightManager, quotaComponents, searchIndex, MailboxManagerConfiguration.DEFAULT,
-            preDeletionHooks, threadIdGuessingAlgorithm);
+            preDeletionHooks, threadIdGuessingAlgorithm, clock);
     }
 
     @Override
@@ -90,6 +92,7 @@ public class InMemoryMailboxManager extends StoreMailboxManager {
             configuration.getBatchSizes(),
             getStoreRightManager(),
             getPreDeletionHooks(),
-            getThreadIdGuessingAlgorithm());
+            getThreadIdGuessingAlgorithm(),
+            getClock());
     }
 }
diff --git a/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMessageManager.java b/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMessageManager.java
index 9f95cf7f71..46d61cec72 100644
--- a/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMessageManager.java
+++ b/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMessageManager.java
@@ -1,5 +1,7 @@
 package org.apache.james.mailbox.inmemory;
 
+import java.time.Clock;
+
 import javax.mail.Flags;
 
 import org.apache.james.events.EventBus;
@@ -33,12 +35,13 @@ public class InMemoryMessageManager extends StoreMessageManager {
                                   BatchSizes batchSizes,
                                   StoreRightManager storeRightManager,
                                   PreDeletionHooks preDeletionHooks,
-                                  ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm) {
+                                  ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm,
+                                  Clock clock) {
 
         super(InMemoryMailboxManager.MESSAGE_CAPABILITIES, mapperFactory, index, eventBus, locker, mailbox, quotaManager, quotaRootResolver,
             batchSizes, storeRightManager, preDeletionHooks,
             new MessageStorer.WithAttachment(mapperFactory, messageIdFactory, new MessageFactory.StoreMessageFactory(), (InMemoryMailboxSessionMapperFactory) mapperFactory, messageParser,
-                threadIdGuessingAlgorithm));
+                threadIdGuessingAlgorithm, clock));
     }
 
     @Override
diff --git a/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/manager/InMemoryIntegrationResources.java b/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/manager/InMemoryIntegrationResources.java
index 724938e235..d6a5c06759 100644
--- a/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/manager/InMemoryIntegrationResources.java
+++ b/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/manager/InMemoryIntegrationResources.java
@@ -19,6 +19,7 @@
 
 package org.apache.james.mailbox.inmemory.manager;
 
+import java.time.Instant;
 import java.util.Collection;
 import java.util.Optional;
 import java.util.function.Function;
@@ -72,6 +73,7 @@ import org.apache.james.mailbox.store.search.ListeningMessageSearchIndex;
 import org.apache.james.mailbox.store.search.MessageSearchIndex;
 import org.apache.james.mailbox.store.search.SimpleMessageSearchIndex;
 import org.apache.james.metrics.tests.RecordingMetricFactory;
+import org.apache.james.utils.UpdatableTickingClock;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
@@ -312,6 +314,7 @@ public class InMemoryIntegrationResources implements IntegrationResources<StoreM
 
             InMemoryMessageId.Factory messageIdFactory = new InMemoryMessageId.Factory();
             ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm = new NaiveThreadIdGuessingAlgorithm();
+            UpdatableTickingClock clock = new UpdatableTickingClock(Instant.now());
 
             MailboxManagerPreInstanciationStage preInstanciationStage = new MailboxManagerPreInstanciationStage(mailboxSessionMapperFactory, sessionProvider);
             PreDeletionHooks hooks = createHooks(preInstanciationStage);
@@ -334,7 +337,8 @@ public class InMemoryIntegrationResources implements IntegrationResources<StoreM
                 quotaComponents,
                 index,
                 hooks,
-                threadIdGuessingAlgorithm);
+                threadIdGuessingAlgorithm,
+                clock);
 
             eventBus.register(listeningCurrentQuotaUpdater);
             eventBus.register(new MailboxAnnotationListener(mailboxSessionMapperFactory, sessionProvider));
diff --git a/mailbox/store/pom.xml b/mailbox/store/pom.xml
index 43b2320f26..199632266e 100644
--- a/mailbox/store/pom.xml
+++ b/mailbox/store/pom.xml
@@ -55,6 +55,11 @@
             <artifactId>james-mdn</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-testing</artifactId>
+            <scope>runtime</scope>
+        </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
             <artifactId>james-server-util</artifactId>
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageFactory.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageFactory.java
index dffe153301..0c6181e0ff 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageFactory.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageFactory.java
@@ -21,6 +21,7 @@ package org.apache.james.mailbox.store;
 
 import java.util.Date;
 import java.util.List;
+import java.util.Optional;
 
 import javax.mail.Flags;
 
@@ -35,17 +36,17 @@ import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder;
 import org.apache.james.mailbox.store.mail.model.impl.SimpleMailboxMessage;
 
 public interface MessageFactory<T extends MailboxMessage> {
-    T createMessage(MessageId messageId, ThreadId threadId, Mailbox mailbox, Date internalDate, int size, int bodyStartOctet,
+    T createMessage(MessageId messageId, ThreadId threadId, Mailbox mailbox, Date internalDate, Date saveDate, int size, int bodyStartOctet,
                     Content content, Flags flags, PropertyBuilder propertyBuilder,
                     List<MessageAttachmentMetadata> attachments) throws MailboxException;
 
     class StoreMessageFactory implements MessageFactory<SimpleMailboxMessage> {
         @Override
-        public SimpleMailboxMessage createMessage(MessageId messageId, ThreadId threadId, Mailbox mailbox, Date internalDate, int size,
+        public SimpleMailboxMessage createMessage(MessageId messageId, ThreadId threadId, Mailbox mailbox, Date internalDate, Date saveDate, int size,
                                                   int bodyStartOctet, Content content, Flags flags,
                                                   PropertyBuilder propertyBuilder, List<MessageAttachmentMetadata> attachments) {
             return new SimpleMailboxMessage(messageId, threadId, internalDate, size, bodyStartOctet, content, flags, propertyBuilder.build(),
-                mailbox.getMailboxId(), attachments);
+                mailbox.getMailboxId(), attachments, Optional.of(saveDate));
         }
     }
 }
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageStorer.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageStorer.java
index 797648020c..b612de7b45 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageStorer.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageStorer.java
@@ -20,6 +20,7 @@
 package org.apache.james.mailbox.store;
 
 import java.io.InputStream;
+import java.time.Clock;
 import java.util.Date;
 import java.util.List;
 import java.util.Optional;
@@ -60,14 +61,14 @@ public interface MessageStorer {
     /**
      * If supported by the underlying implementation, this method will parse the messageContent to retrieve associated
      * attachments and will store them.
-     *
+     * <p>
      * Otherwize an empty optional will be returned on the right side of the pair.
      */
     Mono<Pair<MessageMetaData, Optional<List<MessageAttachmentMetadata>>>> appendMessageToStore(Mailbox mailbox, Date internalDate, int size, int bodyStartOctet, Content content, Flags flags, PropertyBuilder propertyBuilder, Optional<Message> maybeMessage, MailboxSession session, HeaderImpl headers) throws MailboxException;
 
     /**
      * MessageStorer parsing, storing and returning AttachmentMetadata
-     *
+     * <p>
      * To be used with implementation that supports attachment content storage
      */
     class WithAttachment implements MessageStorer {
@@ -79,16 +80,18 @@ public interface MessageStorer {
         private final AttachmentMapperFactory attachmentMapperFactory;
         private final MessageParser messageParser;
         private final ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm;
+        private final Clock clock;
 
         public WithAttachment(MailboxSessionMapperFactory mapperFactory, MessageId.Factory messageIdFactory,
                               MessageFactory messageFactory, AttachmentMapperFactory attachmentMapperFactory,
-                              MessageParser messageParser, ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm) {
+                              MessageParser messageParser, ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm, Clock clock) {
             this.mapperFactory = mapperFactory;
             this.messageIdFactory = messageIdFactory;
             this.messageFactory = messageFactory;
             this.attachmentMapperFactory = attachmentMapperFactory;
             this.messageParser = messageParser;
             this.threadIdGuessingAlgorithm = threadIdGuessingAlgorithm;
+            this.clock = clock;
         }
 
         @Override
@@ -105,13 +108,14 @@ public interface MessageStorer {
                     storeAttachments(messageId, content, maybeMessage, session)
                         .zipWith(threadIdGuessingAlgorithm.guessThreadIdReactive(messageId, mimeMessageId, inReplyTo, references, subject, session))
                         .flatMap(Throwing.function((Tuple2<List<MessageAttachmentMetadata>, ThreadId> pair) -> {
-                                List<MessageAttachmentMetadata> attachments = pair.getT1();
-                                ThreadId threadId = pair.getT2();
-
-                                MailboxMessage message = messageFactory.createMessage(messageId, threadId, mailbox, internalDate, size, bodyStartOctet, content, flags, propertyBuilder, attachments);
-                                return Mono.from(messageMapper.addReactive(mailbox, message))
-                                    .map(metadata -> Pair.of(metadata, Optional.of(attachments)));
-                            }).sneakyThrow()));
+                            List<MessageAttachmentMetadata> attachments = pair.getT1();
+                            ThreadId threadId = pair.getT2();
+                            Date saveDate = Date.from(clock.instant());
+
+                            MailboxMessage message = messageFactory.createMessage(messageId, threadId, mailbox, internalDate, saveDate, size, bodyStartOctet, content, flags, propertyBuilder, attachments);
+                            return Mono.from(messageMapper.addReactive(mailbox, message))
+                                .map(metadata -> Pair.of(metadata, Optional.of(attachments)));
+                        }).sneakyThrow()));
         }
 
         private Mono<List<MessageAttachmentMetadata>> storeAttachments(MessageId messageId, Content messageContent, Optional<Message> maybeMessage, MailboxSession session) {
@@ -141,7 +145,7 @@ public interface MessageStorer {
 
     /**
      * MessageStorer that does not parse, store, nor return Attachment metadata
-     *
+     * <p>
      * To be used when the underlying implementation does not support attachment storage.
      */
     class WithoutAttachment implements MessageStorer {
@@ -149,12 +153,14 @@ public interface MessageStorer {
         private final MessageId.Factory messageIdFactory;
         private final MessageFactory messageFactory;
         private final ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm;
+        private final Clock clock;
 
-        public WithoutAttachment(MailboxSessionMapperFactory mapperFactory, MessageId.Factory messageIdFactory, MessageFactory messageFactory, ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm) {
+        public WithoutAttachment(MailboxSessionMapperFactory mapperFactory, MessageId.Factory messageIdFactory, MessageFactory messageFactory, ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm, Clock clock) {
             this.mapperFactory = mapperFactory;
             this.messageIdFactory = messageIdFactory;
             this.messageFactory = messageFactory;
             this.threadIdGuessingAlgorithm = threadIdGuessingAlgorithm;
+            this.clock = clock;
         }
 
         @Override
@@ -169,7 +175,9 @@ public interface MessageStorer {
             return mapperFactory.getMessageMapper(session)
                 .executeReactive(threadIdGuessingAlgorithm.guessThreadIdReactive(messageId, mimeMessageId, inReplyTo, references, subject, session)
                     .flatMap(Throwing.function((ThreadId threadId) -> {
-                        MailboxMessage message = messageFactory.createMessage(messageId, threadId, mailbox, internalDate, size, bodyStartOctet, content, flags, propertyBuilder, ImmutableList.of());
+                        Date saveDate = Date.from(clock.instant());
+
+                        MailboxMessage message = messageFactory.createMessage(messageId, threadId, mailbox, internalDate, saveDate, size, bodyStartOctet, content, flags, propertyBuilder, ImmutableList.of());
                         return Mono.from(messageMapper.addReactive(mailbox, message))
                             .map(metadata -> Pair.of(metadata, Optional.empty()));
                     })));
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 9577ac410a..8d374d587a 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
@@ -22,6 +22,7 @@ package org.apache.james.mailbox.store;
 import static org.apache.james.mailbox.store.MailboxReactorUtils.block;
 import static org.apache.james.mailbox.store.mail.AbstractMessageMapper.UNLIMITED;
 
+import java.time.Clock;
 import java.time.Duration;
 import java.util.EnumSet;
 import java.util.List;
@@ -132,14 +133,15 @@ public class StoreMailboxManager implements MailboxManager {
     private final PreDeletionHooks preDeletionHooks;
     protected final MailboxManagerConfiguration configuration;
     private final ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm;
+    private final Clock clock;
 
     @Inject
     public StoreMailboxManager(MailboxSessionMapperFactory mailboxSessionMapperFactory, SessionProvider sessionProvider,
                                MailboxPathLocker locker, MessageParser messageParser,
-                               MessageId.Factory messageIdFactory, MailboxAnnotationManager annotationManager,
+                               Factory messageIdFactory, MailboxAnnotationManager annotationManager,
                                EventBus eventBus, StoreRightManager storeRightManager,
                                QuotaComponents quotaComponents, MessageSearchIndex searchIndex, MailboxManagerConfiguration configuration,
-                               PreDeletionHooks preDeletionHooks, ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm) {
+                               PreDeletionHooks preDeletionHooks, ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm, Clock clock) {
         Preconditions.checkNotNull(eventBus);
         Preconditions.checkNotNull(mailboxSessionMapperFactory);
 
@@ -158,6 +160,7 @@ public class StoreMailboxManager implements MailboxManager {
         this.configuration = configuration;
         this.preDeletionHooks = preDeletionHooks;
         this.threadIdGuessingAlgorithm = threadIdGuessingAlgorithm;
+        this.clock = clock;
     }
 
     public QuotaComponents getQuotaComponents() {
@@ -231,6 +234,10 @@ public class StoreMailboxManager implements MailboxManager {
         return threadIdGuessingAlgorithm;
     }
 
+    public Clock getClock() {
+        return clock;
+    }
+
     @Override
     public MailboxSession createSystemSession(Username userName) {
         return sessionProvider.createSystemSession(userName);
@@ -271,7 +278,7 @@ public class StoreMailboxManager implements MailboxManager {
         return new StoreMessageManager(DEFAULT_NO_MESSAGE_CAPABILITIES, getMapperFactory(), getMessageSearchIndex(), getEventBus(),
             getLocker(), mailbox, quotaManager,
             getQuotaComponents().getQuotaRootResolver(), configuration.getBatchSizes(),
-            getStoreRightManager(), preDeletionHooks, new MessageStorer.WithoutAttachment(mailboxSessionMapperFactory, messageIdFactory, new MessageFactory.StoreMessageFactory(), threadIdGuessingAlgorithm));
+            getStoreRightManager(), preDeletionHooks, new MessageStorer.WithoutAttachment(mailboxSessionMapperFactory, messageIdFactory, new MessageFactory.StoreMessageFactory(), threadIdGuessingAlgorithm, clock));
     }
 
     @Override
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/MessageMapper.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/MessageMapper.java
index 4cefb617ca..3d25e3507f 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/MessageMapper.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/MessageMapper.java
@@ -289,6 +289,7 @@ public interface MessageMapper extends Mapper {
          *  {@link MailboxMessage#getBodyOctets()}
          *  {@link MailboxMessage#getFullContentOctets()}
          *  {@link MailboxMessage#getInternalDate()}
+         *  {@link MailboxMessage#getSaveDate()}
          *  {@link MailboxMessage#getMailboxId()}
          *  {@link MailboxMessage#getMediaType()}
          *  {@link MailboxMessage#getModSeq()}
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/DelegatingMailboxMessage.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/DelegatingMailboxMessage.java
index 94fa44dd3d..dc41398a47 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/DelegatingMailboxMessage.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/DelegatingMailboxMessage.java
@@ -113,6 +113,7 @@ public abstract class DelegatingMailboxMessage implements MailboxMessage {
         return message.getMessageId();
     }
 
+    @Override
     public ThreadId getThreadId() {
         return new ThreadId(message.getMessageId());
     }
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/MailboxMessage.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/MailboxMessage.java
index a5d89b0503..6bfd3efa1f 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/MailboxMessage.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/MailboxMessage.java
@@ -18,6 +18,9 @@
  ****************************************************************/
 package org.apache.james.mailbox.store.mail.model;
 
+import java.util.Date;
+import java.util.Optional;
+
 import javax.mail.Flags;
 
 import org.apache.james.mailbox.MessageUid;
@@ -36,6 +39,8 @@ import org.apache.james.mailbox.model.ThreadId;
  */
 public interface MailboxMessage extends Message, Comparable<MailboxMessage> {
 
+    Optional<Date> EMPTY_SAVE_DATE = Optional.empty();
+
     ThreadId getThreadId();
 
     ComposedMessageIdWithMetaData getComposedMessageIdWithMetaData();
@@ -120,4 +125,6 @@ public interface MailboxMessage extends Message, Comparable<MailboxMessage> {
     }
 
     MailboxMessage copy(Mailbox mailbox) throws MailboxException;
+
+    Optional<Date> getSaveDate();
 }
\ No newline at end of file
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMailboxMessage.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMailboxMessage.java
index c611139539..e5caaa9bef 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMailboxMessage.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMailboxMessage.java
@@ -43,6 +43,7 @@ import org.apache.james.mailbox.model.ThreadId;
 import org.apache.james.mailbox.store.mail.model.DelegatingMailboxMessage;
 import org.apache.james.mailbox.store.mail.model.MailboxMessage;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Objects;
 import com.google.common.base.Preconditions;
@@ -59,6 +60,7 @@ public class SimpleMailboxMessage extends DelegatingMailboxMessage {
         private MessageId messageId;
         private ThreadId threadId;
         private Date internalDate;
+        private Optional<Date> saveDate = Optional.empty();
         private Long size;
         private Integer bodyStartOctet;
         private Content content;
@@ -94,6 +96,16 @@ public class SimpleMailboxMessage extends DelegatingMailboxMessage {
             return this;
         }
 
+        public Builder saveDate(Date saveDate) {
+            this.saveDate = Optional.ofNullable(saveDate);
+            return this;
+        }
+
+        public Builder saveDate(Optional<Date> saveDate) {
+            this.saveDate = saveDate;
+            return this;
+        }
+
         public Builder size(long size) {
             Preconditions.checkArgument(size >= 0, "size can not be negative");
             this.size = size;
@@ -149,7 +161,7 @@ public class SimpleMailboxMessage extends DelegatingMailboxMessage {
 
             ImmutableList<MessageAttachmentMetadata> attachments = this.attachments.build();
             SimpleMailboxMessage simpleMailboxMessage = new SimpleMailboxMessage(messageId, threadId, internalDate, size,
-                bodyStartOctet, content, flags, properties, mailboxId, attachments);
+                bodyStartOctet, content, flags, properties, mailboxId, attachments, saveDate);
 
             uid.ifPresent(simpleMailboxMessage::setUid);
             modseq.ifPresent(simpleMailboxMessage::setModSeq);
@@ -174,6 +186,7 @@ public class SimpleMailboxMessage extends DelegatingMailboxMessage {
             .content(copyFullContent(original))
             .messageId(original.getMessageId())
             .internalDate(original.getInternalDate())
+            .saveDate(original.getSaveDate())
             .size(original.getFullContentOctets())
             .flags(original.createFlags())
             .properties(original.getProperties());
@@ -195,6 +208,7 @@ public class SimpleMailboxMessage extends DelegatingMailboxMessage {
     private MessageUid uid;
     private final MailboxId mailboxId;
     private final ThreadId threadId;
+    private final Optional<Date> saveDate;
     private boolean answered;
     private boolean deleted;
     private boolean draft;
@@ -206,7 +220,7 @@ public class SimpleMailboxMessage extends DelegatingMailboxMessage {
 
     public SimpleMailboxMessage(MessageId messageId, ThreadId threadId, Date internalDate, long size, int bodyStartOctet,
                                 Content content, Flags flags,
-                                Properties properties, MailboxId mailboxId, List<MessageAttachmentMetadata> attachments) {
+                                Properties properties, MailboxId mailboxId, List<MessageAttachmentMetadata> attachments, Optional<Date> saveDate) {
         super(new SimpleMessage(
                 messageId,
                 content, size, internalDate,
@@ -215,17 +229,19 @@ public class SimpleMailboxMessage extends DelegatingMailboxMessage {
                 properties,
                 attachments));
 
-            setFlags(flags);
-            this.mailboxId = mailboxId;
-            this.threadId = threadId;
+        setFlags(flags);
+        this.mailboxId = mailboxId;
+        this.threadId = threadId;
+        this.saveDate = saveDate;
     }
 
+    @VisibleForTesting
     public SimpleMailboxMessage(MessageId messageId, ThreadId threadId, Date internalDate, long size, int bodyStartOctet,
                                 Content content, Flags flags,
                                 Properties properties, MailboxId mailboxId) {
         this(messageId, threadId, internalDate, size, bodyStartOctet,
                 content, flags,
-                properties, mailboxId, ImmutableList.of());
+                properties, mailboxId, ImmutableList.of(), EMPTY_SAVE_DATE);
     }
 
     @Override
@@ -303,6 +319,11 @@ public class SimpleMailboxMessage extends DelegatingMailboxMessage {
         this.uid = uid;
     }
 
+    @Override
+    public Optional<Date> getSaveDate() {
+        return saveDate;
+    }
+
     @Override
     public synchronized void setFlags(Flags flags) {
         answered = flags.contains(Flags.Flag.ANSWERED);
@@ -333,6 +354,7 @@ public class SimpleMailboxMessage extends DelegatingMailboxMessage {
             .add("uid", this.uid)
             .add("mailboxId", this.mailboxId)
             .add("threadId", this.threadId)
+            .add("saveDate", this.saveDate)
             .add("answered", this.answered)
             .add("deleted", this.deleted)
             .add("draft", this.draft)
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/MessageBuilder.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/MessageBuilder.java
index 79eecaadcd..91de349a16 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/MessageBuilder.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/MessageBuilder.java
@@ -26,6 +26,7 @@ import java.nio.charset.StandardCharsets;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Optional;
 
 import javax.mail.Flags;
 
@@ -50,6 +51,7 @@ public class MessageBuilder {
     private TestId mailboxId = TestId.of(113);
     private MessageUid uid = MessageUid.of(776);
     private Date internalDate = new Date();
+    private Optional<Date> saveDate = Optional.of(new Date());
     private int size = 8867;
     private Flags flags = new Flags();
     private byte[] body = {};
@@ -83,7 +85,7 @@ public class MessageBuilder {
         byte[] headerContent = getHeaderContent();
         ThreadId threadId = ThreadId.fromBaseMessageId(messageId);
         SimpleMailboxMessage mailboxMessage = new SimpleMailboxMessage(messageId, threadId, internalDate, size, headerContent.length,
-            new ByteContent(Bytes.concat(headerContent, body)), flags, new PropertyBuilder().build(), mailboxId, NO_ATTACHMENTS);
+            new ByteContent(Bytes.concat(headerContent, body)), flags, new PropertyBuilder().build(), mailboxId, NO_ATTACHMENTS, saveDate);
         mailboxMessage.setUid(uid);
         return mailboxMessage;
     }
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/StoreMailboxManagerTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/StoreMailboxManagerTest.java
index f6fbfa3442..8ac2b58d86 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/StoreMailboxManagerTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/StoreMailboxManagerTest.java
@@ -25,6 +25,8 @@ import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import java.time.Instant;
+
 import org.apache.james.core.Username;
 import org.apache.james.events.InVMEventBus;
 import org.apache.james.events.MemoryEventDeadLetters;
@@ -36,9 +38,9 @@ import org.apache.james.mailbox.MailboxSessionUtil;
 import org.apache.james.mailbox.MessageManager;
 import org.apache.james.mailbox.acl.UnionMailboxACLResolver;
 import org.apache.james.mailbox.exception.BadCredentialsException;
+import org.apache.james.mailbox.exception.ForbiddenDelegationException;
 import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.exception.MailboxNotFoundException;
-import org.apache.james.mailbox.exception.ForbiddenDelegationException;
 import org.apache.james.mailbox.exception.UserDoesNotExistException;
 import org.apache.james.mailbox.model.Mailbox;
 import org.apache.james.mailbox.model.MailboxACL;
@@ -58,6 +60,7 @@ import org.apache.james.mailbox.store.quota.QuotaComponents;
 import org.apache.james.mailbox.store.search.MessageSearchIndex;
 import org.apache.james.mailbox.store.search.SimpleMessageSearchIndex;
 import org.apache.james.metrics.tests.RecordingMetricFactory;
+import org.apache.james.utils.UpdatableTickingClock;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
@@ -110,7 +113,7 @@ class StoreMailboxManagerTest {
         storeMailboxManager = new StoreMailboxManager(mockedMapperFactory, sessionProvider,
                 new JVMMailboxPathLocker(), new MessageParser(), messageIdFactory,
                 annotationManager, eventBus, storeRightManager, quotaComponents, index, MailboxManagerConfiguration.DEFAULT,
-                PreDeletionHooks.NO_PRE_DELETION_HOOK, threadIdGuessingAlgorithm);
+                PreDeletionHooks.NO_PRE_DELETION_HOOK, threadIdGuessingAlgorithm, new UpdatableTickingClock(Instant.now()));
     }
 
     @Test
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageWithAttachmentMapperTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageWithAttachmentMapperTest.java
index 42d66e0098..c39bba5e45 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageWithAttachmentMapperTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageWithAttachmentMapperTest.java
@@ -19,6 +19,7 @@
 
 package org.apache.james.mailbox.store.mail.model;
 
+import static org.apache.james.mailbox.store.mail.model.MailboxMessage.EMPTY_SAVE_DATE;
 import static org.apache.james.mailbox.store.mail.model.MessageAssert.assertThat;
 import static org.assertj.core.api.Assertions.assertThat;
 
@@ -181,7 +182,7 @@ public abstract class MessageWithAttachmentMapperTest {
     }
 
     private SimpleMailboxMessage createMessage(Mailbox mailbox, MessageId messageId, ThreadId threadId, String content, int bodyStart, PropertyBuilder propertyBuilder, List<MessageAttachmentMetadata> attachments) {
-        return new SimpleMailboxMessage(messageId, threadId, new Date(), content.length(), bodyStart, new ByteContent(content.getBytes()), new Flags(), propertyBuilder.build(), mailbox.getMailboxId(), attachments);
+        return new SimpleMailboxMessage(messageId, threadId, new Date(), content.length(), bodyStart, new ByteContent(content.getBytes()), new Flags(), propertyBuilder.build(), mailbox.getMailboxId(), attachments, EMPTY_SAVE_DATE);
     }
 
     private SimpleMailboxMessage createMessage(Mailbox mailbox, MessageId messageId, ThreadId threadId, String content, int bodyStart, PropertyBuilder propertyBuilder) {
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMailboxMessageTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMailboxMessageTest.java
index 217009043d..4f4ed98701 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMailboxMessageTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMailboxMessageTest.java
@@ -19,6 +19,7 @@
 package org.apache.james.mailbox.store.mail.model.impl;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 import java.io.IOException;
@@ -26,6 +27,8 @@ import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.util.Calendar;
 import java.util.Date;
+import java.util.List;
+import java.util.Optional;
 
 import javax.mail.Flags;
 
@@ -43,6 +46,7 @@ import org.apache.james.mailbox.model.TestId;
 import org.apache.james.mailbox.model.TestMessageId;
 import org.apache.james.mailbox.model.ThreadId;
 import org.apache.james.mailbox.store.mail.model.DefaultMessageId;
+import org.apache.james.mailbox.store.mail.model.MailboxMessage;
 import org.assertj.core.api.SoftAssertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -113,13 +117,16 @@ class SimpleMailboxMessageTest {
         propertyBuilder.setSubType(plain);
         MessageId messageId = new TestMessageId.Factory().generate();
         ThreadId threadId = ThreadId.fromBaseMessageId(messageId);
+        Optional<Date> saveDate = Optional.of(new Date());
         SimpleMailboxMessage original = new SimpleMailboxMessage(messageId, threadId, new Date(),
             MESSAGE_CONTENT.length(),
             BODY_START_OCTET,
             CONTENT_STREAM,
             new Flags(),
             propertyBuilder.build(),
-            TEST_ID);
+            TEST_ID,
+            List.of(),
+            saveDate);
 
         SimpleMailboxMessage copy = SimpleMailboxMessage.copy(TestId.of(1337), original);
 
@@ -132,7 +139,7 @@ class SimpleMailboxMessageTest {
         assertThat(SimpleMailboxMessage.copy(TEST_ID, original).getTextualLineCount()).isEqualTo(textualLineCount);
         assertThat(SimpleMailboxMessage.copy(TEST_ID, original).getMediaType()).isEqualTo(text);
         assertThat(SimpleMailboxMessage.copy(TEST_ID, original).getSubType()).isEqualTo(plain);
-
+        assertThat(SimpleMailboxMessage.copy(TEST_ID, original).getSaveDate()).isEqualTo(saveDate);
     }
 
     private static SimpleMailboxMessage buildMessage(String content) {
@@ -254,6 +261,22 @@ class SimpleMailboxMessageTest {
             .isInstanceOf(NullPointerException.class);
     }
 
+    @Test
+    void buildShouldNotThrowOnMissingSaveDate() {
+        assertThatCode(() -> SimpleMailboxMessage.builder()
+            .messageId(MESSAGE_ID)
+            .mailboxId(TEST_ID)
+            .threadId(THREAD_ID)
+            .internalDate(new Date())
+            .bodyStartOctet(BODY_START_OCTET)
+            .size(SIZE)
+            .content(CONTENT_STREAM)
+            .flags(new Flags())
+            .properties(new PropertyBuilder())
+            .build())
+            .doesNotThrowAnyException();
+    }
+
     @Test
     void buildShouldThrowOnMissingMailboxId() {
         Date internalDate = new Date();
@@ -370,4 +393,40 @@ class SimpleMailboxMessageTest {
         assertThat(message.getThreadId().getBaseMessageId()).isInstanceOf(MessageId.class);
     }
 
+    @Test
+    void simpleMessageShouldReturnSaveDateWhenEmpty() {
+        MailboxMessage mailboxMessage = SimpleMailboxMessage.builder()
+            .messageId(MESSAGE_ID)
+            .mailboxId(TEST_ID)
+            .threadId(THREAD_ID)
+            .internalDate(new Date())
+            .bodyStartOctet(BODY_START_OCTET)
+            .size(SIZE)
+            .content(CONTENT_STREAM)
+            .flags(new Flags())
+            .properties(new PropertyBuilder())
+            .build();
+
+        assertThat(mailboxMessage.getSaveDate()).isEmpty();
+    }
+
+    @Test
+    void simpleMessageShouldReturnSaveDate() {
+        Optional<Date> saveDate = Optional.of(new Date());
+        MailboxMessage mailboxMessage = SimpleMailboxMessage.builder()
+            .messageId(MESSAGE_ID)
+            .mailboxId(TEST_ID)
+            .threadId(THREAD_ID)
+            .internalDate(new Date())
+            .saveDate(saveDate)
+            .bodyStartOctet(BODY_START_OCTET)
+            .size(SIZE)
+            .content(CONTENT_STREAM)
+            .flags(new Flags())
+            .properties(new PropertyBuilder())
+            .build();
+
+        assertThat(mailboxMessage.getSaveDate()).isEqualTo(saveDate);
+    }
+
 }
diff --git a/mpt/impl/imap-mailbox/cassandra/pom.xml b/mpt/impl/imap-mailbox/cassandra/pom.xml
index d50eb2454b..2d0ef3d317 100644
--- a/mpt/impl/imap-mailbox/cassandra/pom.xml
+++ b/mpt/impl/imap-mailbox/cassandra/pom.xml
@@ -76,6 +76,11 @@
             <artifactId>event-bus-in-vm</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-testing</artifactId>
+            <scope>runtime</scope>
+        </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
             <artifactId>metrics-tests</artifactId>
diff --git a/mpt/impl/imap-mailbox/cassandra/src/test/java/org/apache/james/mpt/imapmailbox/cassandra/host/CassandraHostSystem.java b/mpt/impl/imap-mailbox/cassandra/src/test/java/org/apache/james/mpt/imapmailbox/cassandra/host/CassandraHostSystem.java
index e9b7b3c789..055718d03e 100644
--- a/mpt/impl/imap-mailbox/cassandra/src/test/java/org/apache/james/mpt/imapmailbox/cassandra/host/CassandraHostSystem.java
+++ b/mpt/impl/imap-mailbox/cassandra/src/test/java/org/apache/james/mpt/imapmailbox/cassandra/host/CassandraHostSystem.java
@@ -18,6 +18,8 @@
  ****************************************************************/
 package org.apache.james.mpt.imapmailbox.cassandra.host;
 
+import java.time.Instant;
+
 import org.apache.james.backends.cassandra.CassandraCluster;
 import org.apache.james.core.quota.QuotaCountLimit;
 import org.apache.james.core.quota.QuotaSizeLimit;
@@ -65,6 +67,7 @@ import org.apache.james.metrics.tests.RecordingMetricFactory;
 import org.apache.james.mpt.api.ImapFeatures;
 import org.apache.james.mpt.api.ImapFeatures.Feature;
 import org.apache.james.mpt.host.JamesImapHostSystem;
+import org.apache.james.utils.UpdatableTickingClock;
 
 import com.datastax.oss.driver.api.core.CqlSession;
 
@@ -95,6 +98,7 @@ public class CassandraHostSystem extends JamesImapHostSystem {
         CqlSession session = cassandra.getConf();
         CassandraMessageId.Factory messageIdFactory = new CassandraMessageId.Factory();
         ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm = new NaiveThreadIdGuessingAlgorithm();
+        UpdatableTickingClock clock = new UpdatableTickingClock(Instant.now());
         CassandraMailboxSessionMapperFactory mapperFactory = TestCassandraMailboxSessionMapperFactory.forTests(
             cassandra, messageIdFactory);
 
@@ -123,7 +127,7 @@ public class CassandraHostSystem extends JamesImapHostSystem {
         mailboxManager = new CassandraMailboxManager(mapperFactory, sessionProvider,
             new JVMMailboxPathLocker(), new MessageParser(), messageIdFactory,
             eventBus, annotationManager, storeRightManager, quotaComponents, index, MailboxManagerConfiguration.DEFAULT,
-            PreDeletionHooks.NO_PRE_DELETION_HOOK, threadIdGuessingAlgorithm);
+            PreDeletionHooks.NO_PRE_DELETION_HOOK, threadIdGuessingAlgorithm, clock);
 
         eventBus.register(quotaUpdater);
 
diff --git a/mpt/impl/imap-mailbox/jpa/pom.xml b/mpt/impl/imap-mailbox/jpa/pom.xml
index 2a51980556..64553eea5f 100644
--- a/mpt/impl/imap-mailbox/jpa/pom.xml
+++ b/mpt/impl/imap-mailbox/jpa/pom.xml
@@ -74,6 +74,11 @@
             <artifactId>event-bus-in-vm</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-testing</artifactId>
+            <scope>runtime</scope>
+        </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
             <artifactId>metrics-tests</artifactId>
diff --git a/mpt/impl/imap-mailbox/jpa/src/test/java/org/apache/james/mpt/imapmailbox/jpa/host/JPAHostSystem.java b/mpt/impl/imap-mailbox/jpa/src/test/java/org/apache/james/mpt/imapmailbox/jpa/host/JPAHostSystem.java
index 4ef7a4fa78..f2ecfd6b74 100644
--- a/mpt/impl/imap-mailbox/jpa/src/test/java/org/apache/james/mpt/imapmailbox/jpa/host/JPAHostSystem.java
+++ b/mpt/impl/imap-mailbox/jpa/src/test/java/org/apache/james/mpt/imapmailbox/jpa/host/JPAHostSystem.java
@@ -19,6 +19,8 @@
 
 package org.apache.james.mpt.imapmailbox.jpa.host;
 
+import java.time.Instant;
+
 import javax.persistence.EntityManagerFactory;
 
 import org.apache.james.backends.jpa.JpaTestCluster;
@@ -65,6 +67,7 @@ import org.apache.james.metrics.tests.RecordingMetricFactory;
 import org.apache.james.mpt.api.ImapFeatures;
 import org.apache.james.mpt.api.ImapFeatures.Feature;
 import org.apache.james.mpt.host.JamesImapHostSystem;
+import org.apache.james.utils.UpdatableTickingClock;
 
 import com.google.common.collect.ImmutableList;
 
@@ -116,7 +119,7 @@ public class JPAHostSystem extends JamesImapHostSystem {
         MessageSearchIndex index = new SimpleMessageSearchIndex(mapperFactory, mapperFactory, new DefaultTextExtractor(), attachmentContentLoader);
 
         mailboxManager = new OpenJPAMailboxManager(mapperFactory, sessionProvider, messageParser, new DefaultMessageId.Factory(),
-            eventBus, annotationManager, storeRightManager, quotaComponents, index, new NaiveThreadIdGuessingAlgorithm());
+            eventBus, annotationManager, storeRightManager, quotaComponents, index, new NaiveThreadIdGuessingAlgorithm(), new UpdatableTickingClock(Instant.now()));
 
         eventBus.register(quotaUpdater);
         eventBus.register(new MailboxAnnotationListener(mapperFactory, sessionProvider));


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