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:43 UTC

[james-project] branch master updated (f4a708488c -> 73ab92cf19)

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

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


    from f4a708488c JAMES-3754 LIST STATUS should not read mailboxes information twice
     new 439c961a4c JAMES-3754 Add saveDate field to MailboxMessage and associated Cassandra tables
     new 12535503be [FIX] UnloadedMessageResult::getThreadId should not return naive threadId anymore
     new 574a3204e7 JAMES-3754 Add saveDate field to MessageResult
     new 6e0656ab87 [FIX] MessageMetaData equals and hashcode should not just depend on uid
     new de43a28cdb [FIX] InMemoryMessageMapper::save should return MessageMetaData of copy message
     new 746ca874ae JAMES-3754 Mapper layers should set new saveDate when add/copy/move messages
     new 7f17df7a62 JAMES-3754 Cassandra tables upgrade instructions
     new 73ab92cf19 JAMES-3754 Fix Guice binding

The 8 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../listeners/SetCustomFlagOnBigMessagesTest.java  |  3 +-
 .../james/mailbox/model/MessageMetaData.java       | 32 ++++++--
 .../apache/james/mailbox/model/MessageResult.java  |  3 +
 .../java/org/apache/james/mailbox/EventTest.java   |  9 +-
 .../apache/james/mailbox/MailboxListenerTest.java  |  2 +-
 .../apache/james/mailbox/MailboxManagerTest.java   | 20 +++++
 ...entQuotasTest.java => MessageMetaDataTest.java} |  7 +-
 mailbox/cassandra/pom.xml                          |  5 ++
 .../mailbox/cassandra/CassandraMailboxManager.java |  9 +-
 .../CassandraMailboxSessionMapperFactory.java      |  9 +-
 .../mailbox/cassandra/CassandraMessageManager.java |  7 +-
 .../mailbox/cassandra/mail/AttachmentLoader.java   |  5 +-
 .../cassandra/mail/CassandraMessageIdDAO.java      |  5 ++
 .../cassandra/mail/CassandraMessageIdMapper.java   | 10 ++-
 .../mail/CassandraMessageIdToImapUidDAO.java       |  8 ++
 .../cassandra/mail/CassandraMessageMapper.java     | 24 ++++--
 .../cassandra/mail/CassandraMessageMetadata.java   | 39 ++++++++-
 .../cassandra/mail/MessageRepresentation.java      |  4 +-
 .../cassandra/modules/CassandraMessageModule.java  |  2 +
 .../cassandra/table/CassandraMessageIdTable.java   |  2 +
 .../cassandra/CassandraMailboxManagerProvider.java | 12 ++-
 .../CassandraSubscriptionManagerTest.java          |  5 +-
 .../cassandra/CassandraTestSystemFixture.java      |  5 +-
 .../TestCassandraMailboxSessionMapperFactory.java  | 13 +++
 .../CassandraMailboxManagerAttachmentTest.java     |  6 +-
 .../cassandra/mail/CassandraMapperProvider.java    | 14 +++-
 .../cassandra/mail/CassandraMessageDAOV3Test.java  | 18 ++--
 .../cassandra/mail/CassandraMessageIdDAOTest.java  | 52 ++++++++++++
 ...andraMessageIdMapperRelaxedConsistencyTest.java | 39 ++++++---
 .../mail/CassandraMessageIdMapperTest.java         | 16 +++-
 .../mail/CassandraMessageIdToImapUidDAOTest.java   | 51 ++++++++++++
 ...ssandraMessageMapperRelaxedConsistencyTest.java | 39 ++++++---
 .../cassandra/mail/CassandraMessageMapperTest.java | 16 +++-
 .../mailbox/cassandra/mail/utils/GuiceUtils.java   |  4 +-
 .../scala/org/apache/james/event/json/DTOs.scala   |  5 +-
 .../james/event/json/AddedSerializationTest.java   | 13 ++-
 .../event/json/ExpungedSerializationTest.java      | 16 ++--
 mailbox/jpa/pom.xml                                |  5 ++
 .../model/openjpa/AbstractJPAMailboxMessage.java   | 11 +++
 .../mailbox/jpa/openjpa/OpenJPAMailboxManager.java |  9 +-
 .../mailbox/jpa/openjpa/OpenJPAMessageFactory.java |  2 +-
 .../mailbox/jpa/openjpa/OpenJPAMessageManager.java |  6 +-
 .../main/resources/META-INF/spring/mailbox-jpa.xml |  2 +
 .../james/mailbox/jpa/JPAMailboxManagerTest.java   |  6 ++
 .../mailbox/jpa/JpaMailboxManagerProvider.java     |  5 +-
 .../mailbox/jpa/mail/JpaMessageMapperTest.java     | 16 +++-
 mailbox/memory/pom.xml                             |  5 ++
 .../mailbox/inmemory/InMemoryMailboxManager.java   |  9 +-
 .../InMemoryMailboxSessionMapperFactory.java       |  9 +-
 .../mailbox/inmemory/InMemoryMessageManager.java   |  7 +-
 .../inmemory/mail/InMemoryMessageMapper.java       |  9 +-
 .../resources/META-INF/spring/mailbox-memory.xml   |  7 +-
 .../inmemory/mail/InMemoryMapperProvider.java      |  9 +-
 .../inmemory/mail/InMemoryMessageIdMapperTest.java |  9 +-
 .../inmemory/mail/MemoryMessageMapperTest.java     | 10 ++-
 .../manager/InMemoryIntegrationResources.java      |  8 +-
 .../OpenSearchListeningMessageSearchIndexTest.java |  6 +-
 mailbox/store/pom.xml                              |  5 ++
 .../apache/james/mailbox/store/MessageFactory.java |  7 +-
 .../james/mailbox/store/MessageResultImpl.java     |  6 ++
 .../apache/james/mailbox/store/MessageStorer.java  | 34 +++++---
 .../james/mailbox/store/StoreMailboxManager.java   | 13 ++-
 .../mailbox/store/StoreMessageResultIterator.java  |  8 +-
 .../mailbox/store/mail/AbstractMessageMapper.java  |  5 +-
 .../james/mailbox/store/mail/MessageMapper.java    |  1 +
 .../store/mail/model/DelegatingMailboxMessage.java |  1 +
 .../mailbox/store/mail/model/MailboxMessage.java   | 11 ++-
 .../mail/model/impl/SimpleMailboxMessage.java      | 39 +++++++--
 .../apache/james/mailbox/store/MessageBuilder.java |  4 +-
 .../james/mailbox/store/PreDeletionHooksTest.java  |  3 +-
 .../mailbox/store/StoreMailboxManagerTest.java     |  7 +-
 .../store/mail/model/MessageIdMapperTest.java      | 51 ++++++++++++
 .../store/mail/model/MessageMapperTest.java        | 96 ++++++++++++++++++++++
 .../model/MessageWithAttachmentMapperTest.java     |  3 +-
 .../store/mail/model/MetadataMapAssertTest.java    |  9 +-
 .../mail/model/impl/SimpleMailboxMessageTest.java  | 63 +++++++++++++-
 .../quota/ListeningCurrentQuotaUpdaterTest.java    |  8 +-
 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 +-
 .../processor/base/MailboxEventAnalyserTest.java   |  3 +-
 .../processor/base/SelectedMailboxImplTest.java    |  3 +-
 upgrade-instructions.md                            | 17 ++++
 84 files changed, 922 insertions(+), 175 deletions(-)
 copy mailbox/api/src/test/java/org/apache/james/mailbox/model/{CurrentQuotasTest.java => MessageMetaDataTest.java} (90%)


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


[james-project] 05/08: [FIX] InMemoryMessageMapper::save should return MessageMetaData of copy message

Posted by bt...@apache.org.
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 de43a28cdb9444d8ceeee0268a86f123444218bb
Author: Quan Tran <hq...@linagora.com>
AuthorDate: Thu Dec 1 17:32:46 2022 +0700

    [FIX] InMemoryMessageMapper::save should return MessageMetaData of copy message
    
    Not original message.
---
 .../org/apache/james/mailbox/inmemory/mail/InMemoryMessageMapper.java   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryMessageMapper.java b/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryMessageMapper.java
index 34e1f55cca..3545e85b7c 100644
--- a/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryMessageMapper.java
+++ b/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryMessageMapper.java
@@ -202,7 +202,7 @@ public class InMemoryMessageMapper extends AbstractMessageMapper {
         copy.setModSeq(message.getModSeq());
         getMembershipByUidForMailbox(mailbox).put(message.getUid(), copy);
 
-        return message.metaData();
+        return copy.metaData();
     }
 
     @Override


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


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

Posted by bt...@apache.org.
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


[james-project] 02/08: [FIX] UnloadedMessageResult::getThreadId should not return naive threadId anymore

Posted by bt...@apache.org.
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 12535503bee7fda52c01d8447a3a513834accdb2
Author: Quan Tran <hq...@linagora.com>
AuthorDate: Tue Nov 29 17:05:36 2022 +0700

    [FIX] UnloadedMessageResult::getThreadId should not return naive threadId anymore
---
 .../java/org/apache/james/mailbox/store/StoreMessageResultIterator.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageResultIterator.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageResultIterator.java
index 52110c05d6..ec9d68b755 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageResultIterator.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageResultIterator.java
@@ -180,7 +180,7 @@ public class StoreMessageResultIterator implements MessageResultIterator {
 
         @Override
         public ThreadId getThreadId() {
-            return new ThreadId(messageMetaData.getMessageId());
+            return messageMetaData.getThreadId();
         }
 
         @Override


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


[james-project] 06/08: JAMES-3754 Mapper layers should set new saveDate when add/copy/move messages

Posted by bt...@apache.org.
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 746ca874aecfb5ea77679a081a32999471bbbce7
Author: Quan Tran <hq...@linagora.com>
AuthorDate: Thu Dec 1 17:35:58 2022 +0700

    JAMES-3754 Mapper layers should set new saveDate when add/copy/move messages
---
 .../CassandraMailboxSessionMapperFactory.java      |  9 +-
 .../mailbox/cassandra/mail/AttachmentLoader.java   |  5 +-
 .../cassandra/mail/CassandraMessageIdMapper.java   | 10 ++-
 .../cassandra/mail/CassandraMessageMapper.java     | 24 ++++--
 .../cassandra/mail/CassandraMessageMetadata.java   |  5 ++
 .../cassandra/mail/MessageRepresentation.java      |  4 +-
 .../CassandraSubscriptionManagerTest.java          |  5 +-
 .../TestCassandraMailboxSessionMapperFactory.java  | 13 +++
 .../cassandra/mail/CassandraMapperProvider.java    | 14 +++-
 ...andraMessageIdMapperRelaxedConsistencyTest.java | 39 ++++++---
 .../mail/CassandraMessageIdMapperTest.java         | 16 +++-
 ...ssandraMessageMapperRelaxedConsistencyTest.java | 39 ++++++---
 .../cassandra/mail/CassandraMessageMapperTest.java | 16 +++-
 .../mailbox/cassandra/mail/utils/GuiceUtils.java   |  4 +-
 .../model/openjpa/AbstractJPAMailboxMessage.java   |  5 ++
 .../mailbox/jpa/mail/JpaMessageMapperTest.java     | 16 +++-
 .../InMemoryMailboxSessionMapperFactory.java       |  6 +-
 .../inmemory/mail/InMemoryMessageMapper.java       |  7 +-
 .../inmemory/mail/InMemoryMapperProvider.java      |  9 +-
 .../inmemory/mail/InMemoryMessageIdMapperTest.java |  9 +-
 .../inmemory/mail/MemoryMessageMapperTest.java     | 10 ++-
 .../manager/InMemoryIntegrationResources.java      |  4 +-
 .../OpenSearchListeningMessageSearchIndexTest.java |  6 +-
 .../mailbox/store/mail/AbstractMessageMapper.java  |  5 +-
 .../mailbox/store/mail/model/MailboxMessage.java   |  2 +
 .../mail/model/impl/SimpleMailboxMessage.java      |  7 +-
 .../store/mail/model/MessageIdMapperTest.java      | 51 ++++++++++++
 .../store/mail/model/MessageMapperTest.java        | 96 ++++++++++++++++++++++
 28 files changed, 370 insertions(+), 66 deletions(-)

diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxSessionMapperFactory.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxSessionMapperFactory.java
index 4e57852f88..22e150af51 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxSessionMapperFactory.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxSessionMapperFactory.java
@@ -19,6 +19,8 @@
 
 package org.apache.james.mailbox.cassandra;
 
+import java.time.Clock;
+
 import javax.inject.Inject;
 
 import org.apache.james.backends.cassandra.init.configuration.CassandraConfiguration;
@@ -106,7 +108,8 @@ public class CassandraMailboxSessionMapperFactory extends MailboxSessionMapperFa
                                                 CassandraUserMailboxRightsDAO userMailboxRightsDAO,
                                                 RecomputeMailboxCountersService recomputeMailboxCountersService,
                                                 CassandraConfiguration cassandraConfiguration,
-                                                BatchSizes batchSizes) {
+                                                BatchSizes batchSizes,
+                                                Clock clock) {
         this.uidProvider = uidProvider;
         this.modSeqProvider = modSeqProvider;
         this.threadDAO = threadDAO;
@@ -150,10 +153,10 @@ public class CassandraMailboxSessionMapperFactory extends MailboxSessionMapperFa
             firstUnseenDAO,
             deletedMessageDAO,
             blobStore,
-            cassandraConfiguration, batchSizes, recomputeMailboxCountersService);
+            cassandraConfiguration, batchSizes, recomputeMailboxCountersService, clock);
         this.cassandraMessageIdMapper = new CassandraMessageIdMapper(cassandraMailboxMapper, mailboxDAO,
             cassandraAttachmentMapper, imapUidDAO, messageIdDAO, messageDAO, messageDAOV3, indexTableHandler,
-            modSeqProvider, blobStore, cassandraConfiguration, batchSizes);
+            modSeqProvider, blobStore, cassandraConfiguration, batchSizes, clock);
         this.cassandraAnnotationMapper = new CassandraAnnotationMapper(session);
     }
 
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/AttachmentLoader.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/AttachmentLoader.java
index 4d1a4f77c8..f37267caa2 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/AttachmentLoader.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/AttachmentLoader.java
@@ -18,7 +18,9 @@
  ****************************************************************/
 package org.apache.james.mailbox.cassandra.mail;
 
