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

[james-project] branch master updated: JAMES-3461 - Fix Mailbox/changes do not take isSubscribe changes into account (#1320)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 0a7c50830f JAMES-3461 - Fix Mailbox/changes do not take isSubscribe changes into account (#1320)
0a7c50830f is described below

commit 0a7c50830fa98ee32b8822816349cc8e655824c5
Author: vttran <vt...@linagora.com>
AuthorDate: Mon Dec 5 09:54:08 2022 +0700

    JAMES-3461 - Fix Mailbox/changes do not take isSubscribe changes into account (#1320)
---
 .../apache/james/mailbox/events/MailboxEvents.java |  62 ++++++++
 .../cassandra/CassandraMailboxManagerTest.java     |   4 +-
 .../CassandraSubscriptionManagerTest.java          |  23 ++-
 .../james/event/json/MailboxEventSerializer.scala  |  26 ++-
 .../main/resources/META-INF/spring/mailbox-jpa.xml |   2 +
 .../james/mailbox/jpa/JPAMailboxManagerTest.java   |   2 +-
 .../mailbox/jpa/JPASubscriptionManagerTest.java    |   9 +-
 .../resources/META-INF/spring/mailbox-memory.xml   |   2 +
 .../mailbox/inmemory/MemoryMailboxManagerTest.java |   2 +-
 .../mailbox/store/StoreSubscriptionManager.java    |  41 ++++-
 .../james/mailbox/store/event/EventFactory.java    |  61 ++++++-
 .../cassandra/host/CassandraHostSystem.java        |   2 +-
 .../org/apache/james/imap/scripts/Subscribe.test   |  16 ++
 .../inmemory/host/InMemoryHostSystem.java          |   4 +-
 .../mpt/imapmailbox/jpa/host/JPAHostSystem.java    |   2 +-
 .../lucenesearch/host/LuceneSearchHostSystem.java  |   2 +-
 .../elasticsearch/host/OpenSearchHostSystem.java   |   2 +-
 .../rabbitmq/host/RabbitMQEventBusHostSystem.java  |   2 +-
 .../james/jmap/api/change/MailboxChange.java       |  49 ++++++
 .../jmap/draft/methods/GetMailboxesMethodTest.java |   2 +-
 .../jmap/http/DefaultMailboxesProvisionerTest.java |   2 +-
 .../james/jmap/rfc8621/contract/JmapRequests.scala |  58 +++++++
 .../contract/MailboxChangesMethodContract.scala    | 176 +++++++++++++++++++++
 .../contract/MailboxSetMethodContract.scala        | 128 ++++++++++++++-
 .../james/jmap/change/MailboxChangeListener.scala  |   8 +-
 .../jmap/change/MailboxChangeListenerTest.scala    |  45 +++++-
 .../james/jmap/http/MailboxesProvisionerTest.scala |   2 +-
 .../james/imapserver/netty/IMAPServerTest.java     |   4 +-
 .../service/SubscribeAllRequestToTaskTest.java     |   4 +-
 29 files changed, 704 insertions(+), 38 deletions(-)

diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/events/MailboxEvents.java b/mailbox/api/src/main/java/org/apache/james/mailbox/events/MailboxEvents.java
index 3a45e0eff6..f56c8518d3 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/events/MailboxEvents.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/events/MailboxEvents.java
@@ -587,4 +587,66 @@ public interface MailboxEvents {
         }
     }
 
+    class MailboxSubscribedEvent extends MailboxEvent {
+
+        public MailboxSubscribedEvent(MailboxSession.SessionId sessionId, Username username, MailboxPath path, MailboxId mailboxId, EventId eventId) {
+            super(sessionId, username, path, mailboxId, eventId);
+        }
+
+        @Override
+        public boolean isNoop() {
+            return false;
+        }
+
+        @Override
+        public final boolean equals(Object o) {
+            if (o instanceof MailboxACLUpdated) {
+                MailboxACLUpdated that = (MailboxACLUpdated) o;
+
+                return Objects.equals(this.eventId, that.eventId)
+                    && Objects.equals(this.sessionId, that.sessionId)
+                    && Objects.equals(this.username, that.username)
+                    && Objects.equals(this.path, that.path)
+                    && Objects.equals(this.mailboxId, that.mailboxId);
+            }
+            return false;
+        }
+
+        @Override
+        public final int hashCode() {
+            return Objects.hash(eventId, sessionId, username, path,  mailboxId);
+        }
+    }
+
+    class MailboxUnsubscribedEvent extends MailboxEvent {
+
+        public MailboxUnsubscribedEvent(MailboxSession.SessionId sessionId, Username username, MailboxPath path, MailboxId mailboxId, EventId eventId) {
+            super(sessionId, username, path, mailboxId, eventId);
+        }
+
+        @Override
+        public boolean isNoop() {
+            return false;
+        }
+
+        @Override
+        public final boolean equals(Object o) {
+            if (o instanceof MailboxACLUpdated) {
+                MailboxACLUpdated that = (MailboxACLUpdated) o;
+
+                return Objects.equals(this.eventId, that.eventId)
+                    && Objects.equals(this.sessionId, that.sessionId)
+                    && Objects.equals(this.username, that.username)
+                    && Objects.equals(this.path, that.path)
+                    && Objects.equals(this.mailboxId, that.mailboxId);
+            }
+            return false;
+        }
+
+        @Override
+        public final int hashCode() {
+            return Objects.hash(eventId, sessionId, username, path,  mailboxId);
+        }
+    }
+
 }
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraMailboxManagerTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraMailboxManagerTest.java
index 97aa4363df..7fff10ce0b 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraMailboxManagerTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraMailboxManagerTest.java
@@ -120,7 +120,7 @@ public class CassandraMailboxManagerTest extends MailboxManagerTest<CassandraMai
 
     @Override
     protected SubscriptionManager provideSubscriptionManager() {
-        return new StoreSubscriptionManager(provideMailboxManager().getMapperFactory());
+        return new StoreSubscriptionManager(provideMailboxManager().getMapperFactory(), provideMailboxManager().getMapperFactory(), provideMailboxManager().getEventBus());
     }
 
     @Override
@@ -904,7 +904,7 @@ public class CassandraMailboxManagerTest extends MailboxManagerTest<CassandraMai
 
         @Override
         protected SubscriptionManager provideSubscriptionManager() {
-            return new StoreSubscriptionManager(provideMailboxManager().getMapperFactory());
+            return new StoreSubscriptionManager(provideMailboxManager().getMapperFactory(), provideMailboxManager().getMapperFactory(), provideMailboxManager().getEventBus());
         }
 
         @Override
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 37d980a9e2..2c6a90d691 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
@@ -24,8 +24,13 @@ import static org.assertj.core.api.Assertions.assertThat;
 import org.apache.james.backends.cassandra.CassandraClusterExtension;
 import org.apache.james.backends.cassandra.components.CassandraModule;
 import org.apache.james.backends.cassandra.init.configuration.CassandraConfiguration;
+import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionModule;
 import org.apache.james.blob.api.BlobStore;
 import org.apache.james.core.Username;
+import org.apache.james.events.EventBusTestFixture;
+import org.apache.james.events.InVMEventBus;
+import org.apache.james.events.MemoryEventDeadLetters;
+import org.apache.james.events.delivery.InVmEventDelivery;
 import org.apache.james.mailbox.SubscriptionManager;
 import org.apache.james.mailbox.SubscriptionManagerContract;
 import org.apache.james.mailbox.cassandra.mail.CassandraACLMapper;
@@ -49,11 +54,13 @@ import org.apache.james.mailbox.cassandra.mail.CassandraUidProvider;
 import org.apache.james.mailbox.cassandra.mail.CassandraUserMailboxRightsDAO;
 import org.apache.james.mailbox.cassandra.mail.task.RecomputeMailboxCountersService;
 import org.apache.james.mailbox.cassandra.modules.CassandraAnnotationModule;
+import org.apache.james.mailbox.cassandra.modules.CassandraMailboxModule;
 import org.apache.james.mailbox.cassandra.modules.CassandraSubscriptionModule;
 import org.apache.james.mailbox.model.MailboxPath;
 import org.apache.james.mailbox.store.BatchSizes;
 import org.apache.james.mailbox.store.StoreSubscriptionManager;
 import org.apache.james.mailbox.store.user.model.Subscription;
+import org.apache.james.metrics.tests.RecordingMetricFactory;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
@@ -69,7 +76,9 @@ class CassandraSubscriptionManagerTest implements SubscriptionManagerContract {
     @RegisterExtension
     static CassandraClusterExtension cassandraCluster = new CassandraClusterExtension(CassandraModule.aggregateModules(
         CassandraSubscriptionModule.MODULE,
-        CassandraAnnotationModule.MODULE));
+        CassandraAnnotationModule.MODULE,
+        CassandraSchemaVersionModule.MODULE,
+        CassandraMailboxModule.MODULE));
 
     private SubscriptionManager subscriptionManager;
     private CassandraMailboxSessionMapperFactory mailboxSessionMapperFactory;
@@ -90,7 +99,7 @@ class CassandraSubscriptionManagerTest implements SubscriptionManagerContract {
         CassandraMailboxCounterDAO mailboxCounterDAO = null;
         CassandraMailboxRecentsDAO mailboxRecentsDAO = null;
         CassandraMailboxDAO mailboxDAO = null;
-        CassandraMailboxPathV3DAO mailboxPathV3DAO = null;
+        CassandraMailboxPathV3DAO mailboxPathV3DAO = new CassandraMailboxPathV3DAO(cassandraCluster.getCassandraCluster().getConf());
         CassandraFirstUnseenDAO firstUnseenDAO = null;
         CassandraApplicableFlagDAO applicableFlagDAO = null;
         CassandraDeletedMessageDAO deletedMessageDAO = null;
@@ -103,7 +112,7 @@ class CassandraSubscriptionManagerTest implements SubscriptionManagerContract {
         CassandraModSeqProvider modSeqProvider = null;
         RecomputeMailboxCountersService recomputeMailboxCountersService = null;
 
-        mailboxSessionMapperFactory = new CassandraMailboxSessionMapperFactory(
+        mailboxSessionMapperFactory =  new CassandraMailboxSessionMapperFactory(
             uidProvider,
             modSeqProvider,
             cassandraCluster.getCassandraCluster().getConf(),
@@ -128,7 +137,13 @@ class CassandraSubscriptionManagerTest implements SubscriptionManagerContract {
             recomputeMailboxCountersService,
             CassandraConfiguration.DEFAULT_CONFIGURATION,
             BatchSizes.defaultValues());
-        subscriptionManager = new StoreSubscriptionManager(mailboxSessionMapperFactory);
+
+        InVMEventBus eventBus = new InVMEventBus(new InVmEventDelivery(new RecordingMetricFactory()), EventBusTestFixture.RETRY_BACKOFF_CONFIGURATION, new MemoryEventDeadLetters());
+
+        subscriptionManager = new StoreSubscriptionManager(
+            mailboxSessionMapperFactory,
+            mailboxSessionMapperFactory,
+            eventBus);
     }
 
     @Test
diff --git a/mailbox/event/json/src/main/scala/org/apache/james/event/json/MailboxEventSerializer.scala b/mailbox/event/json/src/main/scala/org/apache/james/event/json/MailboxEventSerializer.scala
index 23cf3d8298..1e31a140db 100644
--- a/mailbox/event/json/src/main/scala/org/apache/james/event/json/MailboxEventSerializer.scala
+++ b/mailbox/event/json/src/main/scala/org/apache/james/event/json/MailboxEventSerializer.scala
@@ -31,7 +31,7 @@ import org.apache.james.event.json.DTOs._
 import org.apache.james.events.Event.EventId
 import org.apache.james.events.{EventSerializer, Event => JavaEvent}
 import org.apache.james.mailbox.MailboxSession.SessionId
-import org.apache.james.mailbox.events.MailboxEvents.{Added => JavaAdded, Expunged => JavaExpunged, FlagsUpdated => JavaFlagsUpdated, MailboxACLUpdated => JavaMailboxACLUpdated, MailboxAdded => JavaMailboxAdded, MailboxDeletion => JavaMailboxDeletion, MailboxRenamed => JavaMailboxRenamed, QuotaUsageUpdatedEvent => JavaQuotaUsageUpdatedEvent}
+import org.apache.james.mailbox.events.MailboxEvents.{MailboxSubscribedEvent => JavaMailboxSubscribedEvent, MailboxUnsubscribedEvent => JavaMailboxUnsubscribedEvent, Added => JavaAdded, Expunged => JavaExpunged, FlagsUpdated => JavaFlagsUpdated, MailboxACLUpdated => JavaMailboxACLUpdated, MailboxAdded => JavaMailboxAdded, MailboxDeletion => JavaMailboxDeletion, MailboxRenamed => JavaMailboxRenamed, QuotaUsageUpdatedEvent => JavaQuotaUsageUpdatedEvent}
 import org.apache.james.mailbox.events.{MessageMoveEvent => JavaMessageMoveEvent}
 import org.apache.james.mailbox.model.{MailboxId, MessageId, MessageMoves, QuotaRoot, ThreadId, MailboxACL => JavaMailboxACL, MessageMetaData => JavaMessageMetaData, Quota => JavaQuota}
 import org.apache.james.mailbox.quota.QuotaRootDeserializer
@@ -116,6 +116,14 @@ private object DTO {
       updatedFlags.map(_.toJava).asJava,
       eventId)
   }
+
+  case class MailboxSubscribedEvent(eventId: EventId, mailboxPath: MailboxPath, mailboxId: MailboxId, user: Username, sessionId: SessionId) extends Event {
+    override def toJava: JavaEvent = new JavaMailboxSubscribedEvent(sessionId, user, mailboxPath.toJava, mailboxId, eventId)
+  }
+
+  case class MailboxUnSubscribedEvent(eventId: EventId, mailboxPath: MailboxPath, mailboxId: MailboxId, user: Username, sessionId: SessionId) extends Event {
+    override def toJava: JavaEvent = new JavaMailboxUnsubscribedEvent(sessionId, user, mailboxPath.toJava, mailboxId, eventId)
+  }
 }
 
 private object ScalaConverter {
@@ -193,6 +201,20 @@ private object ScalaConverter {
     mailboxId = event.getMailboxId,
     updatedFlags = event.getUpdatedFlags.asScala.toList.map(DTOs.UpdatedFlags.toUpdatedFlags))
 
+  private def toScala(event: JavaMailboxSubscribedEvent): DTO.MailboxSubscribedEvent = DTO.MailboxSubscribedEvent(
+    eventId = event.getEventId,
+    mailboxPath = MailboxPath.fromJava(event.getMailboxPath),
+    mailboxId = event.getMailboxId,
+    user = event.getUsername,
+    sessionId = event.getSessionId)
+
+  private def toScala(event: JavaMailboxUnsubscribedEvent): DTO.MailboxUnSubscribedEvent = DTO.MailboxUnSubscribedEvent(
+    eventId = event.getEventId,
+    mailboxPath = MailboxPath.fromJava(event.getMailboxPath),
+    mailboxId = event.getMailboxId,
+    user = event.getUsername,
+    sessionId = event.getSessionId)
+
   def toScala(javaEvent: JavaEvent): Event = javaEvent match {
     case e: JavaAdded => toScala(e)
     case e: JavaExpunged => toScala(e)
@@ -203,6 +225,8 @@ private object ScalaConverter {
     case e: JavaMailboxRenamed => toScala(e)
     case e: JavaMessageMoveEvent => toScala(e)
     case e: JavaQuotaUsageUpdatedEvent => toScala(e)
+    case e: JavaMailboxSubscribedEvent => toScala(e)
+    case e: JavaMailboxUnsubscribedEvent => toScala(e)
     case _ => throw new RuntimeException("no Scala conversion known")
   }
 }
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 6d717dc1d2..044f1d3a98 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
@@ -48,6 +48,8 @@
 
     <bean id ="jpa-subscriptionManager" class="org.apache.james.mailbox.store.StoreSubscriptionManager">
         <constructor-arg index="0" ref="jpa-sessionMapperFactory"/>
+        <constructor-arg index="1" ref="jpa-sessionMapperFactory"/>
+        <constructor-arg index="2" ref="event-bus"/>
     </bean>
     <bean id="jpa-sessionMapperFactory" class="org.apache.james.mailbox.jpa.JPAMailboxSessionMapperFactory">
         <constructor-arg index="0" ref="entityManagerFactory"/>
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 bd632cca13..bab657d713 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
@@ -52,7 +52,7 @@ class JPAMailboxManagerTest extends MailboxManagerTest<OpenJPAMailboxManager> {
 
     @Override
     protected SubscriptionManager provideSubscriptionManager() {
-        return new StoreSubscriptionManager(provideMailboxManager().getMapperFactory());
+        return new StoreSubscriptionManager(provideMailboxManager().getMapperFactory(), provideMailboxManager().getMapperFactory(), provideMailboxManager().getEventBus());
     }
 
     @AfterEach
diff --git a/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JPASubscriptionManagerTest.java b/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JPASubscriptionManagerTest.java
index 41bfd71b79..de8112a3e4 100644
--- a/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JPASubscriptionManagerTest.java
+++ b/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JPASubscriptionManagerTest.java
@@ -21,11 +21,16 @@ package org.apache.james.mailbox.jpa;
 import javax.persistence.EntityManagerFactory;
 
 import org.apache.james.backends.jpa.JpaTestCluster;
+import org.apache.james.events.EventBusTestFixture;
+import org.apache.james.events.InVMEventBus;
+import org.apache.james.events.MemoryEventDeadLetters;
+import org.apache.james.events.delivery.InVmEventDelivery;
 import org.apache.james.mailbox.SubscriptionManager;
 import org.apache.james.mailbox.SubscriptionManagerContract;
 import org.apache.james.mailbox.jpa.mail.JPAModSeqProvider;
 import org.apache.james.mailbox.jpa.mail.JPAUidProvider;
 import org.apache.james.mailbox.store.StoreSubscriptionManager;
+import org.apache.james.metrics.tests.RecordingMetricFactory;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 
@@ -47,8 +52,8 @@ class JPASubscriptionManagerTest implements SubscriptionManagerContract {
         JPAMailboxSessionMapperFactory mapperFactory = new JPAMailboxSessionMapperFactory(entityManagerFactory,
             new JPAUidProvider(entityManagerFactory),
             new JPAModSeqProvider(entityManagerFactory));
-
-        subscriptionManager = new StoreSubscriptionManager(mapperFactory);
+        InVMEventBus eventBus = new InVMEventBus(new InVmEventDelivery(new RecordingMetricFactory()), EventBusTestFixture.RETRY_BACKOFF_CONFIGURATION, new MemoryEventDeadLetters());
+        subscriptionManager = new StoreSubscriptionManager(mapperFactory, mapperFactory, eventBus);
     }
 
     @AfterEach
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 6f5ba0cf2a..a4cf3f9053 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
@@ -48,6 +48,8 @@
 
     <bean id ="memory-subscriptionManager" class="org.apache.james.mailbox.store.StoreSubscriptionManager">
         <constructor-arg index="0" ref="memory-sessionMapperFactory"/>
+        <constructor-arg index="1" ref="memory-sessionMapperFactory"/>
+        <constructor-arg index="2" ref="event-bus"/>
     </bean>
 
     <bean id="memory-sessionMapperFactory" class="org.apache.james.mailbox.inmemory.InMemoryMailboxSessionMapperFactory" />
diff --git a/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/MemoryMailboxManagerTest.java b/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/MemoryMailboxManagerTest.java
index 4bde607d21..2c107a5af4 100644
--- a/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/MemoryMailboxManagerTest.java
+++ b/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/MemoryMailboxManagerTest.java
@@ -33,7 +33,7 @@ class MemoryMailboxManagerTest extends MailboxManagerTest<InMemoryMailboxManager
 
     @Override
     protected SubscriptionManager provideSubscriptionManager() {
-        return new StoreSubscriptionManager(provideMailboxManager().getMapperFactory());
+        return new StoreSubscriptionManager(provideMailboxManager().getMapperFactory(), provideMailboxManager().getMapperFactory(), provideMailboxManager().getEventBus());
     }
 
     @Override
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreSubscriptionManager.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreSubscriptionManager.java
index 8e0f9f1df0..090e01f0a9 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreSubscriptionManager.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreSubscriptionManager.java
@@ -25,12 +25,15 @@ import java.util.stream.Collectors;
 
 import javax.inject.Inject;
 
+import org.apache.james.events.EventBus;
 import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.RequestAware;
 import org.apache.james.mailbox.SubscriptionManager;
+import org.apache.james.mailbox.events.MailboxIdRegistrationKey;
 import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.exception.SubscriptionException;
 import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.mailbox.store.event.EventFactory;
 import org.apache.james.mailbox.store.transaction.Mapper;
 import org.apache.james.mailbox.store.user.SubscriptionMapper;
 import org.apache.james.mailbox.store.user.SubscriptionMapperFactory;
@@ -46,10 +49,16 @@ public class StoreSubscriptionManager implements SubscriptionManager {
     private static final int INITIAL_SIZE = 32;
     
     protected SubscriptionMapperFactory mapperFactory;
+    private final MailboxSessionMapperFactory mailboxSessionMapperFactory;
+    private final EventBus eventBus;
 
     @Inject
-    public StoreSubscriptionManager(SubscriptionMapperFactory mapperFactory) {
+    public StoreSubscriptionManager(SubscriptionMapperFactory mapperFactory,
+                                    MailboxSessionMapperFactory mailboxSessionMapperFactory,
+                                    EventBus eventBus) {
         this.mapperFactory = mapperFactory;
+        this.mailboxSessionMapperFactory = mailboxSessionMapperFactory;
+        this.eventBus = eventBus;
     }
 
     @Override
@@ -63,6 +72,7 @@ public class StoreSubscriptionManager implements SubscriptionManager {
         } catch (MailboxException e) {
             throw new SubscriptionException(e);
         }
+        dispatchSubscribedEvent(session, mailbox).block();
     }
 
     @Override
@@ -70,12 +80,24 @@ public class StoreSubscriptionManager implements SubscriptionManager {
         try {
             SubscriptionMapper mapper = mapperFactory.getSubscriptionMapper(session);
             Subscription newSubscription = new Subscription(session.getUser(), mailbox.asEscapedString());
-            return mapper.executeReactive(mapper.saveReactive(newSubscription));
+            return mapper.executeReactive(mapper.saveReactive(newSubscription))
+                .then(dispatchSubscribedEvent(session, mailbox));
         } catch (SubscriptionException e) {
             return Mono.error(e);
         }
     }
 
+    private Mono<Void> dispatchSubscribedEvent(MailboxSession session, MailboxPath mailboxPath) {
+        return mailboxSessionMapperFactory.getMailboxMapper(session)
+            .findMailboxByPath(mailboxPath)
+            .flatMap(mailbox -> eventBus.dispatch(EventFactory.mailboxSubscribed()
+                    .randomEventId()
+                    .mailboxSession(session)
+                    .mailbox(mailbox)
+                    .build(),
+                new MailboxIdRegistrationKey(mailbox.getMailboxId())));
+    }
+
     @Override
     public Publisher<Void> unsubscribeReactive(MailboxPath mailbox, MailboxSession session) {
         try {
@@ -86,7 +108,8 @@ public class StoreSubscriptionManager implements SubscriptionManager {
             return mapper.executeReactive(mapper.deleteReactive(oldSubscription))
                 .then(legacyOldSubscription
                     .map(subscription -> mapper.executeReactive(mapper.deleteReactive(subscription)))
-                    .orElse(Mono.empty()));
+                    .orElse(Mono.empty()))
+                .then(dispatchUnSubscribedEvent(session, mailbox));
         } catch (SubscriptionException e) {
             return Mono.error(e);
         }
@@ -122,6 +145,18 @@ public class StoreSubscriptionManager implements SubscriptionManager {
         } catch (MailboxException e) {
             throw new SubscriptionException(e);
         }
+        dispatchUnSubscribedEvent(session, mailbox).block();
+    }
+
+    private Mono<Void> dispatchUnSubscribedEvent(MailboxSession session, MailboxPath mailboxPath) {
+        return mailboxSessionMapperFactory.getMailboxMapper(session)
+            .findMailboxByPath(mailboxPath)
+            .flatMap(mailbox -> eventBus.dispatch(EventFactory.mailboxUnSubscribed()
+                    .randomEventId()
+                    .mailboxSession(session)
+                    .mailbox(mailbox)
+                    .build(),
+                new MailboxIdRegistrationKey(mailbox.getMailboxId())));
     }
 
     @Override
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/EventFactory.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/EventFactory.java
index a725f0ff0c..f2a7052e54 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/EventFactory.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/EventFactory.java
@@ -34,6 +34,7 @@ import org.apache.james.events.Event;
 import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.MessageUid;
 import org.apache.james.mailbox.acl.ACLDiff;
+import org.apache.james.mailbox.events.MailboxEvents;
 import org.apache.james.mailbox.events.MailboxEvents.Added;
 import org.apache.james.mailbox.events.MailboxEvents.Expunged;
 import org.apache.james.mailbox.events.MailboxEvents.FlagsUpdated;
@@ -427,6 +428,56 @@ public class EventFactory {
         }
     }
 
+    public static class MailboxSubscribedFinalStage {
+        private final Event.EventId eventId;
+        private final MailboxPath path;
+        private final MailboxId mailboxId;
+        private final Username username;
+        private final MailboxSession.SessionId sessionId;
+
+        MailboxSubscribedFinalStage(Event.EventId eventId, MailboxPath path, MailboxId mailboxId, Username username, MailboxSession.SessionId sessionId) {
+            this.eventId = eventId;
+            this.path = path;
+            this.mailboxId = mailboxId;
+            this.username = username;
+            this.sessionId = sessionId;
+        }
+
+        public MailboxEvents.MailboxSubscribedEvent build() {
+            Preconditions.checkNotNull(path);
+            Preconditions.checkNotNull(mailboxId);
+            Preconditions.checkNotNull(username);
+            Preconditions.checkNotNull(sessionId);
+
+            return new MailboxEvents.MailboxSubscribedEvent(sessionId, username, path, mailboxId, eventId);
+        }
+    }
+
+    public static class MailboxUnSubscribedFinalStage {
+        private final Event.EventId eventId;
+        private final MailboxPath path;
+        private final MailboxId mailboxId;
+        private final Username username;
+        private final MailboxSession.SessionId sessionId;
+
+        MailboxUnSubscribedFinalStage(Event.EventId eventId, MailboxPath path, MailboxId mailboxId, Username username, MailboxSession.SessionId sessionId) {
+            this.eventId = eventId;
+            this.path = path;
+            this.mailboxId = mailboxId;
+            this.username = username;
+            this.sessionId = sessionId;
+        }
+
+        public MailboxEvents.MailboxUnsubscribedEvent build() {
+            Preconditions.checkNotNull(path);
+            Preconditions.checkNotNull(mailboxId);
+            Preconditions.checkNotNull(username);
+            Preconditions.checkNotNull(sessionId);
+
+            return new MailboxEvents.MailboxUnsubscribedEvent(sessionId, username, path, mailboxId, eventId);
+        }
+    }
+
     public static RequireMailboxEvent<RequireMetadata<RequireIsDelivery<AddedFinalStage>>> added() {
         return eventId -> user -> sessionId -> mailboxId -> path -> metaData -> isDelivery -> new AddedFinalStage(eventId, path, mailboxId, user, sessionId, metaData, isDelivery);
     }
@@ -443,7 +494,7 @@ public class EventFactory {
         return eventId -> user -> sessionId -> mailboxId -> oldPath -> newPath -> new MailboxRenamedFinalStage(eventId, oldPath, mailboxId, user, sessionId, newPath);
     }
 
-    public static  RequireMailboxEvent<RequireQuotaRoot<RequireMailboxACL<RequireQuotaCountValue<RequireQuotaSizeValue<MailboxDeletionFinalStage>>>>> mailboxDeleted() {
+    public static RequireMailboxEvent<RequireQuotaRoot<RequireMailboxACL<RequireQuotaCountValue<RequireQuotaSizeValue<MailboxDeletionFinalStage>>>>> mailboxDeleted() {
         return eventId -> user -> sessionId -> mailboxId -> path -> quotaRoot -> mailboxACL -> quotaCount -> quotaSize -> new MailboxDeletionFinalStage(
             eventId, path, mailboxACL, mailboxId, user, sessionId, quotaRoot, quotaCount, quotaSize);
     }
@@ -460,6 +511,14 @@ public class EventFactory {
         return eventId -> user -> quotaRoot -> quotaCount -> quotaSize -> instant -> new QuotaUsageUpdatedFinalStage(eventId, user, quotaRoot, quotaCount, quotaSize, instant);
     }
 
+    public static RequireMailboxEvent<MailboxSubscribedFinalStage> mailboxSubscribed() {
+        return eventId -> user -> sessionId -> mailboxId -> path -> new MailboxSubscribedFinalStage(eventId, path, mailboxId, user, sessionId);
+    }
+
+    public static RequireMailboxEvent<MailboxUnSubscribedFinalStage> mailboxUnSubscribed() {
+        return eventId -> user -> sessionId -> mailboxId -> path -> new MailboxUnSubscribedFinalStage(eventId, path, mailboxId, user, sessionId);
+    }
+
     public static MessageMoveEvent.Builder moved() {
         return MessageMoveEvent.builder();
     }
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 aec693c450..e9b7b3c789 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
@@ -127,7 +127,7 @@ public class CassandraHostSystem extends JamesImapHostSystem {
 
         eventBus.register(quotaUpdater);
 
-        SubscriptionManager subscriptionManager = new StoreSubscriptionManager(mapperFactory);
+        SubscriptionManager subscriptionManager = new StoreSubscriptionManager(mapperFactory, mapperFactory, eventBus);
 
         configure(new DefaultImapDecoderFactory().buildImapDecoder(),
                 new DefaultImapEncoderFactory().buildImapEncoder(),
diff --git a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/Subscribe.test b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/Subscribe.test
index 461d067b8b..1621e72a2e 100644
--- a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/Subscribe.test
+++ b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/Subscribe.test
@@ -51,6 +51,22 @@ S: \* LSUB \(\) \"\.\" \"subscribetest1\.subfolder1\"
 }
 S: a06 OK LSUB completed.
 
+C: b05 SUBSCRIBE whatever
+S: b05 OK SUBSCRIBE completed.
+
+# LIST All subscribed
+C: b06 LSUB "" "*"
+SUB {
+S: \* LSUB \(\) \"\.\" \"whatever\"
+S: \* LSUB \(\) \"\.\" \"subscribetest\"
+S: \* LSUB \(\) \"\.\" \"subscribetest\.subfolder\"
+S: \* LSUB \(\) \"\.\" \"subscribetest1\.subfolder1\"
+}
+S: b06 OK LSUB completed.
+
+C: b08 UNSUBSCRIBE whatever
+S: b08 OK UNSUBSCRIBE completed.
+
 # LIST A subset of subscribed
 C: a07 LSUB "" "subscribetest.sub*"
 S: \* LSUB \(\) \"\.\" \"subscribetest\.subfolder\"
diff --git a/mpt/impl/imap-mailbox/inmemory/src/test/java/org/apache/james/mpt/imapmailbox/inmemory/host/InMemoryHostSystem.java b/mpt/impl/imap-mailbox/inmemory/src/test/java/org/apache/james/mpt/imapmailbox/inmemory/host/InMemoryHostSystem.java
index d327eaeb4e..a354f8e09d 100644
--- a/mpt/impl/imap-mailbox/inmemory/src/test/java/org/apache/james/mpt/imapmailbox/inmemory/host/InMemoryHostSystem.java
+++ b/mpt/impl/imap-mailbox/inmemory/src/test/java/org/apache/james/mpt/imapmailbox/inmemory/host/InMemoryHostSystem.java
@@ -64,7 +64,9 @@ public class InMemoryHostSystem extends JamesImapHostSystem {
         this.mailboxManager = resources.getMailboxManager();
         this.perUserMaxQuotaManager = resources.getMaxQuotaManager();
 
-        ImapProcessor defaultImapProcessorFactory = DefaultImapProcessorFactory.createDefaultProcessor(mailboxManager,  mailboxManager.getEventBus(), new StoreSubscriptionManager(mailboxManager.getMapperFactory()),
+        ImapProcessor defaultImapProcessorFactory = DefaultImapProcessorFactory.createDefaultProcessor(mailboxManager,  mailboxManager.getEventBus(), new StoreSubscriptionManager(mailboxManager.getMapperFactory(),
+                mailboxManager.getMapperFactory(),
+                mailboxManager.getEventBus()),
             mailboxManager.getQuotaComponents().getQuotaManager(), mailboxManager.getQuotaComponents().getQuotaRootResolver(), new DefaultMetricFactory());
 
         configure(new DefaultImapDecoderFactory().buildImapDecoder(),
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 7e6095f072..4ef7a4fa78 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
@@ -121,7 +121,7 @@ public class JPAHostSystem extends JamesImapHostSystem {
         eventBus.register(quotaUpdater);
         eventBus.register(new MailboxAnnotationListener(mapperFactory, sessionProvider));
 
-        SubscriptionManager subscriptionManager = new StoreSubscriptionManager(mapperFactory);
+        SubscriptionManager subscriptionManager = new StoreSubscriptionManager(mapperFactory, mapperFactory, eventBus);
         
         ImapProcessor defaultImapProcessorFactory =
                 DefaultImapProcessorFactory.createDefaultProcessor(
diff --git a/mpt/impl/imap-mailbox/lucenesearch/src/test/java/org/apache/james/mpt/imapmailbox/lucenesearch/host/LuceneSearchHostSystem.java b/mpt/impl/imap-mailbox/lucenesearch/src/test/java/org/apache/james/mpt/imapmailbox/lucenesearch/host/LuceneSearchHostSystem.java
index a19759930f..84cbb60c2e 100644
--- a/mpt/impl/imap-mailbox/lucenesearch/src/test/java/org/apache/james/mpt/imapmailbox/lucenesearch/host/LuceneSearchHostSystem.java
+++ b/mpt/impl/imap-mailbox/lucenesearch/src/test/java/org/apache/james/mpt/imapmailbox/lucenesearch/host/LuceneSearchHostSystem.java
@@ -75,7 +75,7 @@ public class LuceneSearchHostSystem extends JamesImapHostSystem {
 
         searchIndex = (LuceneMessageSearchIndex) resources.getSearchIndex();
         searchIndex.setEnableSuffixMatch(true);
-        SubscriptionManager subscriptionManager = new StoreSubscriptionManager(mailboxManager.getMapperFactory());
+        SubscriptionManager subscriptionManager = new StoreSubscriptionManager(mailboxManager.getMapperFactory(), mailboxManager.getMapperFactory(), mailboxManager.getEventBus());
 
         ImapProcessor defaultImapProcessorFactory =
             DefaultImapProcessorFactory.createDefaultProcessor(
diff --git a/mpt/impl/imap-mailbox/opensearch/src/test/java/org/apache/james/mpt/imapmailbox/elasticsearch/host/OpenSearchHostSystem.java b/mpt/impl/imap-mailbox/opensearch/src/test/java/org/apache/james/mpt/imapmailbox/elasticsearch/host/OpenSearchHostSystem.java
index ed602b4876..a8dcfcf86b 100644
--- a/mpt/impl/imap-mailbox/opensearch/src/test/java/org/apache/james/mpt/imapmailbox/elasticsearch/host/OpenSearchHostSystem.java
+++ b/mpt/impl/imap-mailbox/opensearch/src/test/java/org/apache/james/mpt/imapmailbox/elasticsearch/host/OpenSearchHostSystem.java
@@ -114,7 +114,7 @@ public class OpenSearchHostSystem extends JamesImapHostSystem {
         ImapProcessor defaultImapProcessorFactory =
             DefaultImapProcessorFactory.createDefaultProcessor(mailboxManager,
                 resources.getMailboxManager().getEventBus(),
-                new StoreSubscriptionManager(mailboxManager.getMapperFactory()),
+                new StoreSubscriptionManager(mailboxManager.getMapperFactory(), mailboxManager.getMapperFactory(), mailboxManager.getEventBus()),
                 new NoQuotaManager(),
                 resources.getDefaultUserQuotaRootResolver(),
                 new DefaultMetricFactory());
diff --git a/mpt/impl/imap-mailbox/rabbitmq/src/test/java/org/apache/james/mpt/imapmailbox/rabbitmq/host/RabbitMQEventBusHostSystem.java b/mpt/impl/imap-mailbox/rabbitmq/src/test/java/org/apache/james/mpt/imapmailbox/rabbitmq/host/RabbitMQEventBusHostSystem.java
index 057cc3a96b..a2e1cc5c18 100644
--- a/mpt/impl/imap-mailbox/rabbitmq/src/test/java/org/apache/james/mpt/imapmailbox/rabbitmq/host/RabbitMQEventBusHostSystem.java
+++ b/mpt/impl/imap-mailbox/rabbitmq/src/test/java/org/apache/james/mpt/imapmailbox/rabbitmq/host/RabbitMQEventBusHostSystem.java
@@ -101,7 +101,7 @@ public class RabbitMQEventBusHostSystem extends JamesImapHostSystem {
             DefaultImapProcessorFactory.createDefaultProcessor(
                 resources.getMailboxManager(),
                 eventBus,
-                new StoreSubscriptionManager(resources.getMailboxManager().getMapperFactory()),
+                new StoreSubscriptionManager(resources.getMailboxManager().getMapperFactory(), resources.getMailboxManager().getMapperFactory(), resources.getMailboxManager().getEventBus()),
                 resources.getQuotaManager(),
                 resources.getDefaultUserQuotaRootResolver(),
                 new DefaultMetricFactory());
diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxChange.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxChange.java
index fc029c2397..f8cc6b02c3 100644
--- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxChange.java
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxChange.java
@@ -28,6 +28,7 @@ import java.util.stream.Stream;
 import javax.inject.Inject;
 
 import org.apache.james.jmap.api.model.AccountId;
+import org.apache.james.mailbox.events.MailboxEvents;
 import org.apache.james.mailbox.events.MailboxEvents.MailboxACLUpdated;
 import org.apache.james.mailbox.events.MailboxEvents.MailboxAdded;
 import org.apache.james.mailbox.events.MailboxEvents.MailboxDeletion;
@@ -158,6 +159,54 @@ public class MailboxChange implements JmapChange {
                 .collect(ImmutableList.toImmutableList());
         }
 
+        public List<JmapChange> fromMailboxSubscribed(List<AccountId> sharees, MailboxEvents.MailboxSubscribedEvent mailboxSubscribedEvent,
+                                                      ZonedDateTime now) {
+            MailboxChange ownerChange = MailboxChange.builder()
+                .accountId(AccountId.fromUsername(mailboxSubscribedEvent.getUsername()))
+                .state(stateFactory.generate())
+                .date(now)
+                .isCountChange(false)
+                .updated(ImmutableList.of(mailboxSubscribedEvent.getMailboxId()))
+                .build();
+
+            Stream<MailboxChange> shareeChanges = sharees.stream()
+                .map(shareeId -> MailboxChange.builder()
+                    .accountId(shareeId)
+                    .state(stateFactory.generate())
+                    .date(now)
+                    .isCountChange(false)
+                    .updated(ImmutableList.of(mailboxSubscribedEvent.getMailboxId()))
+                    .delegated()
+                    .build());
+
+            return Stream.concat(Stream.of(ownerChange), shareeChanges)
+                .collect(ImmutableList.toImmutableList());
+        }
+
+        public List<JmapChange> fromMailboxUnSubscribed(List<AccountId> sharees, MailboxEvents.MailboxUnsubscribedEvent mailboxUnsubscribedEvent,
+                                                      ZonedDateTime now) {
+            MailboxChange ownerChange = MailboxChange.builder()
+                .accountId(AccountId.fromUsername(mailboxUnsubscribedEvent.getUsername()))
+                .state(stateFactory.generate())
+                .date(now)
+                .isCountChange(false)
+                .updated(ImmutableList.of(mailboxUnsubscribedEvent.getMailboxId()))
+                .build();
+
+            Stream<MailboxChange> shareeChanges = sharees.stream()
+                .map(shareeId -> MailboxChange.builder()
+                    .accountId(shareeId)
+                    .state(stateFactory.generate())
+                    .date(now)
+                    .isCountChange(false)
+                    .updated(ImmutableList.of(mailboxUnsubscribedEvent.getMailboxId()))
+                    .delegated()
+                    .build());
+
+            return Stream.concat(Stream.of(ownerChange), shareeChanges)
+                .collect(ImmutableList.toImmutableList());
+        }
+
         public List<JmapChange> fromMailboxACLUpdated(MailboxACLUpdated mailboxACLUpdated, ZonedDateTime now, List<AccountId> sharees) {
             MailboxChange ownerChange = MailboxChange.builder()
                 .accountId(AccountId.fromUsername(mailboxACLUpdated.getUsername()))
diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/GetMailboxesMethodTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/GetMailboxesMethodTest.java
index c17fbf61f0..dbbeb02c3d 100644
--- a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/GetMailboxesMethodTest.java
+++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/GetMailboxesMethodTest.java
@@ -86,7 +86,7 @@ public class GetMailboxesMethodTest {
         quotaRootResolver = mailboxManager.getQuotaComponents().getQuotaRootResolver();
         quotaManager = mailboxManager.getQuotaComponents().getQuotaManager();
         mailboxFactory = new MailboxFactory(mailboxManager, quotaManager, quotaRootResolver);
-        provisioner = new DefaultMailboxesProvisioner(mailboxManager, new StoreSubscriptionManager(mailboxManager.getMapperFactory()), new DefaultMetricFactory());
+        provisioner = new DefaultMailboxesProvisioner(mailboxManager, new StoreSubscriptionManager(mailboxManager.getMapperFactory(), mailboxManager.getMapperFactory(), mailboxManager.getEventBus()), new DefaultMetricFactory());
 
         getMailboxesMethod = new GetMailboxesMethod(mailboxManager, quotaRootResolver, quotaManager,  mailboxFactory, new DefaultMetricFactory(), provisioner);
     }
diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/http/DefaultMailboxesProvisionerTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/http/DefaultMailboxesProvisionerTest.java
index 03810bdd09..fdb8c49ebe 100644
--- a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/http/DefaultMailboxesProvisionerTest.java
+++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/http/DefaultMailboxesProvisionerTest.java
@@ -52,7 +52,7 @@ public class DefaultMailboxesProvisionerTest {
         session = MailboxSessionUtil.create(USERNAME);
 
         mailboxManager = InMemoryIntegrationResources.defaultResources().getMailboxManager();
-        subscriptionManager = new StoreSubscriptionManager(mailboxManager.getMapperFactory());
+        subscriptionManager = new StoreSubscriptionManager(mailboxManager.getMapperFactory(), mailboxManager.getMapperFactory(), mailboxManager.getEventBus());
         testee = new DefaultMailboxesProvisioner(mailboxManager, subscriptionManager, new RecordingMetricFactory());
     }
 
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/JmapRequests.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/JmapRequests.scala
index dd9cd534c2..4610f4a94f 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/JmapRequests.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/JmapRequests.scala
@@ -156,4 +156,62 @@ object JmapRequests {
       .statusCode(SC_OK)
       .contentType(JSON)
   }
+
+  def subscribe( mailboxId: String) : Unit = {
+    val request =
+      s"""
+         |{
+         |  "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ],
+         |  "methodCalls": [[
+         |    "Mailbox/set", {
+         |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "update": {
+         |        "$mailboxId": {
+         |          "isSubscribed": true
+         |        }
+         |      }
+         |    }, "c1"]
+         |  ]
+         |}
+         |""".stripMargin
+
+    `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(request)
+    .when
+      .post
+    .`then`
+      .log().ifValidationFails()
+      .statusCode(SC_OK)
+      .contentType(JSON)
+  }
+
+  def unSubscribe(mailboxId: String): Unit = {
+    val request =
+      s"""
+         |{
+         |  "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ],
+         |  "methodCalls": [[
+         |    "Mailbox/set", {
+         |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "update": {
+         |        "$mailboxId": {
+         |          "isSubscribed": false
+         |        }
+         |      }
+         |    }, "c1"]
+         |  ]
+         |}
+         |""".stripMargin
+
+    `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(request)
+    .when
+      .post
+    .`then`
+      .log().ifValidationFails()
+      .statusCode(SC_OK)
+      .contentType(JSON)
+  }
 }
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxChangesMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxChangesMethodContract.scala
index dad2ea17ac..24442ab51c 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxChangesMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxChangesMethodContract.scala
@@ -201,6 +201,182 @@ trait MailboxChangesMethodContract {
     }
   }
 
+  @Test
+  def mailboxChangesShouldReturnUpdatedChangesWhenSubscribed(server: GuiceJamesServer): Unit = {
+    val accountId: AccountId = AccountId.fromUsername(BOB)
+    val provisioningState: State = provisionSystemMailboxes(server)
+
+    // create mailbox with isSubscribed = false
+    val mailboxId: String = `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+    .body(
+      """
+        |{
+        |   "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ],
+        |   "methodCalls": [
+        |       [
+        |           "Mailbox/set",
+        |           {
+        |                "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+        |                "create": {
+        |                    "C42": {
+        |                      "name": "myMailbox",
+        |                      "isSubscribed": false
+        |                    }
+        |                }
+        |           },
+        |    "c1"
+        |       ]
+        |   ]
+        |}
+        |""".stripMargin)
+    .when
+      .post
+    .`then`
+      .log().ifValidationFails()
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .jsonPath()
+      .get("methodResponses[0][1].created.C42.id");
+
+    val oldState: State = waitForNextState(server, accountId, provisioningState)
+
+    // change subscription
+    JmapRequests.subscribe(mailboxId)
+
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(
+          s"""{
+             |  "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+             |  "methodCalls": [[
+             |    "Mailbox/changes",
+             |    {
+             |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |      "sinceState": "${oldState.getValue}"
+             |    },
+             |    "c1"]]
+             |}""".stripMargin)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
+
+      assertThatJson(response)
+        .whenIgnoringPaths("methodResponses[0][1].newState")
+        .withOptions(new Options(IGNORING_ARRAY_ORDER))
+        .isEqualTo(
+          s"""{
+             |    "sessionState": "${SESSION_STATE.value}",
+             |    "methodResponses": [
+             |      [ "Mailbox/changes", {
+             |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |        "oldState": "${oldState.getValue}",
+             |        "hasMoreChanges": false,
+             |        "updatedProperties": null,
+             |        "created": [],
+             |        "updated": ["$mailboxId"],
+             |        "destroyed": []
+             |      }, "c1"]
+             |    ]
+             |}""".stripMargin)
+    }
+  }
+
+  @Test
+  def mailboxChangesShouldReturnUpdatedChangesWhenUnSubscribed(server: GuiceJamesServer): Unit = {
+    val accountId: AccountId = AccountId.fromUsername(BOB)
+    val provisioningState: State = provisionSystemMailboxes(server)
+
+    // create mailbox with isSubscribed = true
+    val mailboxId: String = `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(
+        """
+          |{
+          |   "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ],
+          |   "methodCalls": [
+          |       [
+          |           "Mailbox/set",
+          |           {
+          |                "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+          |                "create": {
+          |                    "C42": {
+          |                      "name": "myMailbox",
+          |                      "isSubscribed": true
+          |                    }
+          |                }
+          |           },
+          |    "c1"
+          |       ]
+          |   ]
+          |}
+          |""".stripMargin)
+    .when
+      .post
+    .`then`
+      .log().ifValidationFails()
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .jsonPath()
+      .get("methodResponses[0][1].created.C42.id");
+
+    val oldState: State = waitForNextState(server, accountId, provisioningState)
+
+    // change subscription
+    JmapRequests.unSubscribe(mailboxId)
+
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(
+          s"""{
+             |  "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+             |  "methodCalls": [[
+             |    "Mailbox/changes",
+             |    {
+             |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |      "sinceState": "${oldState.getValue}"
+             |    },
+             |    "c1"]]
+             |}""".stripMargin)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
+
+      assertThatJson(response)
+        .whenIgnoringPaths("methodResponses[0][1].newState")
+        .withOptions(new Options(IGNORING_ARRAY_ORDER))
+        .isEqualTo(
+          s"""{
+             |    "sessionState": "${SESSION_STATE.value}",
+             |    "methodResponses": [
+             |      [ "Mailbox/changes", {
+             |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |        "oldState": "${oldState.getValue}",
+             |        "hasMoreChanges": false,
+             |        "updatedProperties": null,
+             |        "created": [],
+             |        "updated": ["$mailboxId"],
+             |        "destroyed": []
+             |      }, "c1"]
+             |    ]
+             |}""".stripMargin)
+    }
+  }
+
   @Test
   def mailboxChangesShouldReturnUpdatedChangesWhenAppendMessageToMailbox(server: GuiceJamesServer): Unit = {
     val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxSetMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxSetMethodContract.scala
index bed70285a0..ba755bedfc 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxSetMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxSetMethodContract.scala
@@ -19,9 +19,6 @@
 
 package org.apache.james.jmap.rfc8621.contract
 
-import java.nio.charset.StandardCharsets
-import java.time.Duration
-
 import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT
 import io.restassured.RestAssured._
 import io.restassured.http.ContentType.JSON
@@ -32,9 +29,9 @@ import org.apache.http.HttpStatus.SC_OK
 import org.apache.james.GuiceJamesServer
 import org.apache.james.jmap.core.ResponseObject.SESSION_STATE
 import org.apache.james.jmap.core.UuidState.INSTANCE
-import org.apache.james.jmap.draft.MessageIdProbe
+import org.apache.james.jmap.draft.{JmapGuiceProbe, MessageIdProbe}
 import org.apache.james.jmap.http.UserCredential
-import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, ANDRE, BOB, BOB_PASSWORD, CEDRIC, DAVID, DOMAIN, authScheme, baseRequestSpecBuilder}
+import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, ACCOUNT_ID, ANDRE, BOB, BOB_PASSWORD, CEDRIC, DAVID, DOMAIN, authScheme, baseRequestSpecBuilder}
 import org.apache.james.jmap.rfc8621.contract.tags.CategoryTags
 import org.apache.james.mailbox.MessageManager.AppendCommand
 import org.apache.james.mailbox.model.MailboxACL.{EntryKey, Right}
@@ -45,12 +42,34 @@ import org.apache.james.util.concurrency.ConcurrentTestRunner
 import org.apache.james.utils.DataProbeImpl
 import org.assertj.core.api.Assertions.assertThat
 import org.assertj.core.api.{Assertions, SoftAssertions}
+import org.awaitility.Awaitility
 import org.hamcrest.Matchers.{equalTo, hasSize, not}
-import org.junit.jupiter.api.{BeforeEach, Disabled, RepeatedTest, Tag, Test}
+import org.junit.jupiter.api.{BeforeEach, RepeatedTest, Tag, Test}
 import reactor.core.scala.publisher.{SFlux, SMono}
+import reactor.core.scheduler.Schedulers
+import reactor.netty.http.client.HttpClient
+import sttp.capabilities.WebSockets
+import sttp.client3.monad.IdMonad
+import sttp.client3.okhttp.OkHttpSyncBackend
+import sttp.client3.{Identity, SttpBackend, asWebSocket, basicRequest}
+import sttp.model.Uri
+import sttp.monad.MonadError
+import sttp.monad.syntax.MonadErrorOps
+import sttp.ws.WebSocketFrame
+import sttp.ws.WebSocketFrame.Text
+
+import java.net.URI
+import java.nio.charset.StandardCharsets
+import java.time.Duration
+import scala.collection.mutable.ListBuffer
+import scala.jdk.CollectionConverters._
+
 
 trait MailboxSetMethodContract {
 
+  private lazy val backend: SttpBackend[Identity, WebSockets] = OkHttpSyncBackend()
+  private lazy implicit val monadError: MonadError[Identity] = IdMonad
+
   @BeforeEach
   def setUp(server: GuiceJamesServer): Unit = {
     server.getProbe(classOf[DataProbeImpl])
@@ -7981,4 +8000,101 @@ trait MailboxSetMethodContract {
       .statusCode(SC_OK)
       .body("methodResponses[0][1].oldState", not(equalTo(state)))
   }
+
+  @Test
+  def webSocketShouldPushNewMessageWhenChangeSubscriptionOfMailbox(server: GuiceJamesServer): Unit = {
+    val bobPath = MailboxPath.inbox(BOB)
+    val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
+    Thread.sleep(100)
+
+    val socketPort = server.getProbe(classOf[JmapGuiceProbe])
+      .getJmapPort
+      .getValue
+
+    val response: Either[String, List[String]] =
+      basicRequest.get(Uri.apply(new URI(s"ws://127.0.0.1:$socketPort/jmap/ws")))
+        .header("Authorization", "Basic Ym9iQGRvbWFpbi50bGQ6Ym9icGFzc3dvcmQ=")
+        .header("Accept", ACCEPT_RFC8621_VERSION_HEADER)
+        .response(asWebSocket[Identity, List[String]] {
+          ws =>
+            ws.send(WebSocketFrame.text(
+              """{
+                |  "@type": "WebSocketPushEnable",
+                |  "dataTypes": ["Mailbox"]
+                |}""".stripMargin))
+
+            Thread.sleep(100)
+
+            ws.send(WebSocketFrame.text(
+              s"""{
+                 |  "@type": "Request",
+                 |  "id": "req-36",
+                 |  "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+                 |  "methodCalls": [
+                 |    ["Mailbox/set", {
+                 |      "accountId": "$ACCOUNT_ID",
+                 |      "update": {
+                 |        "${mailboxId.serialize}" : {
+                 |           "isSubscribed": true
+                 |        }
+                 |      }
+                 |    }, "c1"]]
+                 |}""".stripMargin))
+
+            List(ws.receive()
+              .map { case t: Text =>
+                t.payload
+              })
+        })
+        .send(backend)
+        .body
+
+    Thread.sleep(200)
+    assertThat(response.toOption.get.asJava)
+      .hasSize(1)
+    assertThat(response.toOption.get.head)
+      .startsWith("{\"@type\":\"StateChange\",\"changed\":{\"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6\":{\"Mailbox\":")
+  }
+
+  @Test
+  def sseShouldHasNewEventWhenChangeSubscribeOfMailbox(server: GuiceJamesServer): Unit = {
+    val port = server.getProbe(classOf[JmapGuiceProbe]).getJmapPort.getValue
+    val mailboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.forUser(BOB, "mailbox12"))
+    Thread.sleep(500)
+
+    val seq = new ListBuffer[String]()
+    HttpClient.create
+      .baseUrl(s"http://127.0.0.1:$port/eventSource?types=*&ping=0&closeAfter=no")
+      .headers(builder => {
+        builder.add("Authorization", "Basic Ym9iQGRvbWFpbi50bGQ6Ym9icGFzc3dvcmQ=")
+        builder.add("Accept", ACCEPT_RFC8621_VERSION_HEADER)
+      })
+      .get()
+      .responseContent()
+      .map(buffer => {
+        val bytes = new Array[Byte](buffer.readableBytes)
+        buffer.readBytes(bytes)
+        new String(bytes, StandardCharsets.UTF_8)
+      })
+      .doOnNext(seq.addOne)
+      .subscribeOn(Schedulers.boundedElastic())
+      .subscribe()
+
+    Awaitility.`with`
+      .pollInterval(Duration.ofMillis(100))
+      .atMost(Duration.ofSeconds(100))
+      .await
+      .untilAsserted { () =>
+        // change subscription
+        JmapRequests.subscribe(mailboxId.serialize())
+        Thread.sleep(200)
+
+        assertThat(seq.asJava)
+          .hasSize(1)
+        assertThat(seq.head)
+          .startsWith("event: state\ndata: {\"@type\":\"StateChange\",\"changed\":{\"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6\":{\"Mailbox\":")
+        assertThat(seq.head).endsWith("\n\n")
+      }
+  }
+
 }
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/MailboxChangeListener.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/MailboxChangeListener.scala
index 7d3b1bb020..9165342cf7 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/MailboxChangeListener.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/MailboxChangeListener.scala
@@ -20,7 +20,6 @@
 package org.apache.james.jmap.change
 
 import java.time.{Clock, ZonedDateTime}
-
 import javax.inject.{Inject, Named}
 import org.apache.james.core.Username
 import org.apache.james.events.Event.EventId
@@ -32,6 +31,7 @@ import org.apache.james.jmap.api.model.{AccountId, State, TypeName}
 import org.apache.james.jmap.change.MailboxChangeListener.LOGGER
 import org.apache.james.jmap.core.UuidState
 import org.apache.james.mailbox.MailboxManager
+import org.apache.james.mailbox.events.MailboxEvents
 import org.apache.james.mailbox.events.MailboxEvents.{Added, Expunged, FlagsUpdated, MailboxACLUpdated, MailboxAdded, MailboxDeletion, MailboxEvent, MailboxRenamed}
 import org.apache.james.mailbox.model.{MailboxACL, MailboxId}
 import org.apache.james.util.ReactorUtils.DEFAULT_CONCURRENCY
@@ -90,6 +90,12 @@ case class MailboxChangeListener @Inject() (@Named(InjectionKeys.JMAP) eventBus:
         getSharees(mailboxId, username)
           .flatMapMany(sharees =>
             SFlux(emailChangeFactory.fromExpunged(expunged, now, sharees.map(_.getIdentifier).map(Username.of).asJava)))
+      case subscribed: MailboxEvents.MailboxSubscribedEvent =>
+        getSharees(mailboxId, username)
+          .flatMapIterable(sharees => mailboxChangeFactory.fromMailboxSubscribed(sharees.asJava, subscribed, now).asScala)
+      case unSubscribed: MailboxEvents.MailboxUnsubscribedEvent =>
+        getSharees(mailboxId, username)
+          .flatMapIterable(sharees => mailboxChangeFactory.fromMailboxUnSubscribed(sharees.asJava, unSubscribed, now).asScala)
     }
   }
 
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/change/MailboxChangeListenerTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/change/MailboxChangeListenerTest.scala
index 26f9309d3e..6c02539e90 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/change/MailboxChangeListenerTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/change/MailboxChangeListenerTest.scala
@@ -19,10 +19,6 @@
 
 package org.apache.james.jmap.change
 
-import java.time.{Clock, ZonedDateTime}
-import java.util
-
-import javax.mail.Flags
 import org.apache.james.events.delivery.InVmEventDelivery
 import org.apache.james.events.{Event, EventBus, EventListener, Group, InVMEventBus, MemoryEventDeadLetters, Registration, RegistrationKey, RetryBackoffConfiguration}
 import org.apache.james.jmap.api.change.{EmailChange, EmailChangeRepository, Limit, MailboxAndEmailChange, MailboxChange, MailboxChangeRepository, State}
@@ -33,13 +29,17 @@ import org.apache.james.mailbox.MessageManager.{AppendCommand, AppendResult, Fla
 import org.apache.james.mailbox.fixture.MailboxFixture.{ALICE, BOB}
 import org.apache.james.mailbox.inmemory.manager.InMemoryIntegrationResources
 import org.apache.james.mailbox.model.{MailboxACL, MailboxId, MailboxPath, MessageRange, TestId, TestMessageId}
-import org.apache.james.mailbox.{MailboxManager, MailboxSessionUtil, MessageManager}
+import org.apache.james.mailbox.store.StoreSubscriptionManager
+import org.apache.james.mailbox.{MailboxManager, MailboxSessionUtil, MessageManager, SubscriptionManager}
 import org.apache.james.metrics.tests.RecordingMetricFactory
 import org.assertj.core.api.Assertions.assertThat
 import org.junit.jupiter.api.{BeforeEach, Nested, Test}
 import org.reactivestreams.Publisher
 import reactor.core.publisher.Mono
 
+import java.time.{Clock, ZonedDateTime}
+import java.util
+import javax.mail.Flags
 import scala.jdk.CollectionConverters._
 import scala.jdk.OptionConverters._
 
@@ -58,6 +58,7 @@ class MailboxChangeListenerTest {
   var stateFactory: State.Factory = _
   var listener: MailboxChangeListener = _
   var clock: Clock = _
+  var subscriptionManager: SubscriptionManager = _
 
   @BeforeEach
   def setUp: Unit = {
@@ -84,6 +85,10 @@ class MailboxChangeListenerTest {
 
       override def reDeliver(group: Group, event: Event): Mono[Void] = Mono.empty()
     }
+
+    subscriptionManager = new StoreSubscriptionManager(resources.getMailboxManager.getMapperFactory,
+      resources.getMailboxManager.getMapperFactory,
+      resources.getEventBus)
     listener = MailboxChangeListener(eventBus, mailboxChangeRepository, mailboxChangeFactory, emailChangeRepository, emailChangeFactory, mailboxManager, clock)
     resources.getEventBus.register(listener)
   }
@@ -252,6 +257,36 @@ class MailboxChangeListenerTest {
       assertThat(mailboxChangeRepository.getSinceState(ACCOUNT_ID, state, None.toJava).block().getDestroyed)
         .containsExactly(inboxId)
     }
+
+    @Test
+    def subscribeMailboxShouldStoreMailboxSubscribedEvent(): Unit = {
+      val mailboxSession = MailboxSessionUtil.create(BOB)
+      val path = MailboxPath.forUser(BOB, "test")
+      val inboxId: MailboxId = mailboxManager.createMailbox(path, mailboxSession).get
+
+      val state = stateFactory.generate()
+      mailboxChangeRepository.save(MailboxChange.builder().accountId(ACCOUNT_ID).state(state).date(ZonedDateTime.now).isCountChange(false).created(List[MailboxId](TestId.of(0)).asJava).build).block()
+
+      subscriptionManager.subscribe(mailboxSession, path)
+      Thread.sleep(200)
+      assertThat(mailboxChangeRepository.getSinceState(ACCOUNT_ID, state, None.toJava).block().getUpdated)
+        .containsExactly(inboxId)
+    }
+
+    @Test
+    def unSubscribeMailboxShouldStoreMailboxUnSubscribedEvent(): Unit = {
+      val mailboxSession = MailboxSessionUtil.create(BOB)
+      val path = MailboxPath.forUser(BOB, "test")
+      val inboxId: MailboxId = mailboxManager.createMailbox(path, mailboxSession).get
+
+      val state = stateFactory.generate()
+      mailboxChangeRepository.save(MailboxChange.builder().accountId(ACCOUNT_ID).state(state).date(ZonedDateTime.now).isCountChange(false).created(List[MailboxId](TestId.of(0)).asJava).build).block()
+
+      subscriptionManager.unsubscribe(mailboxSession, path)
+      Thread.sleep(200)
+      assertThat(mailboxChangeRepository.getSinceState(ACCOUNT_ID, state, None.toJava).block().getUpdated)
+        .containsExactly(inboxId)
+    }
   }
 
   @Nested
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/MailboxesProvisionerTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/MailboxesProvisionerTest.scala
index c9d8400043..688cbce007 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/MailboxesProvisionerTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/MailboxesProvisionerTest.scala
@@ -51,7 +51,7 @@ class MailboxesProvisionerTest {
   def setup(): Unit = {
     session = MailboxSessionUtil.create(USERNAME)
     mailboxManager = InMemoryIntegrationResources.defaultResources.getMailboxManager
-    subscriptionManager = new StoreSubscriptionManager(mailboxManager.getMapperFactory)
+    subscriptionManager = new StoreSubscriptionManager(mailboxManager.getMapperFactory, mailboxManager.getMapperFactory, mailboxManager.getEventBus)
     testee = new MailboxesProvisioner(mailboxManager, subscriptionManager, new RecordingMetricFactory)
   }
 
diff --git a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
index 5973e676a9..0bdb91e8a0 100644
--- a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
+++ b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
@@ -154,7 +154,9 @@ class IMAPServerTest {
             DefaultImapProcessorFactory.createXListSupportingProcessor(
                 memoryIntegrationResources.getMailboxManager(),
                 memoryIntegrationResources.getEventBus(),
-                new StoreSubscriptionManager(memoryIntegrationResources.getMailboxManager().getMapperFactory()),
+                new StoreSubscriptionManager(memoryIntegrationResources.getMailboxManager().getMapperFactory(),
+                    memoryIntegrationResources.getMailboxManager().getMapperFactory(),
+                    memoryIntegrationResources.getMailboxManager().getEventBus()),
                 null,
                 memoryIntegrationResources.getQuotaManager(),
                 memoryIntegrationResources.getQuotaRootResolver(),
diff --git a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/service/SubscribeAllRequestToTaskTest.java b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/service/SubscribeAllRequestToTaskTest.java
index 09bee339bb..414f7bdbb0 100644
--- a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/service/SubscribeAllRequestToTaskTest.java
+++ b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/service/SubscribeAllRequestToTaskTest.java
@@ -99,7 +99,9 @@ class SubscribeAllRequestToTaskTest {
     void setUp() throws Exception {
         InMemoryIntegrationResources inMemoryIntegrationResources = InMemoryIntegrationResources.defaultResources();
         mailboxManager = inMemoryIntegrationResources.getMailboxManager();
-        subscriptionManager = new StoreSubscriptionManager(inMemoryIntegrationResources.getMailboxManager().getMapperFactory());
+        subscriptionManager = new StoreSubscriptionManager(inMemoryIntegrationResources.getMailboxManager().getMapperFactory(),
+            inMemoryIntegrationResources.getMailboxManager().getMapperFactory(),
+            inMemoryIntegrationResources.getMailboxManager().getEventBus());
         DomainList domainList = mock(DomainList.class);
         Mockito.when(domainList.containsDomain(any())).thenReturn(true);
         MemoryUsersRepository usersRepository = MemoryUsersRepository.withVirtualHosting(domainList);


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