+import java.util.Date;
 import java.util.List;
+import java.util.Optional;
 import java.util.stream.Stream;
 
 import org.apache.commons.lang3.tuple.Pair;
@@ -43,9 +45,10 @@ public class AttachmentLoader {
     }
 
     public Mono<CassandraMailboxMessage> addAttachmentToMessage(Pair<ComposedMessageIdWithMetaData, MessageRepresentation> messageRepresentation,
+                                                                Optional<Date> saveDate,
                                                                 MessageMapper.FetchType fetchType) {
         return loadAttachments(messageRepresentation.getRight().getAttachments().stream(), fetchType)
-            .map(attachments -> messageRepresentation.getRight().toMailboxMessage(messageRepresentation.getLeft(), attachments))
+            .map(attachments -> messageRepresentation.getRight().toMailboxMessage(messageRepresentation.getLeft(), attachments, saveDate))
             .map(message -> new CassandraMailboxMessage(message, messageRepresentation.getRight().getHeaderId()));
     }
 
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapper.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapper.java
index 39bf53c626..9b2b7dd7af 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapper.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapper.java
@@ -23,8 +23,10 @@ import static org.apache.james.backends.cassandra.init.configuration.JamesExecut
 import static org.apache.james.blob.api.BlobStore.StoragePolicy.SIZE_BASED;
 import static org.apache.james.util.ReactorUtils.DEFAULT_CONCURRENCY;
 
+import java.time.Clock;
 import java.time.Duration;
 import java.util.Collection;
+import java.util.Date;
 import java.util.List;
 import java.util.Optional;
 import java.util.function.Function;
@@ -90,11 +92,12 @@ public class CassandraMessageIdMapper implements MessageIdMapper {
     private final BlobStore blobStore;
     private final CassandraConfiguration cassandraConfiguration;
     private final BatchSizes batchSizes;
+    private final Clock clock;
 
     public CassandraMessageIdMapper(MailboxMapper mailboxMapper, CassandraMailboxDAO mailboxDAO, CassandraAttachmentMapper attachmentMapper,
                                     CassandraMessageIdToImapUidDAO imapUidDAO, CassandraMessageIdDAO messageIdDAO,
                                     CassandraMessageDAO messageDAO, CassandraMessageDAOV3 messageDAOV3, CassandraIndexTableHandler indexTableHandler,
-                                    ModSeqProvider modSeqProvider, BlobStore blobStore, CassandraConfiguration cassandraConfiguration, BatchSizes batchSizes) {
+                                    ModSeqProvider modSeqProvider, BlobStore blobStore, CassandraConfiguration cassandraConfiguration, BatchSizes batchSizes, Clock clock) {
 
         this.mailboxMapper = mailboxMapper;
         this.mailboxDAO = mailboxDAO;
@@ -108,6 +111,7 @@ public class CassandraMessageIdMapper implements MessageIdMapper {
         this.blobStore = blobStore;
         this.cassandraConfiguration = cassandraConfiguration;
         this.batchSizes = batchSizes;
+        this.clock = clock;
     }
 
     @Override
@@ -138,7 +142,7 @@ public class CassandraMessageIdMapper implements MessageIdMapper {
         return messageDAOV3.retrieveMessage(metadata.getComposedMessageId(), fetchType)
             .switchIfEmpty(Mono.defer(() -> messageDAO.retrieveMessage(metadata.getComposedMessageId(), fetchType)))
             .map(messageRepresentation -> Pair.of(metadata.getComposedMessageId(), messageRepresentation))
-            .flatMap(messageRepresentation -> attachmentLoader.addAttachmentToMessage(messageRepresentation, fetchType));
+            .flatMap(messageRepresentation -> attachmentLoader.addAttachmentToMessage(messageRepresentation, metadata.getSaveDate(), fetchType));
     }
 
     @Override
@@ -187,6 +191,7 @@ public class CassandraMessageIdMapper implements MessageIdMapper {
     @Override
     public void save(MailboxMessage mailboxMessage) throws MailboxException {
         CassandraId mailboxId = (CassandraId) mailboxMessage.getMailboxId();
+        mailboxMessage.setSaveDate(Date.from(clock.instant()));
         MailboxReactorUtils.block(mailboxMapper.findMailboxById(mailboxId)
             .switchIfEmpty(Mono.error(() -> new MailboxNotFoundException(mailboxId)))
             .then(messageDAOV3.save(mailboxMessage))
@@ -200,6 +205,7 @@ public class CassandraMessageIdMapper implements MessageIdMapper {
 
     @Override
     public Mono<Void> copyInMailboxReactive(MailboxMessage mailboxMessage, Mailbox mailbox) {
+        mailboxMessage.setSaveDate(Date.from(clock.instant()));
         CassandraId mailboxId = (CassandraId) mailbox.getMailboxId();
         return insertMetadata(mailboxMessage, mailboxId, CassandraMessageMetadata.from(mailboxMessage)
             .withMailboxId(mailboxId));
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapper.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapper.java
index ae473652a4..5f6924ae0b 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapper.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapper.java
@@ -25,9 +25,11 @@ import static org.apache.james.blob.api.BlobStore.StoragePolicy.SIZE_BASED;
 import static org.apache.james.util.ReactorUtils.DEFAULT_CONCURRENCY;
 
 import java.security.SecureRandom;
+import java.time.Clock;
 import java.time.Duration;
 import java.util.Collection;
 import java.util.Comparator;
+import java.util.Date;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -110,6 +112,7 @@ public class CassandraMessageMapper implements MessageMapper {
     private final RecomputeMailboxCountersService recomputeMailboxCountersService;
     private final SecureRandom secureRandom;
     private final int reactorConcurrency;
+    private final Clock clock;
 
     public CassandraMessageMapper(UidProvider uidProvider, ModSeqProvider modSeqProvider,
                                   CassandraAttachmentMapper attachmentMapper,
@@ -118,7 +121,7 @@ public class CassandraMessageMapper implements MessageMapper {
                                   CassandraMailboxRecentsDAO mailboxRecentDAO, CassandraApplicableFlagDAO applicableFlagDAO,
                                   CassandraIndexTableHandler indexTableHandler, CassandraFirstUnseenDAO firstUnseenDAO,
                                   CassandraDeletedMessageDAO deletedMessageDAO, BlobStore blobStore, CassandraConfiguration cassandraConfiguration,
-                                  BatchSizes batchSizes, RecomputeMailboxCountersService recomputeMailboxCountersService) {
+                                  BatchSizes batchSizes, RecomputeMailboxCountersService recomputeMailboxCountersService, Clock clock) {
         this.uidProvider = uidProvider;
         this.modSeqProvider = modSeqProvider;
         this.messageDAO = messageDAO;
@@ -138,6 +141,7 @@ public class CassandraMessageMapper implements MessageMapper {
         this.recomputeMailboxCountersService = recomputeMailboxCountersService;
         this.secureRandom = new SecureRandom();
         this.reactorConcurrency = evaluateReactorConcurrency();
+        this.clock = clock;
     }
 
     @Override
@@ -273,7 +277,7 @@ public class CassandraMessageMapper implements MessageMapper {
         return messageDAOV3.retrieveMessage(metadata.getComposedMessageId(), fetchType)
             .switchIfEmpty(Mono.defer(() -> messageDAO.retrieveMessage(metadata.getComposedMessageId(), fetchType)))
             .map(messageRepresentation -> Pair.of(metadata.getComposedMessageId(), messageRepresentation))
-            .flatMap(messageRepresentation -> attachmentLoader.addAttachmentToMessage(messageRepresentation, fetchType));
+            .flatMap(messageRepresentation -> attachmentLoader.addAttachmentToMessage(messageRepresentation, metadata.getSaveDate(), fetchType));
     }
 
     @Override
@@ -333,18 +337,17 @@ public class CassandraMessageMapper implements MessageMapper {
 
         return Flux.fromIterable(MessageRange.toRanges(uids))
             .concatMap(range -> messageIdDAO.retrieveMessages(mailboxId, range, Limit.unlimited()))
-            .map(CassandraMessageMetadata::getComposedMessageId)
-            .flatMap(this::expungeOne, cassandraConfiguration.getExpungeChunkSize())
+            .flatMap(cassandraMessageMetadata -> expungeOne(cassandraMessageMetadata.getComposedMessageId(), cassandraMessageMetadata.getSaveDate()), cassandraConfiguration.getExpungeChunkSize())
             .collect(ImmutableMap.toImmutableMap(MailboxMessage::getUid, MailboxMessage::metaData))
             .flatMap(messageMap -> indexTableHandler.updateIndexOnDelete(mailboxId, messageMap.values())
                 .thenReturn(messageMap));
     }
 
-    private Mono<SimpleMailboxMessage> expungeOne(ComposedMessageIdWithMetaData metaData) {
+    private Mono<SimpleMailboxMessage> expungeOne(ComposedMessageIdWithMetaData metaData, Optional<Date> saveDate) {
         return delete(metaData)
             .then(messageDAOV3.retrieveMessage(metaData, FetchType.METADATA)
                 .switchIfEmpty(Mono.defer(() -> messageDAO.retrieveMessage(metaData, FetchType.METADATA))))
-            .map(pair -> pair.toMailboxMessage(metaData, ImmutableList.of()));
+            .map(pair -> pair.toMailboxMessage(metaData, ImmutableList.of(), saveDate));
     }
 
     @Override
@@ -401,14 +404,14 @@ public class CassandraMessageMapper implements MessageMapper {
     public Mono<MessageMetaData> addReactive(Mailbox mailbox, MailboxMessage message) {
         CassandraId mailboxId = (CassandraId) mailbox.getMailboxId();
 
-        return addUidAndModseq(message, mailboxId)
+        return addUidAndModseqAndSaveDate(message, mailboxId)
             .flatMap(Throwing.function((MailboxMessage messageWithUidAndModSeq) ->
                 save(mailbox, messageWithUidAndModSeq)
                     .thenReturn(messageWithUidAndModSeq)))
             .map(MailboxMessage::metaData);
     }
 
-    private Mono<MailboxMessage> addUidAndModseq(MailboxMessage message, CassandraId mailboxId) {
+    private Mono<MailboxMessage> addUidAndModseqAndSaveDate(MailboxMessage message, CassandraId mailboxId) {
         Mono<MessageUid> messageUidMono = uidProvider
             .nextUidReactive(mailboxId)
             .switchIfEmpty(Mono.error(() -> new MailboxException("Can not find a UID to save " + message.getMessageId() + " in " + mailboxId)));
@@ -420,6 +423,7 @@ public class CassandraMessageMapper implements MessageMapper {
                 .doOnNext(tuple -> {
                     message.setUid(tuple.getT1());
                     message.setModSeq(tuple.getT2());
+                    message.setSaveDate(Date.from(clock.instant()));
                 })
                 .thenReturn(message);
     }
@@ -545,6 +549,7 @@ public class CassandraMessageMapper implements MessageMapper {
     @Override
     public Mono<MessageMetaData> copyReactive(Mailbox mailbox, MailboxMessage original) {
         original.setFlags(new FlagsBuilder().add(original.createFlags()).add(Flag.RECENT).build());
+        original.setSaveDate(Date.from(clock.instant()));
         return setInMailboxReactive(mailbox, original);
     }
 
@@ -556,6 +561,7 @@ public class CassandraMessageMapper implements MessageMapper {
         return setMessagesInMailboxReactive(mailbox, originals.stream()
             .map(original -> {
                 original.setFlags(new FlagsBuilder().add(original.createFlags()).add(Flag.RECENT).build());
+                original.setSaveDate(Date.from(clock.instant()));
                 return original;
             }).collect(ImmutableList.toImmutableList()))
             .collectList();
@@ -584,7 +590,7 @@ public class CassandraMessageMapper implements MessageMapper {
 
     private Mono<MessageMetaData> setInMailboxReactive(Mailbox mailbox, MailboxMessage message) {
         CassandraId mailboxId = (CassandraId) mailbox.getMailboxId();
-        return addUidAndModseq(message, mailboxId)
+        return addUidAndModseqAndSaveDate(message, mailboxId)
             .flatMap(messageWithUidAndModseq ->
                 insertMetadata(messageWithUidAndModseq, mailboxId,
                     CassandraMessageMetadata.from(messageWithUidAndModseq)
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 0e34163bf5..ebc9436315 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
@@ -203,6 +203,11 @@ public class CassandraMessageMetadata {
             delegate.setFlags(flags);
         }
 
+        @Override
+        public void setSaveDate(Date saveDate) {
+            delegate.setSaveDate(saveDate);
+        }
+
         @Override
         public Flags createFlags() {
             return delegate.createFlags();
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/MessageRepresentation.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/MessageRepresentation.java
index 39cada4b95..025e1b2bf9 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/MessageRepresentation.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/MessageRepresentation.java
@@ -21,6 +21,7 @@ package org.apache.james.mailbox.cassandra.mail;
 
 import java.util.Date;
 import java.util.List;
+import java.util.Optional;
 
 import org.apache.james.blob.api.BlobId;
 import org.apache.james.mailbox.model.ComposedMessageIdWithMetaData;
@@ -54,7 +55,7 @@ public class MessageRepresentation {
         this.bodyId = bodyId;
     }
 
-    public SimpleMailboxMessage toMailboxMessage(ComposedMessageIdWithMetaData metadata, List<MessageAttachmentMetadata> attachments) {
+    public SimpleMailboxMessage toMailboxMessage(ComposedMessageIdWithMetaData metadata, List<MessageAttachmentMetadata> attachments, Optional<Date> saveDate) {
         return SimpleMailboxMessage.builder()
             .messageId(messageId)
             .threadId(metadata.getThreadId())
@@ -62,6 +63,7 @@ public class MessageRepresentation {
             .uid(metadata.getComposedMessageId().getUid())
             .modseq(metadata.getModSeq())
             .internalDate(internalDate)
+            .saveDate(saveDate)
             .bodyStartOctet(bodyStartOctet)
             .size(size)
             .content(content)
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraSubscriptionManagerTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraSubscriptionManagerTest.java
index 2c6a90d691..e51dc31081 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraSubscriptionManagerTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraSubscriptionManagerTest.java
@@ -21,6 +21,8 @@ package org.apache.james.mailbox.cassandra;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
+import java.time.Clock;
+
 import org.apache.james.backends.cassandra.CassandraClusterExtension;
 import org.apache.james.backends.cassandra.components.CassandraModule;
 import org.apache.james.backends.cassandra.init.configuration.CassandraConfiguration;
@@ -136,7 +138,8 @@ class CassandraSubscriptionManagerTest implements SubscriptionManagerContract {
             userMailboxRightsDAO,
             recomputeMailboxCountersService,
             CassandraConfiguration.DEFAULT_CONFIGURATION,
-            BatchSizes.defaultValues());
+            BatchSizes.defaultValues(),
+            Clock.systemUTC());
 
         InVMEventBus eventBus = new InVMEventBus(new InVmEventDelivery(new RecordingMetricFactory()), EventBusTestFixture.RETRY_BACKOFF_CONFIGURATION, new MemoryEventDeadLetters());
 
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/TestCassandraMailboxSessionMapperFactory.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/TestCassandraMailboxSessionMapperFactory.java
index a1e3d7976f..a4cd1f124d 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/TestCassandraMailboxSessionMapperFactory.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/TestCassandraMailboxSessionMapperFactory.java
@@ -19,11 +19,14 @@
 
 package org.apache.james.mailbox.cassandra;
 
+import java.time.Clock;
+
 import org.apache.james.backends.cassandra.CassandraCluster;
 import org.apache.james.backends.cassandra.init.configuration.CassandraConfiguration;
 import org.apache.james.mailbox.cassandra.ids.CassandraMessageId;
 import org.apache.james.mailbox.cassandra.mail.utils.GuiceUtils;
 import org.apache.james.mailbox.store.BatchSizes;
+import org.apache.james.utils.UpdatableTickingClock;
 
 import com.google.inject.Guice;
 import com.google.inject.util.Modules;
@@ -42,6 +45,16 @@ public class TestCassandraMailboxSessionMapperFactory {
             .getInstance(CassandraMailboxSessionMapperFactory.class);
     }
 
+    public static CassandraMailboxSessionMapperFactory forTests(CassandraCluster cassandra,
+                                                                CassandraMessageId.Factory factory,
+                                                                CassandraConfiguration cassandraConfiguration,
+                                                                UpdatableTickingClock updatableTickingClock) {
+
+        return Guice.createInjector(Modules.override(GuiceUtils.commonModules(cassandra.getConf(), cassandra.getTypesProvider(), factory, cassandraConfiguration))
+                .with(binder -> binder.bind(Clock.class).toInstance(updatableTickingClock)))
+            .getInstance(CassandraMailboxSessionMapperFactory.class);
+    }
+
     public static CassandraMailboxSessionMapperFactory forTests(CassandraCluster cassandra,
                                                                 CassandraMessageId.Factory factory,
                                                                 CassandraConfiguration cassandraConfiguration,
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMapperProvider.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMapperProvider.java
index 145fb709b8..e3e7cd9f2b 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMapperProvider.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMapperProvider.java
@@ -18,6 +18,7 @@
  ****************************************************************/
 package org.apache.james.mailbox.cassandra.mail;
 
+import java.time.Instant;
 import java.util.List;
 
 import org.apache.james.backends.cassandra.CassandraCluster;
@@ -41,6 +42,7 @@ import org.apache.james.mailbox.store.mail.MessageIdMapper;
 import org.apache.james.mailbox.store.mail.MessageMapper;
 import org.apache.james.mailbox.store.mail.model.MapperProvider;
 import org.apache.james.mailbox.store.mail.model.MessageUidProvider;
+import org.apache.james.utils.UpdatableTickingClock;
 
 import com.google.common.collect.ImmutableList;
 
@@ -51,6 +53,7 @@ public class CassandraMapperProvider implements MapperProvider {
     private final CassandraCluster cassandra;
     private final MessageUidProvider messageUidProvider;
     private final CassandraModSeqProvider cassandraModSeqProvider;
+    private final UpdatableTickingClock updatableTickingClock;
     private final MailboxSession mailboxSession = MailboxSessionUtil.create(Username.of("benwa"));
     private CassandraMailboxSessionMapperFactory mapperFactory;
 
@@ -61,7 +64,8 @@ public class CassandraMapperProvider implements MapperProvider {
         cassandraModSeqProvider = new CassandraModSeqProvider(
                 this.cassandra.getConf(),
                 cassandraConfiguration);
-        mapperFactory = createMapperFactory(cassandraConfiguration);
+        updatableTickingClock = new UpdatableTickingClock(Instant.now());
+        mapperFactory = createMapperFactory(cassandraConfiguration, updatableTickingClock);
     }
 
     @Override
@@ -84,10 +88,11 @@ public class CassandraMapperProvider implements MapperProvider {
         return mapperFactory.getMessageIdMapper(mailboxSession);
     }
 
-    private CassandraMailboxSessionMapperFactory createMapperFactory(CassandraConfiguration cassandraConfiguration) {
+    private CassandraMailboxSessionMapperFactory createMapperFactory(CassandraConfiguration cassandraConfiguration, UpdatableTickingClock updatableTickingClock) {
         return TestCassandraMailboxSessionMapperFactory.forTests(cassandra,
             new CassandraMessageId.Factory(),
-            cassandraConfiguration);
+            cassandraConfiguration,
+            updatableTickingClock);
     }
 
     @Override
@@ -125,4 +130,7 @@ public class CassandraMapperProvider implements MapperProvider {
         return cassandraModSeqProvider.highestModSeq(mailbox);
     }
 
+    public UpdatableTickingClock getUpdatableTickingClock() {
+        return updatableTickingClock;
+    }
 }
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapperRelaxedConsistencyTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapperRelaxedConsistencyTest.java
index 5a00c78bc2..5380a66fa9 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapperRelaxedConsistencyTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapperRelaxedConsistencyTest.java
@@ -22,6 +22,7 @@ package org.apache.james.mailbox.cassandra.mail;
 import org.apache.james.backends.cassandra.CassandraClusterExtension;
 import org.apache.james.backends.cassandra.init.configuration.CassandraConfiguration;
 import org.apache.james.mailbox.store.mail.model.MessageIdMapperTest;
+import org.apache.james.utils.UpdatableTickingClock;
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.extension.RegisterExtension;
@@ -32,27 +33,41 @@ class CassandraMessageIdMapperRelaxedConsistencyTest {
 
     @Nested
     class WeakReadConsistency extends MessageIdMapperTest {
+        private final CassandraMapperProvider mapperProvider = new CassandraMapperProvider(
+            cassandraCluster.getCassandraCluster(),
+            CassandraConfiguration.builder()
+                .messageReadStrongConsistency(false)
+                .messageWriteStrongConsistency(true)
+                .build());
+
         @Override
         protected CassandraMapperProvider provideMapper() {
-            return new CassandraMapperProvider(
-                cassandraCluster.getCassandraCluster(),
-                CassandraConfiguration.builder()
-                    .messageReadStrongConsistency(false)
-                    .messageWriteStrongConsistency(true)
-                    .build());
+            return mapperProvider;
+        }
+
+        @Override
+        protected UpdatableTickingClock updatableTickingClock() {
+            return mapperProvider.getUpdatableTickingClock();
         }
     }
 
     @Nested
     class WeakWriteConsistency extends MessageIdMapperTest {
+        private final CassandraMapperProvider mapperProvider = new CassandraMapperProvider(
+            cassandraCluster.getCassandraCluster(),
+            CassandraConfiguration.builder()
+                .messageReadStrongConsistency(false)
+                .messageWriteStrongConsistency(false)
+                .build());
+
         @Override
         protected CassandraMapperProvider provideMapper() {
-            return new CassandraMapperProvider(
-                cassandraCluster.getCassandraCluster(),
-                CassandraConfiguration.builder()
-                    .messageReadStrongConsistency(false)
-                    .messageWriteStrongConsistency(false)
-                    .build());
+            return mapperProvider;
+        }
+
+        @Override
+        protected UpdatableTickingClock updatableTickingClock() {
+            return mapperProvider.getUpdatableTickingClock();
         }
 
         @Disabled("JAMES-3435 Without strong consistency flags update is not thread safe as long as it follows a read-before-write pattern")
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapperTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapperTest.java
index 958be20af9..4af4d7785b 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapperTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapperTest.java
@@ -47,6 +47,7 @@ import org.apache.james.mailbox.store.mail.MessageMapper;
 import org.apache.james.mailbox.store.mail.model.MailboxMessage;
 import org.apache.james.mailbox.store.mail.model.MessageIdMapperTest;
 import org.apache.james.util.streams.Limit;
+import org.apache.james.utils.UpdatableTickingClock;
 import org.assertj.core.api.SoftAssertions;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Tag;
@@ -61,12 +62,19 @@ class CassandraMessageIdMapperTest extends MessageIdMapperTest {
 
     @RegisterExtension
     static CassandraClusterExtension cassandraCluster = new CassandraClusterExtension(MailboxAggregateModule.MODULE);
-    
+
+    private final CassandraMapperProvider mapperProvider = new CassandraMapperProvider(
+        cassandraCluster.getCassandraCluster(),
+        CassandraConfiguration.DEFAULT_CONFIGURATION);
+
     @Override
     protected CassandraMapperProvider provideMapper() {
-        return new CassandraMapperProvider(
-            cassandraCluster.getCassandraCluster(),
-            CassandraConfiguration.DEFAULT_CONFIGURATION);
+        return mapperProvider;
+    }
+
+    @Override
+    protected UpdatableTickingClock updatableTickingClock() {
+        return mapperProvider.getUpdatableTickingClock();
     }
 
     @Test
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapperRelaxedConsistencyTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapperRelaxedConsistencyTest.java
index 4028772cec..f6d65f6c00 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapperRelaxedConsistencyTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapperRelaxedConsistencyTest.java
@@ -23,6 +23,7 @@ import org.apache.james.backends.cassandra.CassandraClusterExtension;
 import org.apache.james.backends.cassandra.init.configuration.CassandraConfiguration;
 import org.apache.james.mailbox.store.mail.model.MapperProvider;
 import org.apache.james.mailbox.store.mail.model.MessageMapperTest;
+import org.apache.james.utils.UpdatableTickingClock;
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.extension.RegisterExtension;
@@ -33,27 +34,41 @@ class CassandraMessageMapperRelaxedConsistencyTest {
 
     @Nested
     class WeakReadConsistency extends MessageMapperTest {
+        private final CassandraMapperProvider cassandraMapperProvider = new CassandraMapperProvider(
+            cassandraCluster.getCassandraCluster(),
+            CassandraConfiguration.builder()
+                .messageReadStrongConsistency(false)
+                .messageWriteStrongConsistency(true)
+                .build());
+
         @Override
         protected MapperProvider createMapperProvider() {
-            return new CassandraMapperProvider(
-                cassandraCluster.getCassandraCluster(),
-                CassandraConfiguration.builder()
-                    .messageReadStrongConsistency(false)
-                    .messageWriteStrongConsistency(true)
-                    .build());
+            return cassandraMapperProvider;
+        }
+
+        @Override
+        protected UpdatableTickingClock updatableTickingClock() {
+            return cassandraMapperProvider.getUpdatableTickingClock();
         }
     }
 
     @Nested
     class WeakWriteConsistency extends MessageMapperTest {
+        private final CassandraMapperProvider cassandraMapperProvider = new CassandraMapperProvider(
+            cassandraCluster.getCassandraCluster(),
+            CassandraConfiguration.builder()
+                .messageReadStrongConsistency(false)
+                .messageWriteStrongConsistency(false)
+                .build());
+
         @Override
         protected MapperProvider createMapperProvider() {
-            return new CassandraMapperProvider(
-                cassandraCluster.getCassandraCluster(),
-                CassandraConfiguration.builder()
-                    .messageReadStrongConsistency(false)
-                    .messageWriteStrongConsistency(false)
-                    .build());
+            return cassandraMapperProvider;
+        }
+
+        @Override
+        protected UpdatableTickingClock updatableTickingClock() {
+            return cassandraMapperProvider.getUpdatableTickingClock();
         }
 
         @Disabled("JAMES-3435 Without strong consistency flags update is not thread safe as long as it follows a read-before-write pattern")
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapperTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapperTest.java
index ec84ffb1fe..dfc02de3ac 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapperTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapperTest.java
@@ -46,6 +46,7 @@ import org.apache.james.mailbox.store.mail.model.MailboxMessage;
 import org.apache.james.mailbox.store.mail.model.MapperProvider;
 import org.apache.james.mailbox.store.mail.model.MessageMapperTest;
 import org.apache.james.util.streams.Limit;
+import org.apache.james.utils.UpdatableTickingClock;
 import org.assertj.core.api.SoftAssertions;
 import org.awaitility.Awaitility;
 import org.junit.jupiter.api.Nested;
@@ -58,12 +59,19 @@ import com.google.common.collect.ImmutableList;
 class CassandraMessageMapperTest extends MessageMapperTest {
     @RegisterExtension
     static CassandraClusterExtension cassandraCluster = new CassandraClusterExtension(MailboxAggregateModule.MODULE);
-    
+
+    private final CassandraMapperProvider cassandraMapperProvider = new CassandraMapperProvider(
+        cassandraCluster.getCassandraCluster(),
+        CassandraConfiguration.DEFAULT_CONFIGURATION);
+
     @Override
     protected MapperProvider createMapperProvider() {
-        return new CassandraMapperProvider(
-            cassandraCluster.getCassandraCluster(),
-            CassandraConfiguration.DEFAULT_CONFIGURATION);
+        return cassandraMapperProvider;
+    }
+
+    @Override
+    protected UpdatableTickingClock updatableTickingClock() {
+        return cassandraMapperProvider.getUpdatableTickingClock();
     }
 
     @Nested
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/utils/GuiceUtils.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/utils/GuiceUtils.java
index a6dd9b3773..ed06c2c280 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/utils/GuiceUtils.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/utils/GuiceUtils.java
@@ -19,6 +19,7 @@
 
 package org.apache.james.mailbox.cassandra.mail.utils;
 
+import java.time.Clock;
 import java.util.Set;
 
 import org.apache.james.backends.cassandra.CassandraCluster;
@@ -94,6 +95,7 @@ public class GuiceUtils {
             binder -> Multibinder.newSetBinder(binder, new TypeLiteral<EventDTOModule<? extends Event, ? extends EventDTO>>() {}),
             binder -> binder.bind(EventStore.class).to(CassandraEventStore.class),
             binder -> binder.bind(CassandraTypesProvider.class).toInstance(typesProvider),
-            binder -> binder.bind(CassandraConfiguration.class).toInstance(configuration));
+            binder -> binder.bind(CassandraConfiguration.class).toInstance(configuration),
+            binder -> binder.bind(Clock.class).toInstance(Clock.systemUTC()));
     }
 }
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 879171416d..d8c40546db 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
@@ -435,6 +435,11 @@ public abstract class AbstractJPAMailboxMessage implements MailboxMessage {
         this.uid = uid.asLong();
     }
 
+    @Override
+    public void setSaveDate(Date saveDate) {
+
+    }
+
     @Override
     public long getHeaderOctets() {
         return bodyStartOctet;
diff --git a/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/mail/JpaMessageMapperTest.java b/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/mail/JpaMessageMapperTest.java
index 1e5db354e0..6a9c7055dd 100644
--- a/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/mail/JpaMessageMapperTest.java
+++ b/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/mail/JpaMessageMapperTest.java
@@ -35,7 +35,10 @@ import org.apache.james.mailbox.model.UpdatedFlags;
 import org.apache.james.mailbox.store.FlagsUpdateCalculator;
 import org.apache.james.mailbox.store.mail.model.MapperProvider;
 import org.apache.james.mailbox.store.mail.model.MessageMapperTest;
+import org.apache.james.utils.UpdatableTickingClock;
 import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 
 class JpaMessageMapperTest extends MessageMapperTest {
@@ -46,7 +49,12 @@ class JpaMessageMapperTest extends MessageMapperTest {
     protected MapperProvider createMapperProvider() {
         return new JPAMapperProvider(JPA_TEST_CLUSTER);
     }
-    
+
+    @Override
+    protected UpdatableTickingClock updatableTickingClock() {
+        return null;
+    }
+
     @AfterEach
     void cleanUp() {
         JPA_TEST_CLUSTER.clear(JPAMailboxFixture.MAILBOX_TABLE_NAMES);
@@ -139,4 +147,10 @@ class JpaMessageMapperTest extends MessageMapperTest {
                     .newFlags(new Flags())
                     .build());
     }
+
+    @Nested
+    @Disabled("JPA does not support saveDate.")
+    class SaveDateTests {
+
+    }
 }
diff --git a/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMailboxSessionMapperFactory.java b/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMailboxSessionMapperFactory.java
index 42967eeccf..c9e384f3bb 100644
--- a/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMailboxSessionMapperFactory.java
+++ b/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMailboxSessionMapperFactory.java
@@ -18,6 +18,8 @@
  ****************************************************************/
 package org.apache.james.mailbox.inmemory;
 
+import java.time.Clock;
+
 import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.inmemory.mail.InMemoryAnnotationMapper;
@@ -49,11 +51,11 @@ public class InMemoryMailboxSessionMapperFactory extends MailboxSessionMapperFac
     private final InMemoryUidProvider uidProvider;
     private final InMemoryModSeqProvider modSeqProvider;
 
-    public InMemoryMailboxSessionMapperFactory() {
+    public InMemoryMailboxSessionMapperFactory(Clock clock) {
         mailboxMapper = new InMemoryMailboxMapper();
         uidProvider = new InMemoryUidProvider();
         modSeqProvider = new InMemoryModSeqProvider();
-        messageMapper = new InMemoryMessageMapper(null, uidProvider, modSeqProvider);
+        messageMapper = new InMemoryMessageMapper(null, uidProvider, modSeqProvider, clock);
         messageIdMapper = new InMemoryMessageIdMapper(mailboxMapper, messageMapper);
 
         subscriptionMapper = new InMemorySubscriptionMapper();
diff --git a/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryMessageMapper.java b/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryMessageMapper.java
index 3545e85b7c..5b6295e0c5 100644
--- a/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryMessageMapper.java
+++ b/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryMessageMapper.java
@@ -19,8 +19,10 @@
 
 package org.apache.james.mailbox.inmemory.mail;
 
+import java.time.Clock;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Date;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -54,8 +56,8 @@ public class InMemoryMessageMapper extends AbstractMessageMapper {
     private static final int INITIAL_SIZE = 256;
 
     public InMemoryMessageMapper(MailboxSession session, UidProvider uidProvider,
-            ModSeqProvider modSeqProvider) {
-        super(session, uidProvider, modSeqProvider);
+            ModSeqProvider modSeqProvider, Clock clock) {
+        super(session, uidProvider, modSeqProvider, clock);
         this.mailboxByUid = new ConcurrentHashMap<>(INITIAL_SIZE);
     }
 
@@ -200,6 +202,7 @@ public class InMemoryMessageMapper extends AbstractMessageMapper {
         SimpleMailboxMessage copy = SimpleMailboxMessage.copy(mailbox.getMailboxId(), message);
         copy.setUid(message.getUid());
         copy.setModSeq(message.getModSeq());
+        copy.setSaveDate(Date.from(clock.instant()));
         getMembershipByUidForMailbox(mailbox).put(message.getUid(), copy);
 
         return copy.metaData();
diff --git a/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/mail/InMemoryMapperProvider.java b/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/mail/InMemoryMapperProvider.java
index 8f60b26760..4667385719 100644
--- a/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/mail/InMemoryMapperProvider.java
+++ b/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/mail/InMemoryMapperProvider.java
@@ -19,6 +19,7 @@
 
 package org.apache.james.mailbox.inmemory.mail;
 
+import java.time.Instant;
 import java.util.List;
 import java.util.concurrent.ThreadLocalRandom;
 
@@ -39,6 +40,7 @@ import org.apache.james.mailbox.store.mail.MessageIdMapper;
 import org.apache.james.mailbox.store.mail.MessageMapper;
 import org.apache.james.mailbox.store.mail.model.MapperProvider;
 import org.apache.james.mailbox.store.mail.model.MessageUidProvider;
+import org.apache.james.utils.UpdatableTickingClock;
 
 import com.google.common.collect.ImmutableList;
 
@@ -50,12 +52,14 @@ public class InMemoryMapperProvider implements MapperProvider {
     private final MessageId.Factory messageIdFactory;
     private final MessageUidProvider messageUidProvider;
     private final InMemoryMailboxSessionMapperFactory inMemoryMailboxSessionMapperFactory;
+    private final UpdatableTickingClock clock;
 
 
     public InMemoryMapperProvider() {
         messageIdFactory = new InMemoryMessageId.Factory();
         messageUidProvider = new MessageUidProvider();
-        inMemoryMailboxSessionMapperFactory = new InMemoryMailboxSessionMapperFactory();
+        clock = new UpdatableTickingClock(Instant.now());
+        inMemoryMailboxSessionMapperFactory = new InMemoryMailboxSessionMapperFactory(clock);
     }
 
     @Override
@@ -124,4 +128,7 @@ public class InMemoryMapperProvider implements MapperProvider {
             .highestModSeq(mailbox);
     }
 
+    public UpdatableTickingClock getClock() {
+        return clock;
+    }
 }
diff --git a/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/mail/InMemoryMessageIdMapperTest.java b/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/mail/InMemoryMessageIdMapperTest.java
index faf70ab9f9..080f8b8915 100644
--- a/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/mail/InMemoryMessageIdMapperTest.java
+++ b/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/mail/InMemoryMessageIdMapperTest.java
@@ -21,11 +21,18 @@ package org.apache.james.mailbox.inmemory.mail;
 
 import org.apache.james.mailbox.store.mail.model.MapperProvider;
 import org.apache.james.mailbox.store.mail.model.MessageIdMapperTest;
+import org.apache.james.utils.UpdatableTickingClock;
 
 class InMemoryMessageIdMapperTest extends MessageIdMapperTest {
+    private final InMemoryMapperProvider mapperProvider = new InMemoryMapperProvider();
 
     @Override
     protected MapperProvider provideMapper() {
-        return new InMemoryMapperProvider();
+        return mapperProvider;
+    }
+
+    @Override
+    protected UpdatableTickingClock updatableTickingClock() {
+        return mapperProvider.getClock();
     }
 }
diff --git a/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/mail/MemoryMessageMapperTest.java b/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/mail/MemoryMessageMapperTest.java
index 77b3696081..8e0c618a69 100644
--- a/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/mail/MemoryMessageMapperTest.java
+++ b/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/mail/MemoryMessageMapperTest.java
@@ -21,11 +21,19 @@ package org.apache.james.mailbox.inmemory.mail;
 
 import org.apache.james.mailbox.store.mail.model.MapperProvider;
 import org.apache.james.mailbox.store.mail.model.MessageMapperTest;
+import org.apache.james.utils.UpdatableTickingClock;
 
 class MemoryMessageMapperTest extends MessageMapperTest {
+    private final InMemoryMapperProvider inMemoryMapperProvider = new InMemoryMapperProvider();
     
     @Override
     protected MapperProvider createMapperProvider() {
-        return new InMemoryMapperProvider();
+        return inMemoryMapperProvider;
     }
+
+    @Override
+    protected UpdatableTickingClock updatableTickingClock() {
+        return inMemoryMapperProvider.getClock();
+    }
+
 }
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 d6a5c06759..8a390fecfa 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
@@ -294,7 +294,8 @@ public class InMemoryIntegrationResources implements IntegrationResources<StoreM
             Preconditions.checkState(searchIndexFactory.isPresent());
             Preconditions.checkState(messageParser.isPresent());
 
-            InMemoryMailboxSessionMapperFactory mailboxSessionMapperFactory = new InMemoryMailboxSessionMapperFactory();
+            UpdatableTickingClock clock = new UpdatableTickingClock(Instant.now());
+            InMemoryMailboxSessionMapperFactory mailboxSessionMapperFactory = new InMemoryMailboxSessionMapperFactory(clock);
 
             EventBus eventBus = this.eventBus.get();
             StoreRightManager storeRightManager = new StoreRightManager(mailboxSessionMapperFactory, new UnionMailboxACLResolver(), eventBus);
@@ -314,7 +315,6 @@ 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);
diff --git a/mailbox/opensearch/src/test/java/org/apache/james/mailbox/opensearch/events/OpenSearchListeningMessageSearchIndexTest.java b/mailbox/opensearch/src/test/java/org/apache/james/mailbox/opensearch/events/OpenSearchListeningMessageSearchIndexTest.java
index 3070f59872..ce34fbe26b 100644
--- a/mailbox/opensearch/src/test/java/org/apache/james/mailbox/opensearch/events/OpenSearchListeningMessageSearchIndexTest.java
+++ b/mailbox/opensearch/src/test/java/org/apache/james/mailbox/opensearch/events/OpenSearchListeningMessageSearchIndexTest.java
@@ -26,6 +26,7 @@ import static org.awaitility.Durations.ONE_HUNDRED_MILLISECONDS;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
+import java.time.Instant;
 import java.time.ZoneId;
 import java.util.Date;
 
@@ -77,6 +78,7 @@ import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder;
 import org.apache.james.mailbox.store.mail.model.impl.SimpleMailboxMessage;
 import org.apache.james.mailbox.store.search.ListeningMessageSearchIndex;
 import org.apache.james.mailbox.store.search.ListeningMessageSearchIndexContract;
+import org.apache.james.utils.UpdatableTickingClock;
 import org.awaitility.Awaitility;
 import org.awaitility.Durations;
 import org.awaitility.core.ConditionFactory;
@@ -166,13 +168,15 @@ class OpenSearchListeningMessageSearchIndexTest {
     OpenSearchIndexer openSearchIndexer;
     OpenSearchSearcher openSearchSearcher;
     SessionProviderImpl sessionProvider;
+    UpdatableTickingClock clock;
 
     @RegisterExtension
     DockerOpenSearchExtension openSearch = new DockerOpenSearchExtension();
 
     @BeforeEach
     void setup() throws Exception {
-        mapperFactory = new InMemoryMailboxSessionMapperFactory();
+        clock = new UpdatableTickingClock(Instant.now());
+        mapperFactory = new InMemoryMailboxSessionMapperFactory(clock);
 
         MessageToOpenSearchJson messageToOpenSearchJson = new MessageToOpenSearchJson(
             new DefaultTextExtractor(),
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/AbstractMessageMapper.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/AbstractMessageMapper.java
index ad1d9adeed..96a5d09ebc 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/AbstractMessageMapper.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/AbstractMessageMapper.java
@@ -18,6 +18,7 @@
  ****************************************************************/
 package org.apache.james.mailbox.store.mail;
 
+import java.time.Clock;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
@@ -54,11 +55,13 @@ public abstract class AbstractMessageMapper extends TransactionalMapper implemen
     protected final MailboxSession mailboxSession;
     private final UidProvider uidProvider;
     private final ModSeqProvider modSeqProvider;
+    protected final Clock clock;
 
-    public AbstractMessageMapper(MailboxSession mailboxSession, UidProvider uidProvider, ModSeqProvider modSeqProvider) {
+    public AbstractMessageMapper(MailboxSession mailboxSession, UidProvider uidProvider, ModSeqProvider modSeqProvider, Clock clock) {
         this.mailboxSession = mailboxSession;
         this.uidProvider = uidProvider;
         this.modSeqProvider = modSeqProvider;
+        this.clock = clock;
     }
     
     @Override
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 d6e7f17b2b..249cece8dc 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
@@ -108,6 +108,8 @@ public interface MailboxMessage extends Message, Comparable<MailboxMessage> {
      */
     void setFlags(Flags flags);
 
+    void setSaveDate(Date saveDate);
+
     /**
      * Creates a new flags instance populated
      * with the current flag data.
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 e5caaa9bef..c5339ff690 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
@@ -208,7 +208,7 @@ public class SimpleMailboxMessage extends DelegatingMailboxMessage {
     private MessageUid uid;
     private final MailboxId mailboxId;
     private final ThreadId threadId;
-    private final Optional<Date> saveDate;
+    private Optional<Date> saveDate;
     private boolean answered;
     private boolean deleted;
     private boolean draft;
@@ -319,6 +319,11 @@ public class SimpleMailboxMessage extends DelegatingMailboxMessage {
         this.uid = uid;
     }
 
+    @Override
+    public void setSaveDate(Date saveDate) {
+        this.saveDate = Optional.of(saveDate);
+    }
+
     @Override
     public Optional<Date> getSaveDate() {
         return saveDate;
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageIdMapperTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageIdMapperTest.java
index cf66cdcd05..a1c2abd8fb 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageIdMapperTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageIdMapperTest.java
@@ -24,6 +24,7 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 import java.time.Duration;
+import java.time.temporal.ChronoUnit;
 import java.util.Date;
 import java.util.List;
 
@@ -52,9 +53,11 @@ import org.apache.james.mailbox.store.mail.MessageMapper.FetchType;
 import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder;
 import org.apache.james.mailbox.store.mail.model.impl.SimpleMailboxMessage;
 import org.apache.james.util.concurrency.ConcurrentTestRunner;
+import org.apache.james.utils.UpdatableTickingClock;
 import org.assertj.core.data.MapEntry;
 import org.junit.Assume;
 import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 
 import com.google.common.collect.ImmutableList;
@@ -84,6 +87,8 @@ public abstract class MessageIdMapperTest {
 
     protected abstract MapperProvider provideMapper();
 
+    protected abstract UpdatableTickingClock updatableTickingClock();
+
     @BeforeEach
     void setUp() throws MailboxException {
         this.mapperProvider = provideMapper();
@@ -984,6 +989,52 @@ public abstract class MessageIdMapperTest {
                 .build());
     }
 
+    @Nested
+    class SaveDateTests {
+        @Test
+        void saveMessagesShouldSetNewSaveDate() throws MailboxException {
+            message1.setUid(mapperProvider.generateMessageUid());
+            message1.setModSeq(mapperProvider.generateModSeq(benwaInboxMailbox));
+            message1.setFlags(new Flags(Flag.SEEN));
+
+            message2.setUid(mapperProvider.generateMessageUid());
+            message2.setModSeq(mapperProvider.generateModSeq(benwaInboxMailbox));
+
+            sut.save(message1);
+            updatableTickingClock().setInstant(updatableTickingClock().instant().plusSeconds(1000));
+            sut.save(message2);
+
+            MailboxMessage firstMessage = sut.find(ImmutableList.of(message1.getMessageId()), FetchType.METADATA).get(0);
+            MailboxMessage secondMessage = sut.find(ImmutableList.of(message2.getMessageId()), FetchType.METADATA).get(0);
+
+            assertThat(firstMessage.getSaveDate()).isNotEqualTo(secondMessage.getSaveDate());
+        }
+
+        @Test
+        void copyInMailboxReactiveShouldSetNewSaveDate() throws MailboxException, InterruptedException {
+            message1.setUid(mapperProvider.generateMessageUid());
+            message1.setModSeq(mapperProvider.generateModSeq(benwaInboxMailbox));
+            message1.setFlags(new Flags(Flag.SEEN));
+            sut.save(message1);
+
+            MailboxMessage copy = sut.find(ImmutableList.of(message1.getMessageId()), FetchType.METADATA).get(0)
+                .copy(benwaWorkMailbox);
+            copy.setUid(mapperProvider.generateMessageUid());
+            copy.setModSeq(mapperProvider.generateModSeq(benwaWorkMailbox));
+
+            updatableTickingClock().setInstant(updatableTickingClock().instant().plus(8, ChronoUnit.DAYS));
+
+            sut.copyInMailboxReactive(copy, benwaWorkMailbox).block();
+
+            List<MailboxMessage> messages = sut.find(ImmutableList.of(message1.getMessageId()), FetchType.METADATA);
+            MailboxMessage firstMessage = messages.get(0);
+            MailboxMessage secondMessage = messages.get(1);
+
+            assertThat(firstMessage.getSaveDate()).isNotEqualTo(secondMessage.getSaveDate());
+        }
+    }
+
+
     private Mailbox createMailbox(MailboxPath mailboxPath) throws MailboxException {
         return mailboxMapper.create(mailboxPath, UID_VALIDITY).block();
     }
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageMapperTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageMapperTest.java
index 5503e4e782..f2b478d744 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageMapperTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageMapperTest.java
@@ -42,6 +42,7 @@ import org.apache.james.mailbox.MessageUid;
 import org.apache.james.mailbox.ModSeq;
 import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.model.ByteContent;
+import org.apache.james.mailbox.model.Content;
 import org.apache.james.mailbox.model.Mailbox;
 import org.apache.james.mailbox.model.MailboxCounters;
 import org.apache.james.mailbox.model.MailboxPath;
@@ -59,8 +60,10 @@ import org.apache.james.mailbox.store.mail.model.MapperProvider.Capabilities;
 import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder;
 import org.apache.james.mailbox.store.mail.model.impl.SimpleMailboxMessage;
 import org.apache.james.util.concurrency.ConcurrentTestRunner;
+import org.apache.james.utils.UpdatableTickingClock;
 import org.junit.Assume;
 import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 
 import com.google.common.collect.ImmutableList;
@@ -94,6 +97,8 @@ public abstract class MessageMapperTest {
 
     protected abstract MapperProvider createMapperProvider();
 
+    protected abstract UpdatableTickingClock updatableTickingClock();
+
     @BeforeEach
     void setUp() throws Exception {
         this.mapperProvider = createMapperProvider();
@@ -1220,6 +1225,97 @@ public abstract class MessageMapperTest {
             .containsOnly(message1.getUid(), message5.getUid());
     }
 
+    @Nested
+    class SaveDateTests {
+        @Test
+        void addMessageShouldSetNewSaveDate() throws MailboxException {
+            MailboxMessage messageWithoutSaveDate = createMessage(Optional.empty());
+
+            MessageMetaData messageMetaData = messageMapper.add(benwaInboxMailbox, messageWithoutSaveDate);
+
+            assertThat(messageMetaData.getSaveDate()).isPresent();
+        }
+
+        @Test
+        void deleteMessageShouldReturnMetaDataContainsSaveDate() throws MailboxException {
+            MessageMetaData toBeDeletedMessage = messageMapper.add(benwaInboxMailbox, createMessage(Optional.empty()));
+
+            assertThat(messageMapper.deleteMessages(benwaInboxMailbox, List.of(toBeDeletedMessage.getUid()))
+                .values()
+                .stream()
+                .allMatch(messageMetaData -> messageMetaData.getSaveDate().equals(toBeDeletedMessage.getSaveDate())))
+                .isTrue();
+        }
+
+        @Test
+        void copyMessageShouldSetNewSaveDate() throws MailboxException {
+            MailboxMessage originalMessage = createMessage(Optional.of(new Date()));
+            MessageUid uid = messageMapper.add(benwaInboxMailbox, originalMessage).getUid();
+
+            updatableTickingClock().setInstant(updatableTickingClock().instant().plusSeconds(1000));
+
+            MessageMetaData copiedMessageMetaData = messageMapper.copy(benwaInboxMailbox,
+                messageMapper.findInMailbox(benwaInboxMailbox, MessageRange.one(uid), FetchType.METADATA, 1).next());
+
+            assertThat(copiedMessageMetaData.getSaveDate()).isNotEqualTo(originalMessage.getSaveDate());
+        }
+
+        @Test
+        void copyListOfMessagesShouldSetNewSaveDate() throws MailboxException {
+            MailboxMessage originalMessage = createMessage(Optional.of(new Date()));
+            MessageUid uid = messageMapper.add(benwaInboxMailbox, originalMessage).getUid();
+
+            updatableTickingClock().setInstant(updatableTickingClock().instant().plusSeconds(1000));
+
+            List<MessageMetaData> copiedMessageMetaData = messageMapper.copy(benwaInboxMailbox,
+                List.of(messageMapper.findInMailbox(benwaInboxMailbox, MessageRange.one(uid), FetchType.METADATA, 1).next()));
+
+            assertThat(copiedMessageMetaData.get(0).getSaveDate()).isNotEqualTo(originalMessage.getSaveDate());
+        }
+
+        @Test
+        void moveMessageShouldSetNewSaveDate() throws MailboxException {
+            MailboxMessage originalMessage = createMessage(Optional.of(new Date()));
+            MessageUid uid = messageMapper.add(benwaInboxMailbox, originalMessage).getUid();
+
+            updatableTickingClock().setInstant(updatableTickingClock().instant().plusSeconds(1000));
+
+            MessageMetaData movedMessageMetaData = messageMapper.move(benwaInboxMailbox,
+                messageMapper.findInMailbox(benwaInboxMailbox, MessageRange.one(uid), FetchType.METADATA, 1).next());
+
+            assertThat(movedMessageMetaData.getSaveDate()).isNotEqualTo(originalMessage.getSaveDate());
+        }
+
+        @Test
+        void moveListOfMessagesShouldSetNewSaveDate() throws MailboxException {
+            MailboxMessage originalMessage = createMessage(Optional.of(new Date()));
+            MessageUid uid = messageMapper.add(benwaInboxMailbox, originalMessage).getUid();
+
+            updatableTickingClock().setInstant(updatableTickingClock().instant().plusSeconds(1000));
+
+            List<MessageMetaData> movedMessageMetaData = messageMapper.move(benwaInboxMailbox,
+                List.of(messageMapper.findInMailbox(benwaInboxMailbox, MessageRange.one(uid), FetchType.METADATA, 1).next()));
+
+            assertThat(movedMessageMetaData.get(0).getSaveDate()).isNotEqualTo(originalMessage.getSaveDate());
+        }
+
+        private SimpleMailboxMessage createMessage(Optional<Date> saveDate) throws MailboxException {
+            Content content = new ByteContent("Subject: messagePropertiesShouldBeStoredWhenDuplicateEntries \n\nBody\n.\n".getBytes());
+            return SimpleMailboxMessage.builder()
+                .messageId(mapperProvider.generateMessageId())
+                .mailboxId(benwaInboxMailbox.getMailboxId())
+                .threadId(ThreadId.fromBaseMessageId(mapperProvider.generateMessageId()))
+                .internalDate(new Date())
+                .saveDate(saveDate)
+                .bodyStartOctet(16)
+                .size(content.size())
+                .content(content)
+                .flags(new Flags())
+                .properties(new PropertyBuilder())
+                .build();
+        }
+    }
+
     private List<MessageUid> markThenPerformRetrieveMessagesMarkedForDeletion(MessageRange range) throws MailboxException {
         messageMapper.updateFlags(benwaInboxMailbox, message1.getUid(), new FlagsUpdateCalculator(new Flags(Flags.Flag.DELETED), FlagsUpdateMode.REPLACE));
         messageMapper.updateFlags(benwaInboxMailbox, message4.getUid(), new FlagsUpdateCalculator(new Flags(Flags.Flag.DELETED), FlagsUpdateMode.REPLACE));


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


[james-project] 04/08: [FIX] MessageMetaData equals and hashcode should not just depend on uid

Posted by bt...@apache.org.
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 6e0656ab87160f50f2560df330746b50ff4edf55
Author: Quan Tran <hq...@linagora.com>
AuthorDate: Wed Nov 30 16:28:05 2022 +0700

    [FIX] MessageMetaData equals and hashcode should not just depend on uid
---
 .../james/mailbox/model/MessageMetaData.java       | 23 ++++++++++------
 .../james/mailbox/model/MessageMetaDataTest.java   | 31 ++++++++++++++++++++++
 2 files changed, 46 insertions(+), 8 deletions(-)

diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/model/MessageMetaData.java b/mailbox/api/src/main/java/org/apache/james/mailbox/model/MessageMetaData.java
index ed2e7be9a8..19fd0f7ed0 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/model/MessageMetaData.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/model/MessageMetaData.java
@@ -19,6 +19,7 @@
 package org.apache.james.mailbox.model;
 
 import java.util.Date;
+import java.util.Objects;
 import java.util.Optional;
 
 import javax.mail.Flags;
@@ -26,8 +27,6 @@ import javax.mail.Flags;
 import org.apache.james.mailbox.MessageUid;
 import org.apache.james.mailbox.ModSeq;
 
-import com.google.common.base.Objects;
-
 public class MessageMetaData {
     private final MessageUid uid;
     private final Flags flags;
@@ -95,16 +94,24 @@ public class MessageMetaData {
     }
 
     @Override
-    public boolean equals(Object obj) {
-        if (obj instanceof MessageMetaData) {
-            return uid.equals(((MessageMetaData) obj).getUid());
+    public final boolean equals(Object o) {
+        if (o instanceof MessageMetaData) {
+            MessageMetaData that = (MessageMetaData) o;
+
+            return Objects.equals(this.uid, that.uid)
+                && Objects.equals(this.size, that.size)
+                && Objects.equals(this.flags, that.flags)
+                && Objects.equals(this.internalDate, that.internalDate)
+                && Objects.equals(this.saveDate, that.saveDate)
+                && Objects.equals(this.modSeq, that.modSeq)
+                && Objects.equals(this.messageId, that.messageId)
+                && Objects.equals(this.threadId, that.threadId);
         }
         return false;
     }
 
     @Override
-    public int hashCode() {
-        return Objects.hashCode(uid);
+    public final int hashCode() {
+        return Objects.hash(uid, flags, size, internalDate, saveDate, modSeq, messageId, threadId);
     }
-
 }
diff --git a/mailbox/api/src/test/java/org/apache/james/mailbox/model/MessageMetaDataTest.java b/mailbox/api/src/test/java/org/apache/james/mailbox/model/MessageMetaDataTest.java
new file mode 100644
index 0000000000..4fbc779408
--- /dev/null
+++ b/mailbox/api/src/test/java/org/apache/james/mailbox/model/MessageMetaDataTest.java
@@ -0,0 +1,31 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mailbox.model;
+
+import org.junit.jupiter.api.Test;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+
+class MessageMetaDataTest {
+    @Test
+    void beanTest() {
+        EqualsVerifier.forClass(MessageMetaData.class).verify();
+    }
+}


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


[james-project] 08/08: JAMES-3754 Fix Guice binding

Posted by bt...@apache.org.
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 73ab92cf19bd149b9444fb8ffe6ee8ccb86c0884
Author: Quan Tran <hq...@linagora.com>
AuthorDate: Wed Dec 7 13:47:00 2022 +0700

    JAMES-3754 Fix Guice binding
    
    - Spring bean
    - Memory mapper factory injection
---
 mailbox/jpa/src/main/resources/META-INF/spring/mailbox-jpa.xml     | 2 ++
 .../mailbox/inmemory/InMemoryMailboxSessionMapperFactory.java      | 3 +++
 .../memory/src/main/resources/META-INF/spring/mailbox-memory.xml   | 7 ++++++-
 3 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/mailbox/jpa/src/main/resources/META-INF/spring/mailbox-jpa.xml b/mailbox/jpa/src/main/resources/META-INF/spring/mailbox-jpa.xml
index 044f1d3a98..e0fa9d0fd8 100644
--- a/mailbox/jpa/src/main/resources/META-INF/spring/mailbox-jpa.xml
+++ b/mailbox/jpa/src/main/resources/META-INF/spring/mailbox-jpa.xml
@@ -42,9 +42,11 @@
         <constructor-arg index="7" ref="quotaComponents" />
         <constructor-arg index="8" ref="indexer" />
         <constructor-arg index="9" ref="threadIdGuessingAlgorithm" />
+        <constructor-arg index="10" ref="clock" />
     </bean>
 
     <bean id="threadIdGuessingAlgorithm" class="org.apache.james.mailbox.store.mail.NaiveThreadIdGuessingAlgorithm" />
+    <bean id="clock" class="java.time.Clock" factory-method="systemUTC" />
 
     <bean id ="jpa-subscriptionManager" class="org.apache.james.mailbox.store.StoreSubscriptionManager">
         <constructor-arg index="0" ref="jpa-sessionMapperFactory"/>
diff --git a/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMailboxSessionMapperFactory.java b/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMailboxSessionMapperFactory.java
index c9e384f3bb..37da525571 100644
--- a/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMailboxSessionMapperFactory.java
+++ b/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMailboxSessionMapperFactory.java
@@ -20,6 +20,8 @@ package org.apache.james.mailbox.inmemory;
 
 import java.time.Clock;
 
+import javax.inject.Inject;
+
 import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.inmemory.mail.InMemoryAnnotationMapper;
@@ -51,6 +53,7 @@ public class InMemoryMailboxSessionMapperFactory extends MailboxSessionMapperFac
     private final InMemoryUidProvider uidProvider;
     private final InMemoryModSeqProvider modSeqProvider;
 
+    @Inject
     public InMemoryMailboxSessionMapperFactory(Clock clock) {
         mailboxMapper = new InMemoryMailboxMapper();
         uidProvider = new InMemoryUidProvider();
diff --git a/mailbox/memory/src/main/resources/META-INF/spring/mailbox-memory.xml b/mailbox/memory/src/main/resources/META-INF/spring/mailbox-memory.xml
index a4cf3f9053..9f68609c3b 100644
--- a/mailbox/memory/src/main/resources/META-INF/spring/mailbox-memory.xml
+++ b/mailbox/memory/src/main/resources/META-INF/spring/mailbox-memory.xml
@@ -44,6 +44,7 @@
         <constructor-arg index="9" ref="indexer" />
         <constructor-arg index="10" ref="preDeletionHooks" />
         <constructor-arg index="11" ref="threadIdGuessingAlgorithm" />
+        <constructor-arg index="12" ref="clock" />
     </bean>
 
     <bean id ="memory-subscriptionManager" class="org.apache.james.mailbox.store.StoreSubscriptionManager">
@@ -52,8 +53,12 @@
         <constructor-arg index="2" ref="event-bus"/>
     </bean>
 
-    <bean id="memory-sessionMapperFactory" class="org.apache.james.mailbox.inmemory.InMemoryMailboxSessionMapperFactory" />
+    <bean id="memory-sessionMapperFactory" class="org.apache.james.mailbox.inmemory.InMemoryMailboxSessionMapperFactory" >
+        <constructor-arg index="0" ref="clock"/>
+    </bean>
+
     <bean id="threadIdGuessingAlgorithm" class="org.apache.james.mailbox.store.mail.NaiveThreadIdGuessingAlgorithm"/>
+    <bean id="clock" class="java.time.Clock" factory-method="systemUTC" />
 
     <alias name="jvm-locker" alias="memory-locker"/>
 


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


[james-project] 03/08: JAMES-3754 Add saveDate field to MessageResult

Posted by bt...@apache.org.
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 574a3204e7265a8e465d82ae7922bc9f7f0c097b
Author: Quan Tran <hq...@linagora.com>
AuthorDate: Wed Nov 30 16:06:45 2022 +0700

    JAMES-3754 Add saveDate field to MessageResult
---
 .../custom/listeners/SetCustomFlagOnBigMessagesTest.java |  3 ++-
 .../org/apache/james/mailbox/model/MessageMetaData.java  |  9 ++++++++-
 .../org/apache/james/mailbox/model/MessageResult.java    |  3 +++
 .../test/java/org/apache/james/mailbox/EventTest.java    |  9 +++++----
 .../org/apache/james/mailbox/MailboxListenerTest.java    |  2 +-
 .../main/scala/org/apache/james/event/json/DTOs.scala    |  5 +++--
 .../apache/james/event/json/AddedSerializationTest.java  | 13 ++++++++++---
 .../james/event/json/ExpungedSerializationTest.java      | 16 +++++++++++-----
 .../apache/james/mailbox/store/MessageResultImpl.java    |  6 ++++++
 .../james/mailbox/store/StoreMessageResultIterator.java  |  6 ++++++
 .../james/mailbox/store/mail/model/MailboxMessage.java   |  2 +-
 .../apache/james/mailbox/store/PreDeletionHooksTest.java |  3 ++-
 .../mailbox/store/mail/model/MetadataMapAssertTest.java  |  9 +++++----
 .../store/quota/ListeningCurrentQuotaUpdaterTest.java    |  8 ++++----
 .../imap/processor/base/MailboxEventAnalyserTest.java    |  3 ++-
 .../imap/processor/base/SelectedMailboxImplTest.java     |  3 ++-
 16 files changed, 71 insertions(+), 29 deletions(-)

diff --git a/examples/custom-listeners/src/test/java/org/apache/james/examples/custom/listeners/SetCustomFlagOnBigMessagesTest.java b/examples/custom-listeners/src/test/java/org/apache/james/examples/custom/listeners/SetCustomFlagOnBigMessagesTest.java
index 04a0f84e37..bc1aba81c4 100644
--- a/examples/custom-listeners/src/test/java/org/apache/james/examples/custom/listeners/SetCustomFlagOnBigMessagesTest.java
+++ b/examples/custom-listeners/src/test/java/org/apache/james/examples/custom/listeners/SetCustomFlagOnBigMessagesTest.java
@@ -25,6 +25,7 @@ import static org.apache.james.mailbox.events.MailboxEvents.Added.IS_DELIVERY;
 import static org.assertj.core.api.Assertions.assertThat;
 
 import java.nio.charset.StandardCharsets;
+import java.util.Optional;
 import java.util.stream.Stream;
 
 import javax.mail.Flags;
@@ -129,7 +130,7 @@ class SetCustomFlagOnBigMessagesTest {
             .getMessages(MessageRange.one(composedIdOfSmallMessage.getUid()), FetchGroup.MINIMAL, mailboxSession)
             .next();
         MessageMetaData oneMBMetaData = new MessageMetaData(addedMessage.getUid(), addedMessage.getModSeq(),
-            addedMessage.getFlags(), ONE_MB, addedMessage.getInternalDate(), addedMessage.getMessageId(), addedMessage.getThreadId());
+            addedMessage.getFlags(), ONE_MB, addedMessage.getInternalDate(), Optional.empty(), addedMessage.getMessageId(), addedMessage.getThreadId());
 
         Event eventWithAFakeMessageSize = EventFactory.added()
             .eventId(RANDOM_EVENT_ID)
diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/model/MessageMetaData.java b/mailbox/api/src/main/java/org/apache/james/mailbox/model/MessageMetaData.java
index de6fe30802..ed2e7be9a8 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/model/MessageMetaData.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/model/MessageMetaData.java
@@ -19,6 +19,7 @@
 package org.apache.james.mailbox.model;
 
 import java.util.Date;
+import java.util.Optional;
 
 import javax.mail.Flags;
 
@@ -32,16 +33,18 @@ public class MessageMetaData {
     private final Flags flags;
     private final long size;
     private final Date internalDate;
+    private final Optional<Date> saveDate;
     private final ModSeq modSeq;
     private final MessageId messageId;
     private final ThreadId threadId;
 
-    public MessageMetaData(MessageUid uid, ModSeq modSeq, Flags flags, long size, Date internalDate, MessageId messageId, ThreadId threadId) {
+    public MessageMetaData(MessageUid uid, ModSeq modSeq, Flags flags, long size, Date internalDate, Optional<Date> saveDate, MessageId messageId, ThreadId threadId) {
         this.uid = uid;
         this.flags = flags;
         this.size = size;
         this.modSeq = modSeq;
         this.internalDate = internalDate;
+        this.saveDate = saveDate;
         this.messageId = messageId;
         this.threadId = threadId;
     }
@@ -67,6 +70,10 @@ public class MessageMetaData {
         return internalDate;
     }
 
+    public Optional<Date> getSaveDate() {
+        return saveDate;
+    }
+
     public MessageUid getUid() {
         return uid;
     }
diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/model/MessageResult.java b/mailbox/api/src/main/java/org/apache/james/mailbox/model/MessageResult.java
index 331ddf6dc5..a4d84958d5 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/model/MessageResult.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/model/MessageResult.java
@@ -23,6 +23,7 @@ import java.io.IOException;
 import java.util.Date;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Optional;
 
 import javax.mail.Flags;
 
@@ -64,6 +65,8 @@ public interface MessageResult extends Comparable<MessageResult> {
 
     ThreadId getThreadId();
 
+    Optional<Date> getSaveDate();
+
     Date getInternalDate();
 
     Flags getFlags();
diff --git a/mailbox/api/src/test/java/org/apache/james/mailbox/EventTest.java b/mailbox/api/src/test/java/org/apache/james/mailbox/EventTest.java
index c084becb09..14d6d89e36 100644
--- a/mailbox/api/src/test/java/org/apache/james/mailbox/EventTest.java
+++ b/mailbox/api/src/test/java/org/apache/james/mailbox/EventTest.java
@@ -23,6 +23,7 @@ import static org.apache.james.mailbox.events.MailboxEvents.Added.IS_DELIVERY;
 import static org.assertj.core.api.Assertions.assertThat;
 
 import java.util.Date;
+import java.util.Optional;
 import java.util.UUID;
 
 import javax.mail.Flags;
@@ -69,8 +70,8 @@ class EventTest {
         MessageUid uid2 = MessageUid.of(37);
         TestMessageId messageId1 = TestMessageId.of(45);
         TestMessageId messageId2 = TestMessageId.of(46);
-        MessageMetaData metaData1 = new MessageMetaData(uid1, ModSeq.of(85), new Flags(), 36, new Date(), messageId1, ThreadId.fromBaseMessageId(messageId1));
-        MessageMetaData metaData2 = new MessageMetaData(uid2, ModSeq.of(85), new Flags(), 36, new Date(), messageId2, ThreadId.fromBaseMessageId(messageId2));
+        MessageMetaData metaData1 = new MessageMetaData(uid1, ModSeq.of(85), new Flags(), 36, new Date(), Optional.empty(), messageId1, ThreadId.fromBaseMessageId(messageId1));
+        MessageMetaData metaData2 = new MessageMetaData(uid2, ModSeq.of(85), new Flags(), 36, new Date(), Optional.empty(), messageId2, ThreadId.fromBaseMessageId(messageId2));
 
         Added added = new Added(MailboxSession.SessionId.of(36), BOB, MailboxPath.inbox(BOB), TestId.of(48),
             ImmutableSortedMap.of(
@@ -86,8 +87,8 @@ class EventTest {
         MessageUid uid1 = MessageUid.of(36);
         MessageUid uid2 = MessageUid.of(37);
         TestMessageId messageId = TestMessageId.of(45);
-        MessageMetaData metaData1 = new MessageMetaData(uid1, ModSeq.of(85), new Flags(), 36, new Date(), messageId, ThreadId.fromBaseMessageId(messageId));
-        MessageMetaData metaData2 = new MessageMetaData(uid2, ModSeq.of(85), new Flags(), 36, new Date(), messageId, ThreadId.fromBaseMessageId(messageId));
+        MessageMetaData metaData1 = new MessageMetaData(uid1, ModSeq.of(85), new Flags(), 36, new Date(), Optional.empty(), messageId, ThreadId.fromBaseMessageId(messageId));
+        MessageMetaData metaData2 = new MessageMetaData(uid2, ModSeq.of(85), new Flags(), 36, new Date(), Optional.empty(), messageId, ThreadId.fromBaseMessageId(messageId));
 
         Added added = new Added(MailboxSession.SessionId.of(36), BOB, MailboxPath.inbox(BOB), TestId.of(48),
             ImmutableSortedMap.of(
diff --git a/mailbox/api/src/test/java/org/apache/james/mailbox/MailboxListenerTest.java b/mailbox/api/src/test/java/org/apache/james/mailbox/MailboxListenerTest.java
index af96c1cdca..236f1d5e9a 100644
--- a/mailbox/api/src/test/java/org/apache/james/mailbox/MailboxListenerTest.java
+++ b/mailbox/api/src/test/java/org/apache/james/mailbox/MailboxListenerTest.java
@@ -74,7 +74,7 @@ class MailboxListenerTest {
     private static final MailboxACL ACL_2 = new MailboxACL(
         Pair.of(MailboxACL.EntryKey.createUserEntryKey(Username.of("Bob")), new MailboxACL.Rfc4314Rights(MailboxACL.Right.Read)));
     private static final MessageUid UID = MessageUid.of(85);
-    private static final MessageMetaData META_DATA = new MessageMetaData(UID, ModSeq.of(45), new Flags(), 45, new Date(), TestMessageId.of(75), ThreadId.fromBaseMessageId(TestMessageId.of(75)));
+    private static final MessageMetaData META_DATA = new MessageMetaData(UID, ModSeq.of(45), new Flags(), 45, new Date(), Optional.of(new Date()), TestMessageId.of(75), ThreadId.fromBaseMessageId(TestMessageId.of(75)));
 
     @Test
     void mailboxAddedShouldMatchBeanContract() {
diff --git a/mailbox/event/json/src/main/scala/org/apache/james/event/json/DTOs.scala b/mailbox/event/json/src/main/scala/org/apache/james/event/json/DTOs.scala
index 33efeac146..6c0afb3c04 100644
--- a/mailbox/event/json/src/main/scala/org/apache/james/event/json/DTOs.scala
+++ b/mailbox/event/json/src/main/scala/org/apache/james/event/json/DTOs.scala
@@ -90,12 +90,13 @@ object DTOs {
       Flags.fromJavaFlags(javaMessageMetaData.getFlags),
       javaMessageMetaData.getSize,
       javaMessageMetaData.getInternalDate.toInstant,
+      javaMessageMetaData.getSaveDate.map(_.toInstant).toScala,
       javaMessageMetaData.getMessageId,
       Option(javaMessageMetaData.getThreadId))
   }
 
-  case class MessageMetaData(uid: MessageUid, modSeq: ModSeq, flags: Flags, size: Long, internalDate: Instant, messageId: MessageId, threadId: Option[ThreadId]) {
-    def toJava: JavaMessageMetaData = new JavaMessageMetaData(uid, modSeq, Flags.toJavaFlags(flags), size, Date.from(internalDate), messageId, retrieveThreadId)
+  case class MessageMetaData(uid: MessageUid, modSeq: ModSeq, flags: Flags, size: Long, internalDate: Instant, saveDate: Option[Instant], messageId: MessageId, threadId: Option[ThreadId]) {
+    def toJava: JavaMessageMetaData = new JavaMessageMetaData(uid, modSeq, Flags.toJavaFlags(flags), size, Date.from(internalDate), saveDate.map(Date.from).toJava, messageId, retrieveThreadId)
     def retrieveThreadId: ThreadId = threadId.getOrElse(ThreadId.fromBaseMessageId(messageId))
   }
 
diff --git a/mailbox/event/json/src/test/java/org/apache/james/event/json/AddedSerializationTest.java b/mailbox/event/json/src/test/java/org/apache/james/event/json/AddedSerializationTest.java
index 40df84bb4c..8af651840b 100644
--- a/mailbox/event/json/src/test/java/org/apache/james/event/json/AddedSerializationTest.java
+++ b/mailbox/event/json/src/test/java/org/apache/james/event/json/AddedSerializationTest.java
@@ -23,12 +23,14 @@ import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
 import static org.apache.james.event.json.SerializerFixture.EVENT_ID;
 import static org.apache.james.event.json.SerializerFixture.EVENT_SERIALIZER;
 import static org.apache.james.mailbox.events.MailboxEvents.Added.IS_DELIVERY;
+import static org.apache.james.mailbox.store.mail.model.MailboxMessage.EMPTY_SAVE_DATE;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 import java.time.Instant;
 import java.util.Date;
 import java.util.NoSuchElementException;
+import java.util.Optional;
 import java.util.SortedMap;
 
 import javax.mail.Flags;
@@ -68,11 +70,14 @@ class AddedSerializationTest {
         .add("User Custom Flag")
         .build();
     private static final SortedMap<MessageUid, MessageMetaData> ADDED = ImmutableSortedMap.of(
-        MESSAGE_UID, new MessageMetaData(MESSAGE_UID, MOD_SEQ, FLAGS, SIZE, Date.from(INSTANT), MESSAGE_ID, ThreadId.fromBaseMessageId(MESSAGE_ID)));
+        MESSAGE_UID, new MessageMetaData(MESSAGE_UID, MOD_SEQ, FLAGS, SIZE, Date.from(INSTANT), Optional.of(Date.from(INSTANT)), MESSAGE_ID, ThreadId.fromBaseMessageId(MESSAGE_ID)));
+    private static final SortedMap<MessageUid, MessageMetaData> BACKWARD_ADDED = ImmutableSortedMap.of(
+        MESSAGE_UID, new MessageMetaData(MESSAGE_UID, MOD_SEQ, FLAGS, SIZE, Date.from(INSTANT), EMPTY_SAVE_DATE, MESSAGE_ID, ThreadId.fromBaseMessageId(MESSAGE_ID)));
     private static final SortedMap<MessageUid, MessageMetaData> ADDED_WITH_DISTINCT_MESSAGE_ID_AND_THREAD_ID = ImmutableSortedMap.of(
-        MESSAGE_UID, new MessageMetaData(MESSAGE_UID, MOD_SEQ, FLAGS, SIZE, Date.from(INSTANT), MESSAGE_ID, ThreadId.fromBaseMessageId(TestMessageId.of(100))));
+        MESSAGE_UID, new MessageMetaData(MESSAGE_UID, MOD_SEQ, FLAGS, SIZE, Date.from(INSTANT), Optional.of(Date.from(INSTANT)), MESSAGE_ID, ThreadId.fromBaseMessageId(TestMessageId.of(100))));
 
     private static final Added DEFAULT_ADDED_EVENT = new Added(SESSION_ID, USERNAME, MAILBOX_PATH, MAILBOX_ID, ADDED, EVENT_ID, !IS_DELIVERY);
+    private static final Added BACKWARD_ADDED_EVENT = new Added(SESSION_ID, USERNAME, MAILBOX_PATH, MAILBOX_ID, BACKWARD_ADDED, EVENT_ID, !IS_DELIVERY);
     private static final Added ADDED_WITH_DISTINCT_MESSAGE_ID_AND_THREAD_ID_EVENT = new Added(
         SESSION_ID, USERNAME, MAILBOX_PATH, MAILBOX_ID, ADDED_WITH_DISTINCT_MESSAGE_ID_AND_THREAD_ID, EVENT_ID, !IS_DELIVERY);
     private static final String DEFAULT_ADDED_EVENT_JSON = 
@@ -94,6 +99,7 @@ class AddedSerializationTest {
         "          \"userFlags\":[\"User Custom Flag\"]}," +
         "        \"size\": 45,  " +
         "        \"internalDate\": \"2018-12-14T09:41:51.541Z\"," +
+        "        \"saveDate\": \"2018-12-14T09:41:51.541Z\"," +
         "        \"messageId\": \"42\"," +
         "        \"threadId\":\"42\""   +
         "      }" +
@@ -122,6 +128,7 @@ class AddedSerializationTest {
             "          \"userFlags\":[\"User Custom Flag\"]}," +
             "        \"size\": 45,  " +
             "        \"internalDate\": \"2018-12-14T09:41:51.541Z\"," +
+            "        \"saveDate\": \"2018-12-14T09:41:51.541Z\"," +
             "        \"messageId\": \"42\"," +
             "        \"threadId\":\"100\""   +
             "      }" +
@@ -179,7 +186,7 @@ class AddedSerializationTest {
     @Test
     void previousAddedFormatShouldBeWellDeserialized() {
         assertThat(EVENT_SERIALIZER.fromJson(DEFAULT_BACKWARD_ADDED_EVENT_JSON).get())
-            .isEqualTo(DEFAULT_ADDED_EVENT);
+            .isEqualTo(BACKWARD_ADDED_EVENT);
     }
 
     @Nested
diff --git a/mailbox/event/json/src/test/java/org/apache/james/event/json/ExpungedSerializationTest.java b/mailbox/event/json/src/test/java/org/apache/james/event/json/ExpungedSerializationTest.java
index 36c0688444..4a98466031 100644
--- a/mailbox/event/json/src/test/java/org/apache/james/event/json/ExpungedSerializationTest.java
+++ b/mailbox/event/json/src/test/java/org/apache/james/event/json/ExpungedSerializationTest.java
@@ -22,6 +22,7 @@ package org.apache.james.event.json;
 import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
 import static org.apache.james.event.json.SerializerFixture.EVENT_ID;
 import static org.apache.james.event.json.SerializerFixture.EVENT_SERIALIZER;
+import static org.apache.james.mailbox.store.mail.model.MailboxMessage.EMPTY_SAVE_DATE;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
@@ -29,6 +30,7 @@ import java.time.Instant;
 import java.util.Date;
 import java.util.Map;
 import java.util.NoSuchElementException;
+import java.util.Optional;
 
 import javax.mail.Flags;
 
@@ -67,12 +69,14 @@ class ExpungedSerializationTest {
         .add("User Custom Flag")
         .build();
     private static final Map<MessageUid, MessageMetaData> EXPUNGED = ImmutableMap.of(
-        MESSAGE_UID, new MessageMetaData(MESSAGE_UID, MOD_SEQ, FLAGS, SIZE, Date.from(INSTANT), MESSAGE_ID, ThreadId.fromBaseMessageId(MESSAGE_ID)));
+        MESSAGE_UID, new MessageMetaData(MESSAGE_UID, MOD_SEQ, FLAGS, SIZE, Date.from(INSTANT), Optional.of(Date.from(INSTANT)), MESSAGE_ID, ThreadId.fromBaseMessageId(MESSAGE_ID)));
+    private static final Map<MessageUid, MessageMetaData> BACKWARD_EXPUNGED = ImmutableMap.of(
+        MESSAGE_UID, new MessageMetaData(MESSAGE_UID, MOD_SEQ, FLAGS, SIZE, Date.from(INSTANT), EMPTY_SAVE_DATE, MESSAGE_ID, ThreadId.fromBaseMessageId(MESSAGE_ID)));
     private static final Map<MessageUid, MessageMetaData> EXPUNGED_WITH_DISTINCT_MESSAGE_ID_AND_THREAD_ID = ImmutableMap.of(
-        MESSAGE_UID, new MessageMetaData(MESSAGE_UID, MOD_SEQ, FLAGS, SIZE, Date.from(INSTANT), MESSAGE_ID, ThreadId.fromBaseMessageId(TestMessageId.of(100))));
+        MESSAGE_UID, new MessageMetaData(MESSAGE_UID, MOD_SEQ, FLAGS, SIZE, Date.from(INSTANT), Optional.of(Date.from(INSTANT)), MESSAGE_ID, ThreadId.fromBaseMessageId(TestMessageId.of(100))));
 
-    private static final Expunged DEFAULT_EXPUNGED_EVENT = new Expunged(SESSION_ID, USERNAME,
-        MAILBOX_PATH, MAILBOX_ID, EXPUNGED, EVENT_ID);
+    private static final Expunged DEFAULT_EXPUNGED_EVENT = new Expunged(SESSION_ID, USERNAME, MAILBOX_PATH, MAILBOX_ID, EXPUNGED, EVENT_ID);
+    private static final Expunged BACKWARD_EXPUNGED_EVENT = new Expunged(SESSION_ID, USERNAME, MAILBOX_PATH, MAILBOX_ID, BACKWARD_EXPUNGED, EVENT_ID);
     private static final Expunged EXPUNGED_WITH_DISTINCT_MESSAGE_ID_AND_THREAD_ID_EVENT = new Expunged(
         SESSION_ID, USERNAME, MAILBOX_PATH, MAILBOX_ID, EXPUNGED_WITH_DISTINCT_MESSAGE_ID_AND_THREAD_ID, EVENT_ID);
     private static final String DEFAULT_EXPUNGED_EVENT_JSON =
@@ -94,6 +98,7 @@ class ExpungedSerializationTest {
         "          \"userFlags\":[\"User Custom Flag\"]}," +
         "        \"size\": 45,  " +
         "        \"internalDate\": \"2018-12-14T09:41:51.541Z\"," +
+        "        \"saveDate\": \"2018-12-14T09:41:51.541Z\"," +
         "        \"messageId\": \"42\"," +
         "        \"threadId\": \"42\"" +
         "      }" +
@@ -121,6 +126,7 @@ class ExpungedSerializationTest {
             "          \"userFlags\":[\"User Custom Flag\"]}," +
             "        \"size\": 45,  " +
             "        \"internalDate\": \"2018-12-14T09:41:51.541Z\"," +
+            "        \"saveDate\": \"2018-12-14T09:41:51.541Z\"," +
             "        \"messageId\": \"42\"," +
             "        \"threadId\": \"100\"" +
             "      }" +
@@ -177,7 +183,7 @@ class ExpungedSerializationTest {
     @Test
     void previousExpungedFormatShouldBeWellDeserialized() {
         assertThat(EVENT_SERIALIZER.fromJson(DEFAULT_BACKWARD_EXPUNGED_EVENT_JSON).get())
-            .isEqualTo(DEFAULT_EXPUNGED_EVENT);
+            .isEqualTo(BACKWARD_EXPUNGED_EVENT);
     }
 
     @Nested
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageResultImpl.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageResultImpl.java
index 73866e4a20..541e0450d2 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageResultImpl.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageResultImpl.java
@@ -27,6 +27,7 @@ import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 
 import javax.mail.Flags;
 
@@ -96,6 +97,11 @@ public class MessageResultImpl implements MessageResult {
         return message.getThreadId();
     }
 
+    @Override
+    public Optional<Date> getSaveDate() {
+        return message.getSaveDate();
+    }
+
     @Override
     public Date getInternalDate() {
         return message.getInternalDate();
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageResultIterator.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageResultIterator.java
index ec9d68b755..b58f83a756 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageResultIterator.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageResultIterator.java
@@ -25,6 +25,7 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.NoSuchElementException;
 import java.util.Objects;
+import java.util.Optional;
 
 import javax.mail.Flags;
 
@@ -183,6 +184,11 @@ public class StoreMessageResultIterator implements MessageResultIterator {
             return messageMetaData.getThreadId();
         }
 
+        @Override
+        public Optional<Date> getSaveDate() {
+            return messageMetaData.getSaveDate();
+        }
+
         @Override
         public Date getInternalDate() {
             return messageMetaData().getInternalDate();
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 6bfd3efa1f..d6e7f17b2b 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
@@ -117,7 +117,7 @@ public interface MailboxMessage extends Message, Comparable<MailboxMessage> {
     Flags createFlags();
 
     default MessageMetaData metaData() {
-        return new MessageMetaData(getUid(), getModSeq(), createFlags(), getFullContentOctets(), getInternalDate(), getMessageId(), getThreadId());
+        return new MessageMetaData(getUid(), getModSeq(), createFlags(), getFullContentOctets(), getInternalDate(), getSaveDate(), getMessageId(), getThreadId());
     }
 
     default int compareTo(MailboxMessage other) {
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/PreDeletionHooksTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/PreDeletionHooksTest.java
index 3882a8cc83..62675e2901 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/PreDeletionHooksTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/PreDeletionHooksTest.java
@@ -31,6 +31,7 @@ import static org.mockito.Mockito.when;
 
 import java.time.Duration;
 import java.util.Date;
+import java.util.Optional;
 import java.util.concurrent.locks.ReentrantLock;
 
 import javax.mail.Flags;
@@ -59,7 +60,7 @@ class PreDeletionHooksTest {
     private static final TestId MAILBOX_ID = TestId.of(45);
     private static final ModSeq MOD_SEQ = ModSeq.of(18);
     private static final int SIZE = 12;
-    private static final MessageMetaData MESSAGE_META_DATA = new MessageMetaData(MessageUid.of(1), MOD_SEQ, new Flags(), SIZE, new Date(), TestMessageId.of(42), ThreadId.fromBaseMessageId(TestMessageId.of(42)));
+    private static final MessageMetaData MESSAGE_META_DATA = new MessageMetaData(MessageUid.of(1), MOD_SEQ, new Flags(), SIZE, new Date(), Optional.empty(), TestMessageId.of(42), ThreadId.fromBaseMessageId(TestMessageId.of(42)));
     private static final PreDeletionHook.DeleteOperation DELETE_OPERATION = PreDeletionHook.DeleteOperation.from(ImmutableList.of(MetadataWithMailboxId.from(
         MESSAGE_META_DATA,
         MAILBOX_ID)));
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MetadataMapAssertTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MetadataMapAssertTest.java
index 6fe06f6867..b774698262 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MetadataMapAssertTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MetadataMapAssertTest.java
@@ -24,6 +24,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Optional;
 
 import javax.mail.Flags;
 
@@ -63,7 +64,7 @@ class MetadataMapAssertTest {
     @Test
     void metadataMapAssertShouldSucceedWhenContainingRightMetadata() {
         Map<MessageUid, MessageMetaData> metaDataMap = new HashMap<>();
-        metaDataMap.put(UID, new MessageMetaData(UID, MODSEQ, new Flags(), HEADER_STRING.length() + BODY_STRING.length(), DATE, MESSAGE_ID, ThreadId.fromBaseMessageId(MESSAGE_ID)));
+        metaDataMap.put(UID, new MessageMetaData(UID, MODSEQ, new Flags(), HEADER_STRING.length() + BODY_STRING.length(), DATE, Optional.empty(), MESSAGE_ID, ThreadId.fromBaseMessageId(MESSAGE_ID)));
         
         MetadataMapAssert.assertThat(metaDataMap).containsMetadataForMessages(message1);
     }
@@ -71,7 +72,7 @@ class MetadataMapAssertTest {
     @Test
     void metadataMapAssertShouldFailWhenUidMismatch() {
         Map<MessageUid, MessageMetaData> metaDataMap = new HashMap<>();
-        metaDataMap.put(UID, new MessageMetaData(UID.next(), MODSEQ, new Flags(), HEADER_STRING.length() + BODY_STRING.length(), DATE, MESSAGE_ID, ThreadId.fromBaseMessageId(MESSAGE_ID)));
+        metaDataMap.put(UID, new MessageMetaData(UID.next(), MODSEQ, new Flags(), HEADER_STRING.length() + BODY_STRING.length(), DATE, Optional.empty(), MESSAGE_ID, ThreadId.fromBaseMessageId(MESSAGE_ID)));
         
         assertThatThrownBy(() -> MetadataMapAssert.assertThat(metaDataMap).containsMetadataForMessages(message1))
             .isInstanceOf(AssertionError.class);
@@ -82,7 +83,7 @@ class MetadataMapAssertTest {
         Map<MessageUid, MessageMetaData> metaDataMap = new HashMap<>();
         Date date = new Date();
         date.setTime(DATE.getTime() + 100L);
-        metaDataMap.put(UID, new MessageMetaData(UID, MODSEQ, new Flags(), HEADER_STRING.length() + BODY_STRING.length(), date, MESSAGE_ID, ThreadId.fromBaseMessageId(MESSAGE_ID)));
+        metaDataMap.put(UID, new MessageMetaData(UID, MODSEQ, new Flags(), HEADER_STRING.length() + BODY_STRING.length(), date, Optional.empty(), MESSAGE_ID, ThreadId.fromBaseMessageId(MESSAGE_ID)));
 
         assertThatThrownBy(() -> MetadataMapAssert.assertThat(metaDataMap).containsMetadataForMessages(message1))
             .isInstanceOf(AssertionError.class);
@@ -91,7 +92,7 @@ class MetadataMapAssertTest {
     @Test
     void metadataMapAssertShouldFailWhenSizeMismatch() {
         Map<MessageUid, MessageMetaData> metaDataMap = new HashMap<>();
-        metaDataMap.put(UID, new MessageMetaData(UID, MODSEQ, new Flags(), HEADER_STRING.length() + BODY_STRING.length() + 1, DATE, MESSAGE_ID, ThreadId.fromBaseMessageId(MESSAGE_ID)));
+        metaDataMap.put(UID, new MessageMetaData(UID, MODSEQ, new Flags(), HEADER_STRING.length() + BODY_STRING.length() + 1, DATE, Optional.empty(), MESSAGE_ID, ThreadId.fromBaseMessageId(MESSAGE_ID)));
 
         assertThatThrownBy(() -> MetadataMapAssert.assertThat(metaDataMap).containsMetadataForMessages(message1))
             .isInstanceOf(AssertionError.class);
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/quota/ListeningCurrentQuotaUpdaterTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/quota/ListeningCurrentQuotaUpdaterTest.java
index f89989b1d5..f20a2c22fe 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/quota/ListeningCurrentQuotaUpdaterTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/quota/ListeningCurrentQuotaUpdaterTest.java
@@ -100,8 +100,8 @@ class ListeningCurrentQuotaUpdaterTest {
         Added added = mock(Added.class);
         when(added.getMailboxId()).thenReturn(MAILBOX_ID);
         when(added.getMailboxPath()).thenReturn(MAILBOX_PATH);
-        when(added.getMetaData(MessageUid.of(36))).thenReturn(new MessageMetaData(MessageUid.of(36), ModSeq.first(),new Flags(), SIZE, new Date(), new DefaultMessageId(), ThreadId.fromBaseMessageId(new DefaultMessageId())));
-        when(added.getMetaData(MessageUid.of(38))).thenReturn(new MessageMetaData(MessageUid.of(38), ModSeq.first(),new Flags(), SIZE, new Date(), new DefaultMessageId(), ThreadId.fromBaseMessageId(new DefaultMessageId())));
+        when(added.getMetaData(MessageUid.of(36))).thenReturn(new MessageMetaData(MessageUid.of(36), ModSeq.first(),new Flags(), SIZE, new Date(), Optional.empty(), new DefaultMessageId(), ThreadId.fromBaseMessageId(new DefaultMessageId())));
+        when(added.getMetaData(MessageUid.of(38))).thenReturn(new MessageMetaData(MessageUid.of(38), ModSeq.first(),new Flags(), SIZE, new Date(), Optional.empty(), new DefaultMessageId(), ThreadId.fromBaseMessageId(new DefaultMessageId())));
         when(added.getUids()).thenReturn(Lists.newArrayList(MessageUid.of(36), MessageUid.of(38)));
         when(added.getUsername()).thenReturn(USERNAME_BENWA);
         when(mockedQuotaRootResolver.getQuotaRootReactive(eq(MAILBOX_ID))).thenReturn(Mono.just(QUOTA_ROOT));
@@ -116,8 +116,8 @@ class ListeningCurrentQuotaUpdaterTest {
     @Test
     void expungedEventShouldDecreaseCurrentQuotaValues() throws Exception {
         Expunged expunged = mock(Expunged.class);
-        when(expunged.getMetaData(MessageUid.of(36))).thenReturn(new MessageMetaData(MessageUid.of(36), ModSeq.first(), new Flags(), SIZE, new Date(), new DefaultMessageId(), ThreadId.fromBaseMessageId(new DefaultMessageId())));
-        when(expunged.getMetaData(MessageUid.of(38))).thenReturn(new MessageMetaData(MessageUid.of(38), ModSeq.first(), new Flags(), SIZE, new Date(), new DefaultMessageId(), ThreadId.fromBaseMessageId(new DefaultMessageId())));
+        when(expunged.getMetaData(MessageUid.of(36))).thenReturn(new MessageMetaData(MessageUid.of(36), ModSeq.first(), new Flags(), SIZE, new Date(), Optional.empty(), new DefaultMessageId(), ThreadId.fromBaseMessageId(new DefaultMessageId())));
+        when(expunged.getMetaData(MessageUid.of(38))).thenReturn(new MessageMetaData(MessageUid.of(38), ModSeq.first(), new Flags(), SIZE, new Date(), Optional.empty(), new DefaultMessageId(), ThreadId.fromBaseMessageId(new DefaultMessageId())));
         when(expunged.getUids()).thenReturn(Lists.newArrayList(MessageUid.of(36), MessageUid.of(38)));
         when(expunged.getMailboxId()).thenReturn(MAILBOX_ID);
         when(expunged.getUsername()).thenReturn(USERNAME_BENWA);
diff --git a/protocols/imap/src/test/java/org/apache/james/imap/processor/base/MailboxEventAnalyserTest.java b/protocols/imap/src/test/java/org/apache/james/imap/processor/base/MailboxEventAnalyserTest.java
index 8dc7aef80f..f2a9753778 100644
--- a/protocols/imap/src/test/java/org/apache/james/imap/processor/base/MailboxEventAnalyserTest.java
+++ b/protocols/imap/src/test/java/org/apache/james/imap/processor/base/MailboxEventAnalyserTest.java
@@ -26,6 +26,7 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import java.util.Date;
+import java.util.Optional;
 
 import javax.mail.Flags;
 
@@ -132,7 +133,7 @@ class MailboxEventAnalyserTest {
         .randomEventId()
         .mailboxSession(MAILBOX_SESSION)
         .mailbox(DEFAULT_MAILBOX)
-        .addMetaData(new MessageMetaData(MessageUid.of(11), ModSeq.first(), new Flags(), 45, new Date(), new DefaultMessageId(), ThreadId.fromBaseMessageId(new DefaultMessageId())))
+        .addMetaData(new MessageMetaData(MessageUid.of(11), ModSeq.first(), new Flags(), 45, new Date(), Optional.empty(), new DefaultMessageId(), ThreadId.fromBaseMessageId(new DefaultMessageId())))
         .isDelivery(!IS_DELIVERY)
         .build();
 
diff --git a/protocols/imap/src/test/java/org/apache/james/imap/processor/base/SelectedMailboxImplTest.java b/protocols/imap/src/test/java/org/apache/james/imap/processor/base/SelectedMailboxImplTest.java
index d8bc996eac..b628795603 100644
--- a/protocols/imap/src/test/java/org/apache/james/imap/processor/base/SelectedMailboxImplTest.java
+++ b/protocols/imap/src/test/java/org/apache/james/imap/processor/base/SelectedMailboxImplTest.java
@@ -33,6 +33,7 @@ import static org.mockito.Mockito.when;
 
 import java.time.Duration;
 import java.util.Date;
+import java.util.Optional;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ThreadFactory;
@@ -222,7 +223,7 @@ class SelectedMailboxImplTest {
             .randomEventId()
             .mailboxSession(MailboxSessionUtil.create(Username.of("user")))
             .mailbox(mailbox)
-            .addMetaData(new MessageMetaData(EMITTED_EVENT_UID, MOD_SEQ, new Flags(), SIZE, new Date(), new DefaultMessageId(), ThreadId.fromBaseMessageId(new DefaultMessageId())))
+            .addMetaData(new MessageMetaData(EMITTED_EVENT_UID, MOD_SEQ, new Flags(), SIZE, new Date(), Optional.empty(), new DefaultMessageId(), ThreadId.fromBaseMessageId(new DefaultMessageId())))
             .isDelivery(!IS_DELIVERY)
             .build();
     }


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


[james-project] 07/08: JAMES-3754 Cassandra tables upgrade instructions

Posted by bt...@apache.org.
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 7f17df7a62b6883b5050dccd2211661fdab4e4d6
Author: Quan Tran <hq...@linagora.com>
AuthorDate: Thu Dec 1 17:41:30 2022 +0700

    JAMES-3754 Cassandra tables upgrade instructions
---
 upgrade-instructions.md | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/upgrade-instructions.md b/upgrade-instructions.md
index 31e61f6041..074934a1fe 100644
--- a/upgrade-instructions.md
+++ b/upgrade-instructions.md
@@ -23,6 +23,23 @@ Change list:
 - [SortOrder addition in Identity](#sortorder-addition-in-identity)
 - [TLS host name verification is now enabled by default](#tls-host-name-verification-is-now-enabled-by-default)
 - [Blob Store AES upgraded to PBKDF2WithHmacSHA512](#blob-store-aes-upgraded-to-pbkdf2withhmacsha512)
+- [Adding saveDate column to messageIdTable and imapUidTable](#adding-savedate-column-to-messageidtable-and-imapuidtable)
+
+### Adding saveDate column to messageIdTable and imapUidTable
+
+Date 01/12/2022
+
+JIRA: https://issues.apache.org/jira/browse/JAMES-3754
+
+Concerned product: Distributed James, Cassandra James Server
+
+Add `save_date` column to `messageIdTable` and `imapUidTable` tables in order to store saveDate data as part of IMAP SAVEDATE extension.
+
+In order to add this `messageIdTable` column you need to run the following CQL commands:
+```
+ALTER TABLE james_keyspace.messageIdTable ADD save_date timestamp;
+ALTER TABLE james_keyspace.imapUidTable ADD save_date timestamp;
+```
 
 ### Blob Store AES upgraded to PBKDF2WithHmacSHA512
 


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