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/11/27 07:10:12 UTC

[james-project] branch master updated (57b73a0950 -> f0da59b65a)

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 57b73a0950 JAMES-3862: Switch to SLF4J 2.0.x compatible Log4j Adapter. Copy log4j-core dependency jar to appassembler lib dir. (#1333)
     new 76ae107c38 JAMES-3861 Add `isDelivery` field to Added event
     new 4f6913bdbf JAMES-3861 Add `isDelivery` field to AppendCommand
     new 5793e95a63 JAMES-3861 LocalDelivery sets the `isDelivery` to true
     new 5a7b5c1c43 JAMES-3861 Handle Added event message format migration
     new f0da59b65a JAMES-3861 Propagate EmailDelivery push selection to JMAP level

The 5 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  |   2 +
 .../org/apache/james/mailbox/MessageManager.java   |  24 ++-
 .../apache/james/mailbox/events/MailboxEvents.java |  15 +-
 .../java/org/apache/james/mailbox/EventTest.java   |   7 +-
 .../apache/james/mailbox/MailboxListenerTest.java  |   5 +-
 .../scala/org/apache/james/event/json/DTOs.scala   |   2 +
 .../james/event/json/MailboxEventSerializer.scala  |  15 +-
 .../james/event/json/AddedSerializationTest.java   |  10 +-
 .../james/mailbox/store/StoreMessageIdManager.java |   2 +
 .../james/mailbox/store/StoreMessageManager.java   |  21 +-
 .../james/mailbox/store/event/EventFactory.java    |  15 +-
 .../mailbox/store/MessageIdManagerTestSystem.java  |   3 +
 .../store/ThreadIdGuessingAlgorithmContract.java   |   2 +
 .../search/AbstractMessageSearchIndexTest.java     |   2 +
 .../processor/base/MailboxEventAnalyserTest.java   |   2 +
 .../processor/base/SelectedMailboxImplTest.java    |   2 +
 .../apache/james/jmap/api/change/EmailChange.java  |  24 ++-
 .../jmap/api/change/MailboxAndEmailChange.java     |   2 +
 .../mailets/delivery/MailboxAppenderImpl.java      |   1 +
 .../rfc8621/contract/EventSourceContract.scala     | 206 +++++++++++++++++-
 .../jmap/rfc8621/contract/WebPushContract.scala    | 234 ++++++++++++++++++++-
 .../jmap/rfc8621/contract/WebSocketContract.scala  | 114 +++++++++-
 .../james/jmap/change/MailboxChangeListener.scala  |   2 +-
 .../apache/james/rspamd/RspamdListenerTest.java    |   4 +
 .../spamassassin/SpamAssassinListenerTest.java     |   3 +
 25 files changed, 674 insertions(+), 45 deletions(-)


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


[james-project] 01/05: JAMES-3861 Add `isDelivery` field to Added event

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 76ae107c38a9207f030911ed54eba9da0e12d3fd
Author: Quan Tran <hq...@linagora.com>
AuthorDate: Tue Nov 22 15:23:25 2022 +0700

    JAMES-3861 Add `isDelivery` field to Added event
---
 .../custom/listeners/SetCustomFlagOnBigMessagesTest.java  |  2 ++
 .../org/apache/james/mailbox/events/MailboxEvents.java    | 15 ++++++++++++---
 .../src/test/java/org/apache/james/mailbox/EventTest.java |  7 ++++---
 .../org/apache/james/mailbox/MailboxListenerTest.java     |  5 +++--
 .../src/main/scala/org/apache/james/event/json/DTOs.scala |  2 ++
 .../apache/james/event/json/MailboxEventSerializer.scala  | 13 ++++++++++---
 .../apache/james/event/json/AddedSerializationTest.java   | 11 ++++++++---
 .../apache/james/mailbox/store/StoreMessageIdManager.java |  2 ++
 .../apache/james/mailbox/store/StoreMessageManager.java   |  4 ++++
 .../apache/james/mailbox/store/event/EventFactory.java    | 15 +++++++++++----
 .../james/mailbox/store/MessageIdManagerTestSystem.java   |  3 +++
 .../mailbox/store/ThreadIdGuessingAlgorithmContract.java  |  2 ++
 .../store/search/AbstractMessageSearchIndexTest.java      |  2 ++
 .../imap/processor/base/MailboxEventAnalyserTest.java     |  2 ++
 .../imap/processor/base/SelectedMailboxImplTest.java      |  2 ++
 .../java/org/apache/james/rspamd/RspamdListenerTest.java  |  4 ++++
 .../james/spamassassin/SpamAssassinListenerTest.java      |  3 +++
 17 files changed, 76 insertions(+), 18 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 1c5bd5a6fb..04a0f84e37 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
@@ -21,6 +21,7 @@ package org.apache.james.examples.custom.listeners;
 
 import static org.apache.james.examples.custom.listeners.SetCustomFlagOnBigMessages.BIG_MESSAGE;
 import static org.apache.james.examples.custom.listeners.SetCustomFlagOnBigMessages.ONE_MB;
+import static org.apache.james.mailbox.events.MailboxEvents.Added.IS_DELIVERY;
 import static org.assertj.core.api.Assertions.assertThat;
 
 import java.nio.charset.StandardCharsets;
@@ -136,6 +137,7 @@ class SetCustomFlagOnBigMessagesTest {
             .mailboxId(inboxId)
             .mailboxPath(INBOX_PATH)
             .addMetaData(oneMBMetaData)
+            .isDelivery(!IS_DELIVERY)
             .build();
 
         testee.event(eventWithAFakeMessageSize);
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 42853de960..3a45e0eff6 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
@@ -526,12 +526,16 @@ public interface MailboxEvents {
      * A mailbox event related to added message
      */
     class Added extends MetaDataHoldingEvent {
+        public static boolean IS_DELIVERY = true;
+
         private final Map<MessageUid, MessageMetaData> added;
+        private final boolean isDelivery;
 
         public Added(MailboxSession.SessionId sessionId, Username username, MailboxPath path, MailboxId mailboxId,
-                     SortedMap<MessageUid, MessageMetaData> uids, EventId eventId) {
+                     SortedMap<MessageUid, MessageMetaData> uids, EventId eventId, boolean isDelivery) {
             super(sessionId, username, path, mailboxId, eventId);
             this.added = ImmutableMap.copyOf(uids);
+            this.isDelivery = isDelivery;
         }
 
         /**
@@ -552,6 +556,10 @@ public interface MailboxEvents {
             return added;
         }
 
+        public boolean isDelivery() {
+            return isDelivery;
+        }
+
         @Override
         public boolean isNoop() {
             return added.isEmpty();
@@ -567,14 +575,15 @@ public interface MailboxEvents {
                     && Objects.equals(this.username, that.username)
                     && Objects.equals(this.path, that.path)
                     && Objects.equals(this.mailboxId, that.mailboxId)
-                    && Objects.equals(this.added, that.added);
+                    && Objects.equals(this.added, that.added)
+                    && Objects.equals(this.isDelivery, that.isDelivery);
             }
             return false;
         }
 
         @Override
         public final int hashCode() {
-            return Objects.hash(eventId, sessionId, username, path, mailboxId, added);
+            return Objects.hash(eventId, sessionId, username, path, mailboxId, added, isDelivery);
         }
     }
 
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 8ea960340b..c084becb09 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
@@ -19,6 +19,7 @@
 
 package org.apache.james.mailbox;
 
+import static org.apache.james.mailbox.events.MailboxEvents.Added.IS_DELIVERY;
 import static org.assertj.core.api.Assertions.assertThat;
 
 import java.util.Date;
@@ -57,7 +58,7 @@ class EventTest {
 
     @Test
     void getMessageIdsShouldReturnEmptyWhenAddedEmpty() {
-        Added added = new Added(MailboxSession.SessionId.of(36), BOB, MailboxPath.inbox(BOB), TestId.of(48), ImmutableSortedMap.of(), Event.EventId.of(UUID_1));
+        Added added = new Added(MailboxSession.SessionId.of(36), BOB, MailboxPath.inbox(BOB), TestId.of(48), ImmutableSortedMap.of(), Event.EventId.of(UUID_1), !IS_DELIVERY);
 
         assertThat(added.getMessageIds()).isEmpty();
     }
@@ -75,7 +76,7 @@ class EventTest {
             ImmutableSortedMap.of(
                 uid1, metaData1,
                 uid2, metaData2),
-            Event.EventId.of(UUID_1));
+            Event.EventId.of(UUID_1), !IS_DELIVERY);
 
         assertThat(added.getMessageIds()).containsOnly(messageId1, messageId2);
     }
@@ -92,7 +93,7 @@ class EventTest {
             ImmutableSortedMap.of(
                 uid1, metaData1,
                 uid2, metaData2),
-            Event.EventId.of(UUID_1));
+            Event.EventId.of(UUID_1), !IS_DELIVERY);
 
         assertThat(added.getMessageIds()).containsExactly(messageId);
     }
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 82b41b9a58..af96c1cdca 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
@@ -19,6 +19,7 @@
 
 package org.apache.james.mailbox;
 
+import static org.apache.james.mailbox.events.MailboxEvents.Added.IS_DELIVERY;
 import static org.assertj.core.api.Assertions.assertThat;
 
 import java.time.Instant;
@@ -161,7 +162,7 @@ class MailboxListenerTest {
     @Test
     void addedShouldBeNoopWhenEmpty() {
         Added added = new Added(SESSION_ID, BOB, PATH, MAILBOX_ID, ImmutableSortedMap.of(),
-            Event.EventId.random());
+            Event.EventId.random(), !IS_DELIVERY);
 
         assertThat(added.isNoop()).isTrue();
     }
@@ -169,7 +170,7 @@ class MailboxListenerTest {
     @Test
     void addedShouldNotBeNoopWhenNotEmpty() {
         Added added = new Added(SESSION_ID, BOB, PATH, MAILBOX_ID, ImmutableSortedMap.of(UID, META_DATA),
-            Event.EventId.random());
+            Event.EventId.random(), !IS_DELIVERY);
 
         assertThat(added.isNoop()).isFalse();
     }
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 f63c48bc9c..33efeac146 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
@@ -101,6 +101,8 @@ object DTOs {
 
   case class UserFlag(value: String) extends AnyVal
 
+  case class IsDelivery(value: Boolean) extends AnyVal
+
   object SystemFlag extends Enumeration {
     type SystemFlag = Value
     val Answered, Deleted, Draft, Flagged, Recent, Seen = Value
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 8825ba4960..433c6c58f6 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
@@ -69,14 +69,15 @@ private object DTO {
   }
 
   case class Added(eventId: EventId, sessionId: SessionId, user: Username, path: MailboxPath, mailboxId: MailboxId,
-                   added: Map[MessageUid, DTOs.MessageMetaData]) extends Event {
+                   added: Map[MessageUid, DTOs.MessageMetaData], isDelivery: IsDelivery) extends Event {
     override def toJava: JavaEvent = new JavaAdded(
       sessionId,
       user,
       path.toJava,
       mailboxId,
       new JavaTreeMap[MessageUid, JavaMessageMetaData](added.view.mapValues(_.toJava).toMap.asJava),
-      eventId)
+      eventId,
+      isDelivery.value)
   }
 
   case class Expunged(eventId: EventId, sessionId: SessionId, user: Username, path: MailboxPath, mailboxId: MailboxId,
@@ -164,7 +165,8 @@ private object ScalaConverter {
     user = event.getUsername,
     path = MailboxPath.fromJava(event.getMailboxPath),
     mailboxId = event.getMailboxId,
-    added = event.getAdded.asScala.view.mapValues(DTOs.MessageMetaData.fromJava).toMap)
+    added = event.getAdded.asScala.view.mapValues(DTOs.MessageMetaData.fromJava).toMap,
+    isDelivery = IsDelivery(event.isDelivery))
 
   private def toScala(event: JavaExpunged): DTO.Expunged = DTO.Expunged(
     eventId = event.getEventId,
@@ -226,6 +228,7 @@ class JsonSerialize(mailboxIdFactory: MailboxId.Factory, messageIdFactory: Messa
   implicit val messageUidWrites: Writes[MessageUid] = value => JsNumber(value.asLong())
   implicit val modSeqWrites: Writes[ModSeq] = value => JsNumber(value.asLong())
   implicit val userFlagWrites: Writes[UserFlag] = value => JsString(value.value)
+  implicit val isDeliveryWrites: Writes[IsDelivery] = value => JsBoolean(value.value)
   implicit val flagWrites: Writes[Flags] = Json.writes[Flags]
   implicit val eventIdWrites: Writes[EventId] = value => JsString(value.getId.toString)
 
@@ -300,6 +303,10 @@ class JsonSerialize(mailboxIdFactory: MailboxId.Factory, messageIdFactory: Messa
     case JsString(x) => JsSuccess(UserFlag(x))
     case _ => JsError()
   }
+  implicit val isDeliveryReads: Reads[IsDelivery] = {
+    case JsBoolean(x) => JsSuccess(IsDelivery(x))
+    case _ => JsError()
+  }
   implicit val eventIdReads: Reads[EventId] = {
     case JsString(x) => JsSuccess(EventId.of(x))
     case _ => JsError()
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 4acc893080..78cfac48f2 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
@@ -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.events.MailboxEvents.Added.IS_DELIVERY;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
@@ -71,9 +72,9 @@ class AddedSerializationTest {
     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))));
 
-    private static final Added DEFAULT_ADDED_EVENT = new Added(SESSION_ID, USERNAME, MAILBOX_PATH, MAILBOX_ID, ADDED, EVENT_ID);
+    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 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);
+        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 = 
         "{" +
         "  \"Added\": {" +
@@ -98,6 +99,7 @@ class AddedSerializationTest {
         "      }" +
         "    }," +
         "    \"sessionId\": 42," +
+        "    \"isDelivery\": false," +
         "    \"user\": \"user\"" +
         "  }" +
         "}";
@@ -125,6 +127,7 @@ class AddedSerializationTest {
             "      }" +
             "    }," +
             "    \"sessionId\": 42," +
+            "    \"isDelivery\": false," +
             "    \"user\": \"user\"" +
             "  }" +
             "}";
@@ -151,6 +154,7 @@ class AddedSerializationTest {
             "      }" +
             "    }," +
             "    \"sessionId\": 42," +
+            "    \"isDelivery\": false," +
             "    \"user\": \"user\"" +
             "  }" +
             "}";
@@ -183,7 +187,7 @@ class AddedSerializationTest {
     class WithEmptyAddedMap {
 
         private final Added emptyAddedEvent = new Added(SESSION_ID, USERNAME, MAILBOX_PATH,
-            MAILBOX_ID, ImmutableSortedMap.of(), EVENT_ID);
+            MAILBOX_ID, ImmutableSortedMap.of(), EVENT_ID, !IS_DELIVERY);
         private final String emptyAddedEventJson =
             "{" +
             "  \"Added\": {" +
@@ -196,6 +200,7 @@ class AddedSerializationTest {
             "    \"mailboxId\": \"18\"," +
             "    \"added\": {}," +
             "    \"sessionId\": 42," +
+            "    \"isDelivery\": false," +
             "    \"user\": \"user\"" +
             "  }" +
             "}";
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageIdManager.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageIdManager.java
index c54151b0cf..99e3c0dda1 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageIdManager.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageIdManager.java
@@ -19,6 +19,7 @@
 
 package org.apache.james.mailbox.store;
 
+import static org.apache.james.mailbox.events.MailboxEvents.Added.IS_DELIVERY;
 import static org.apache.james.util.ReactorUtils.DEFAULT_CONCURRENCY;
 
 import java.util.Collection;
@@ -483,6 +484,7 @@ public class StoreMessageIdManager implements MessageIdManager {
                             .mailboxSession(mailboxSession)
                             .mailbox(mailbox)
                             .addMetaData(metadata)
+                            .isDelivery(!IS_DELIVERY)
                             .build(),
                         new MailboxIdRegistrationKey(mailbox.getMailboxId())));
             }).sneakyThrow())
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java
index 2eea248588..4beb9e6165 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java
@@ -19,6 +19,7 @@
 
 package org.apache.james.mailbox.store;
 
+import static org.apache.james.mailbox.events.MailboxEvents.Added.IS_DELIVERY;
 import static org.apache.james.mailbox.extension.PreDeletionHook.DeleteOperation;
 import static org.apache.james.mailbox.store.mail.AbstractMessageMapper.UNLIMITED;
 import static org.apache.james.util.ReactorUtils.DEFAULT_CONCURRENCY;
@@ -525,6 +526,7 @@ public class StoreMessageManager implements MessageManager {
                             .mailboxSession(mailboxSession)
                             .mailbox(mailbox)
                             .addMetaData(data.getLeft())
+                            .isDelivery(!IS_DELIVERY)
                             .build(),
                         new MailboxIdRegistrationKey(mailbox.getMailboxId()))
                         .thenReturn(computeAppendResult(data, mailbox))),
@@ -906,6 +908,7 @@ public class StoreMessageManager implements MessageManager {
                             .mailboxSession(session)
                             .mailbox(to.getMailboxEntity())
                             .metaData(copiedUids)
+                            .isDelivery(!IS_DELIVERY)
                             .build(),
                         new MailboxIdRegistrationKey(to.getMailboxEntity().getMailboxId())),
                     eventBus.dispatch(EventFactory.moved()
@@ -940,6 +943,7 @@ public class StoreMessageManager implements MessageManager {
                             .mailboxSession(session)
                             .mailbox(to.getMailboxEntity())
                             .metaData(moveUids)
+                            .isDelivery(!IS_DELIVERY)
                             .build(),
                         new MailboxIdRegistrationKey(to.getMailboxEntity().getMailboxId())),
                     eventBus.dispatch(EventFactory.expunged()
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 b1aa4ceed7..a725f0ff0c 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
@@ -144,6 +144,11 @@ public class EventFactory {
         }
     }
 
+    @FunctionalInterface
+    public interface RequireIsDelivery<T> {
+        T isDelivery(boolean isDelivery);
+    }
+
     @FunctionalInterface
     public interface RequireAclDiff<T> {
         T aclDiff(ACLDiff aclDiff);
@@ -227,14 +232,16 @@ public class EventFactory {
         private final Username username;
         private final MailboxSession.SessionId sessionId;
         private final ImmutableSortedMap<MessageUid, MessageMetaData> metaData;
+        private final boolean isDelivery;
 
-        AddedFinalStage(Event.EventId eventId, MailboxPath path, MailboxId mailboxId, Username username, MailboxSession.SessionId sessionId, Map<MessageUid, MessageMetaData> metaData) {
+        AddedFinalStage(Event.EventId eventId, MailboxPath path, MailboxId mailboxId, Username username, MailboxSession.SessionId sessionId, Map<MessageUid, MessageMetaData> metaData, boolean isDelivery) {
             this.eventId = eventId;
             this.path = path;
             this.mailboxId = mailboxId;
             this.username = username;
             this.sessionId = sessionId;
             this.metaData = ImmutableSortedMap.copyOf(metaData);
+            this.isDelivery = isDelivery;
         }
 
         public Added build() {
@@ -244,7 +251,7 @@ public class EventFactory {
             Preconditions.checkNotNull(sessionId);
             Preconditions.checkNotNull(metaData);
 
-            return new Added(sessionId, username, path, mailboxId, metaData, eventId);
+            return new Added(sessionId, username, path, mailboxId, metaData, eventId, isDelivery);
         }
     }
 
@@ -420,8 +427,8 @@ public class EventFactory {
         }
     }
 
-    public static RequireMailboxEvent<RequireMetadata<AddedFinalStage>> added() {
-        return eventId -> user -> sessionId -> mailboxId -> path -> metaData -> new AddedFinalStage(eventId, path, mailboxId, user, sessionId, metaData);
+    public static RequireMailboxEvent<RequireMetadata<RequireIsDelivery<AddedFinalStage>>> added() {
+        return eventId -> user -> sessionId -> mailboxId -> path -> metaData -> isDelivery -> new AddedFinalStage(eventId, path, mailboxId, user, sessionId, metaData, isDelivery);
     }
 
     public static RequireMailboxEvent<RequireMetadata<ExpungedFinalStage>> expunged() {
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/MessageIdManagerTestSystem.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/MessageIdManagerTestSystem.java
index fd0e814555..1273b09f3a 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/MessageIdManagerTestSystem.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/MessageIdManagerTestSystem.java
@@ -19,6 +19,8 @@
 
 package org.apache.james.mailbox.store;
 
+import static org.apache.james.mailbox.events.MailboxEvents.Added.IS_DELIVERY;
+
 import java.nio.charset.StandardCharsets;
 import java.util.Date;
 
@@ -92,6 +94,7 @@ public class MessageIdManagerTestSystem {
                 .mailboxSession(mailboxSession)
                 .mailbox(mailbox)
                 .addMetaData(message.metaData())
+                .isDelivery(!IS_DELIVERY)
                 .build(),
                 new MailboxIdRegistrationKey(mailboxId))
             .block();
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/ThreadIdGuessingAlgorithmContract.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/ThreadIdGuessingAlgorithmContract.java
index 1e642f8f78..6346b90dc3 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/ThreadIdGuessingAlgorithmContract.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/ThreadIdGuessingAlgorithmContract.java
@@ -19,6 +19,7 @@
 
 package org.apache.james.mailbox.store;
 
+import static org.apache.james.mailbox.events.MailboxEvents.Added.IS_DELIVERY;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
@@ -338,6 +339,7 @@ public abstract class ThreadIdGuessingAlgorithmContract {
                 .mailboxSession(mailboxSession)
                 .mailbox(mailbox)
                 .addMetaData(messageMetaData)
+                .isDelivery(!IS_DELIVERY)
                 .build(),
             new MailboxIdRegistrationKey(mailbox.getMailboxId())).block();
     }
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/search/AbstractMessageSearchIndexTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/search/AbstractMessageSearchIndexTest.java
index 497da02e09..8d880ad76c 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/search/AbstractMessageSearchIndexTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/search/AbstractMessageSearchIndexTest.java
@@ -20,6 +20,7 @@
 
 package org.apache.james.mailbox.store.search;
 
+import static org.apache.james.mailbox.events.MailboxEvents.Added.IS_DELIVERY;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.awaitility.Durations.ONE_HUNDRED_MILLISECONDS;
@@ -1631,6 +1632,7 @@ public abstract class AbstractMessageSearchIndexTest {
                 .mailboxSession(quanSession)
                 .mailbox(quanMailbox)
                 .addMetaData(messageMetaData)
+                .isDelivery(!IS_DELIVERY)
                 .build(),
             new MailboxIdRegistrationKey(quanMailbox.getMailboxId())).block();
     }
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 b5a750b31f..8dc7aef80f 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
@@ -19,6 +19,7 @@
 
 package org.apache.james.imap.processor.base;
 
+import static org.apache.james.mailbox.events.MailboxEvents.Added.IS_DELIVERY;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
@@ -132,6 +133,7 @@ class MailboxEventAnalyserTest {
         .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())))
+        .isDelivery(!IS_DELIVERY)
         .build();
 
     private SelectedMailboxImpl testee;
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 89ff7e077d..d8bc996eac 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
@@ -23,6 +23,7 @@ import static javax.mail.Flags.Flag.ANSWERED;
 import static javax.mail.Flags.Flag.FLAGGED;
 import static javax.mail.Flags.Flag.RECENT;
 import static javax.mail.Flags.Flag.SEEN;
+import static org.apache.james.mailbox.events.MailboxEvents.Added.IS_DELIVERY;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
@@ -222,6 +223,7 @@ class SelectedMailboxImplTest {
             .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())))
+            .isDelivery(!IS_DELIVERY)
             .build();
     }
 
diff --git a/third-party/rspamd/src/test/java/org/apache/james/rspamd/RspamdListenerTest.java b/third-party/rspamd/src/test/java/org/apache/james/rspamd/RspamdListenerTest.java
index 608e25430f..4d0f6bd481 100644
--- a/third-party/rspamd/src/test/java/org/apache/james/rspamd/RspamdListenerTest.java
+++ b/third-party/rspamd/src/test/java/org/apache/james/rspamd/RspamdListenerTest.java
@@ -20,6 +20,7 @@
 package org.apache.james.rspamd;
 
 
+import static org.apache.james.mailbox.events.MailboxEvents.Added.IS_DELIVERY;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
@@ -295,6 +296,7 @@ public class RspamdListenerTest {
             .mailboxSession(MAILBOX_SESSION)
             .mailbox(inbox)
             .addMetaData(message.metaData())
+            .isDelivery(!IS_DELIVERY)
             .build();
 
         listener.event(addedEvent);
@@ -316,6 +318,7 @@ public class RspamdListenerTest {
             .mailboxSession(MAILBOX_SESSION)
             .mailbox(inbox)
             .addMetaData(message.metaData())
+            .isDelivery(!IS_DELIVERY)
             .build();
 
         listener.event(addedEvent);
@@ -333,6 +336,7 @@ public class RspamdListenerTest {
             .mailboxSession(MAILBOX_SESSION)
             .mailbox(mailbox1)
             .addMetaData(message.metaData())
+            .isDelivery(!IS_DELIVERY)
             .build();
 
         listener.event(addedEvent);
diff --git a/third-party/spamassassin/src/test/java/org/apache/james/spamassassin/SpamAssassinListenerTest.java b/third-party/spamassassin/src/test/java/org/apache/james/spamassassin/SpamAssassinListenerTest.java
index 37dedae991..c6006aed81 100644
--- a/third-party/spamassassin/src/test/java/org/apache/james/spamassassin/SpamAssassinListenerTest.java
+++ b/third-party/spamassassin/src/test/java/org/apache/james/spamassassin/SpamAssassinListenerTest.java
@@ -20,6 +20,7 @@
 package org.apache.james.spamassassin;
 
 
+import static org.apache.james.mailbox.events.MailboxEvents.Added.IS_DELIVERY;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
@@ -248,6 +249,7 @@ class SpamAssassinListenerTest {
             .mailboxSession(MAILBOX_SESSION)
             .mailbox(inbox)
             .addMetaData(message.metaData())
+            .isDelivery(!IS_DELIVERY)
             .build();
 
         listener.event(addedEvent);
@@ -264,6 +266,7 @@ class SpamAssassinListenerTest {
             .mailboxSession(MAILBOX_SESSION)
             .mailbox(mailbox1)
             .addMetaData(message.metaData())
+            .isDelivery(!IS_DELIVERY)
             .build();
 
         listener.event(addedEvent);


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


[james-project] 02/05: JAMES-3861 Add `isDelivery` field to AppendCommand

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 4f6913bdbffee34a515115fa3239804787575d52
Author: Quan Tran <hq...@linagora.com>
AuthorDate: Tue Nov 22 16:30:33 2022 +0700

    JAMES-3861 Add `isDelivery` field to AppendCommand
---
 .../org/apache/james/mailbox/MessageManager.java   | 24 +++++++++++++++++++++-
 1 file changed, 23 insertions(+), 1 deletion(-)

diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/MessageManager.java b/mailbox/api/src/main/java/org/apache/james/mailbox/MessageManager.java
index ece9c03b75..5a787665c0 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/MessageManager.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/MessageManager.java
@@ -255,12 +255,14 @@ public interface MessageManager {
         public static class Builder {
             private Optional<Date> internalDate;
             private Optional<Boolean> isRecent;
+            private Optional<Boolean> isDelivery;
             private Optional<Flags> flags;
             private Optional<Message> maybeParsedMessage;
 
             private Builder() {
                 this.internalDate = Optional.empty();
                 this.isRecent = Optional.empty();
+                this.isDelivery = Optional.empty();
                 this.flags = Optional.empty();
                 this.maybeParsedMessage = Optional.empty();
             }
@@ -293,6 +295,19 @@ public interface MessageManager {
                 return isRecent(false);
             }
 
+            public Builder isDelivery(boolean isDelivery) {
+                this.isDelivery = Optional.of(isDelivery);
+                return this;
+            }
+
+            public Builder delivery() {
+                return isDelivery(true);
+            }
+
+            public Builder notDelivery() {
+                return isDelivery(false);
+            }
+
             public Builder withParsedMessage(Message message) {
                 this.maybeParsedMessage = Optional.of(message);
                 return this;
@@ -303,6 +318,7 @@ public interface MessageManager {
                     msgIn,
                     internalDate.orElse(new Date()),
                     isRecent.orElse(true),
+                    isDelivery.orElse(false),
                     flags.orElse(new Flags()),
                     maybeParsedMessage);
             }
@@ -350,13 +366,15 @@ public interface MessageManager {
         private final Content msgIn;
         private final Date internalDate;
         private final boolean isRecent;
+        private final boolean isDelivery;
         private final Flags flags;
         private final Optional<Message> maybeParsedMessage;
 
-        private AppendCommand(Content msgIn, Date internalDate, boolean isRecent, Flags flags, Optional<Message> maybeParsedMessage) {
+        private AppendCommand(Content msgIn, Date internalDate, boolean isRecent, boolean isDelivery, Flags flags, Optional<Message> maybeParsedMessage) {
             this.msgIn = msgIn;
             this.internalDate = internalDate;
             this.isRecent = isRecent;
+            this.isDelivery = isDelivery;
             this.flags = flags;
             this.maybeParsedMessage = maybeParsedMessage;
         }
@@ -373,6 +391,10 @@ public interface MessageManager {
             return isRecent;
         }
 
+        public boolean isDelivery() {
+            return isDelivery;
+        }
+
         public Flags getFlags() {
             return flags;
         }


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


[james-project] 05/05: JAMES-3861 Propagate EmailDelivery push selection to JMAP level

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 f0da59b65a846d9c2aae8b6a5f65c92150ad93a8
Author: Quan Tran <hq...@linagora.com>
AuthorDate: Wed Nov 23 16:48:03 2022 +0700

    JAMES-3861 Propagate EmailDelivery push selection to JMAP level
---
 .../apache/james/jmap/api/change/EmailChange.java  |  24 ++-
 .../jmap/api/change/MailboxAndEmailChange.java     |   2 +
 .../rfc8621/contract/EventSourceContract.scala     | 206 +++++++++++++++++-
 .../jmap/rfc8621/contract/WebPushContract.scala    | 234 ++++++++++++++++++++-
 .../jmap/rfc8621/contract/WebSocketContract.scala  | 114 +++++++++-
 .../james/jmap/change/MailboxChangeListener.scala  |   2 +-
 6 files changed, 562 insertions(+), 20 deletions(-)

diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/EmailChange.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/EmailChange.java
index e22d5c2ca9..6c51f6e2fe 100644
--- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/EmailChange.java
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/EmailChange.java
@@ -23,6 +23,7 @@ import java.time.ZonedDateTime;
 import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
+import java.util.Optional;
 
 import org.apache.james.jmap.api.model.AccountId;
 import org.apache.james.mailbox.model.MessageId;
@@ -60,6 +61,7 @@ public class EmailChange implements JmapChange {
         private final ImmutableList.Builder<MessageId> created;
         private final ImmutableList.Builder<MessageId> updated;
         private final ImmutableList.Builder<MessageId> destroyed;
+        private Optional<Boolean> isDelivery;
 
         private Builder(AccountId accountId, State state, ZonedDateTime date, boolean isDelegated) {
             Preconditions.checkNotNull(accountId, "'accountId' should not be null");
@@ -73,6 +75,7 @@ public class EmailChange implements JmapChange {
             this.destroyed = ImmutableList.builder();
             this.updated = ImmutableList.builder();
             this.created = ImmutableList.builder();
+            this.isDelivery = Optional.empty();
         }
 
         public Builder updated(MessageId... messageId) {
@@ -105,8 +108,13 @@ public class EmailChange implements JmapChange {
             return this;
         }
 
+        public Builder isDelivery(boolean isDelivery) {
+            this.isDelivery = Optional.of(isDelivery);
+            return this;
+        }
+
         public EmailChange build() {
-            return new EmailChange(accountId, state, date, isDelegated, created.build(), updated.build(), destroyed.build());
+            return new EmailChange(accountId, state, date, isDelegated, created.build(), updated.build(), destroyed.build(), isDelivery.orElse(false));
         }
     }
 
@@ -121,8 +129,9 @@ public class EmailChange implements JmapChange {
     private final ImmutableList<MessageId> created;
     private final ImmutableList<MessageId> updated;
     private final ImmutableList<MessageId> destroyed;
+    private final boolean isDelivery;
 
-    private EmailChange(AccountId accountId, State state, ZonedDateTime date, boolean isDelegated, ImmutableList<MessageId> created, ImmutableList<MessageId> updated, ImmutableList<MessageId> destroyed) {
+    private EmailChange(AccountId accountId, State state, ZonedDateTime date, boolean isDelegated, ImmutableList<MessageId> created, ImmutableList<MessageId> updated, ImmutableList<MessageId> destroyed, boolean isDelivery) {
         this.accountId = accountId;
         this.state = state;
         this.date = date;
@@ -130,6 +139,7 @@ public class EmailChange implements JmapChange {
         this.created = created;
         this.updated = updated;
         this.destroyed = destroyed;
+        this.isDelivery = isDelivery;
     }
 
     public AccountId getAccountId() {
@@ -160,6 +170,10 @@ public class EmailChange implements JmapChange {
         return isDelegated;
     }
 
+    public boolean isDelivery() {
+        return isDelivery;
+    }
+
     @Override
     public final boolean equals(Object o) {
         if (o instanceof EmailChange) {
@@ -170,14 +184,15 @@ public class EmailChange implements JmapChange {
                 && Objects.equals(isDelegated, that.isDelegated)
                 && Objects.equals(created, that.created)
                 && Objects.equals(updated, that.updated)
-                && Objects.equals(destroyed, that.destroyed);
+                && Objects.equals(destroyed, that.destroyed)
+                && Objects.equals(isDelivery, that.isDelivery);
         }
         return false;
     }
 
     @Override
     public final int hashCode() {
-        return Objects.hash(accountId, state, date, isDelegated, created, updated, destroyed);
+        return Objects.hash(accountId, state, date, isDelegated, created, updated, destroyed, isDelivery);
     }
 
     @Override
@@ -190,6 +205,7 @@ public class EmailChange implements JmapChange {
             .add("created", created)
             .add("updated", updated)
             .add("destroyed", destroyed)
+            .add("isDelivery", isDelivery)
             .toString();
     }
 }
diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxAndEmailChange.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxAndEmailChange.java
index 23a2d94369..fe6761987a 100644
--- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxAndEmailChange.java
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxAndEmailChange.java
@@ -61,6 +61,7 @@ public class MailboxAndEmailChange implements JmapChange {
                 .state(state)
                 .date(now)
                 .isDelegated(false)
+                .isDelivery(messageAdded.isDelivery())
                 .created(messageAdded.getMessageIds())
                 .build();
 
@@ -82,6 +83,7 @@ public class MailboxAndEmailChange implements JmapChange {
                             .state(state)
                             .date(now)
                             .isDelegated(true)
+                            .isDelivery(messageAdded.isDelivery())
                             .created(messageAdded.getMessageIds())
                             .build(),
                         MailboxChange.builder()
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EventSourceContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EventSourceContract.scala
index fea75f9e61..1d24f53c5c 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EventSourceContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EventSourceContract.scala
@@ -20,15 +20,27 @@
 package org.apache.james.jmap.rfc8621.contract
 
 import java.nio.charset.StandardCharsets
+import java.util.concurrent.TimeUnit
 
+import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT
 import io.netty.handler.codec.http.HttpResponseStatus
+import io.restassured.RestAssured.{`given`, requestSpecification}
+import org.apache.http.HttpStatus.SC_OK
 import org.apache.james.GuiceJamesServer
 import org.apache.james.jmap.draft.JmapGuiceProbe
-import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, ANDRE, ANDRE_PASSWORD, BOB, BOB_PASSWORD, DOMAIN}
-import org.apache.james.mailbox.model.MailboxPath
+import org.apache.james.jmap.http.UserCredential
+import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, ACCOUNT_ID, ANDRE, ANDRE_PASSWORD, BOB, BOB_PASSWORD, DOMAIN, authScheme, baseRequestSpecBuilder}
+import org.apache.james.mailbox.DefaultMailboxes
+import org.apache.james.mailbox.MessageManager.AppendCommand
+import org.apache.james.mailbox.model.{MailboxPath, MessageId}
+import org.apache.james.mime4j.dom.Message
 import org.apache.james.modules.MailboxProbeImpl
-import org.apache.james.utils.DataProbeImpl
+import org.apache.james.modules.protocols.SmtpGuiceProbe
+import org.apache.james.utils.{DataProbeImpl, SMTPMessageSender, SpoolerProbe}
 import org.assertj.core.api.Assertions.assertThat
+import org.awaitility.Awaitility
+import org.awaitility.Durations.ONE_HUNDRED_MILLISECONDS
+import org.awaitility.core.ConditionFactory
 import org.junit.jupiter.api.{BeforeEach, Test}
 import reactor.core.scheduler.Schedulers
 import reactor.netty.http.client.HttpClient
@@ -37,6 +49,12 @@ import scala.collection.mutable.ListBuffer
 import scala.jdk.CollectionConverters._
 
 trait EventSourceContract {
+  private lazy val awaitAtMostTenSeconds: ConditionFactory = Awaitility.`with`
+    .pollInterval(ONE_HUNDRED_MILLISECONDS)
+    .and.`with`.pollDelay(ONE_HUNDRED_MILLISECONDS)
+    .await
+    .atMost(10, TimeUnit.SECONDS)
+
   @BeforeEach
   def setUp(server: GuiceJamesServer): Unit = {
     server.getProbe(classOf[DataProbeImpl])
@@ -44,6 +62,11 @@ trait EventSourceContract {
       .addDomain(DOMAIN.asString())
       .addUser(ANDRE.asString(), ANDRE_PASSWORD)
       .addUser(BOB.asString(), BOB_PASSWORD)
+
+    requestSpecification = baseRequestSpecBuilder(server)
+      .setAuth(authScheme(UserCredential(BOB, BOB_PASSWORD)))
+      .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .build()
   }
 
   @Test
@@ -259,6 +282,156 @@ trait EventSourceContract {
     assertThat(seq.head).endsWith("\n\n")
   }
 
+  @Test
+  def shouldPushEmailDeliveryChangeWhenUserReceivesEmail(server: GuiceJamesServer): Unit = {
+    val port = server.getProbe(classOf[JmapGuiceProbe]).getJmapPort.getValue
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+
+    val seq = new ListBuffer[String]()
+    HttpClient.create
+      .baseUrl(s"http://127.0.0.1:$port/eventSource?types=Mailbox,Email,VacationResponse,Thread,Identity,EmailSubmission,EmailDelivery&ping=0&closeAfter=no")
+      .headers(builder => {
+        builder.add("Authorization", "Basic Ym9iQGRvbWFpbi50bGQ6Ym9icGFzc3dvcmQ=")
+        builder.add("Accept", ACCEPT_RFC8621_VERSION_HEADER)
+      })
+      .get()
+      .responseContent()
+      .map(bb => {
+        val bytes = new Array[Byte](bb.readableBytes)
+        bb.readBytes(bytes)
+        new String(bytes, StandardCharsets.UTF_8)
+      })
+      .doOnNext(seq.addOne)
+      .subscribeOn(Schedulers.boundedElastic())
+      .subscribe()
+
+    // Bob receives a mail
+    Thread.sleep(500)
+    sendEmailToBob(server)
+
+    awaitAtMostTenSeconds.untilAsserted(() => {
+      assertThat(seq.asJava)
+        .hasSize(1)
+      assertThat(seq.head)
+        .contains("EmailDelivery")
+    })
+  }
+
+  @Test
+  def shouldNotPushEmailDeliveryChangeWhenUserCreatesDraftEmail(server: GuiceJamesServer): Unit = {
+    val port = server.getProbe(classOf[JmapGuiceProbe]).getJmapPort.getValue
+    val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+
+    val seq = new ListBuffer[String]()
+    HttpClient.create
+      .baseUrl(s"http://127.0.0.1:$port/eventSource?types=Mailbox,Email,VacationResponse,Thread,Identity,EmailSubmission,EmailDelivery&ping=0&closeAfter=no")
+      .headers(builder => {
+        builder.add("Authorization", "Basic Ym9iQGRvbWFpbi50bGQ6Ym9icGFzc3dvcmQ=")
+        builder.add("Accept", ACCEPT_RFC8621_VERSION_HEADER)
+      })
+      .get()
+      .responseContent()
+      .map(bb => {
+        val bytes = new Array[Byte](bb.readableBytes)
+        bb.readBytes(bytes)
+        new String(bytes, StandardCharsets.UTF_8)
+      })
+      .doOnNext(seq.addOne)
+      .subscribeOn(Schedulers.boundedElastic())
+      .subscribe()
+
+    // Bob creates a draft mail
+    Thread.sleep(500)
+    val request =
+      s"""{
+         |  "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [
+         |    ["Email/set", {
+         |      "accountId": "$ACCOUNT_ID",
+         |      "create": {
+         |        "aaaaaa":{
+         |          "mailboxIds": {
+         |             "${mailboxId.serialize}": true
+         |          },
+         |          "to": [{"email": "rcpt1@apache.org"}, {"email": "rcpt2@apache.org"}],
+         |          "from": [{"email": "${BOB.asString}"}]
+         |        }
+         |      }
+         |    }, "c1"]]
+         |}""".stripMargin
+
+    `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(request)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+
+    awaitAtMostTenSeconds.untilAsserted(() => {
+      assertThat(seq.asJava)
+        .hasSize(1)
+      assertThat(seq.head)
+        .doesNotContain("EmailDelivery")
+    })
+  }
+
+  @Test
+  def shouldNotPushEmailDeliveryChangeWhenUserSendsEmail(server: GuiceJamesServer): Unit = {
+    val port = server.getProbe(classOf[JmapGuiceProbe]).getJmapPort.getValue
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+
+    val messageId: MessageId = prepareDraftMessage(server)
+
+    val seq = new ListBuffer[String]()
+    HttpClient.create
+      .baseUrl(s"http://127.0.0.1:$port/eventSource?types=Mailbox,Email,VacationResponse,Thread,Identity,EmailSubmission,EmailDelivery&ping=0&closeAfter=no")
+      .headers(builder => {
+        builder.add("Authorization", "Basic Ym9iQGRvbWFpbi50bGQ6Ym9icGFzc3dvcmQ=")
+        builder.add("Accept", ACCEPT_RFC8621_VERSION_HEADER)
+      })
+      .get()
+      .responseContent()
+      .map(bb => {
+        val bytes = new Array[Byte](bb.readableBytes)
+        bb.readBytes(bytes)
+        new String(bytes, StandardCharsets.UTF_8)
+      })
+      .doOnNext(seq.addOne)
+      .subscribeOn(Schedulers.boundedElastic())
+      .subscribe()
+
+    // WHEN Bob sends an email to Andre
+    Thread.sleep(500)
+    val requestBob =
+      s"""{
+         |  "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail", "urn:ietf:params:jmap:submission"],
+         |  "methodCalls": [
+         |     ["EmailSubmission/set", {
+         |       "accountId": "$ACCOUNT_ID",
+         |       "create": {
+         |         "k1490": {
+         |           "emailId": "${messageId.serialize}",
+         |           "envelope": {
+         |             "mailFrom": {"email": "${BOB.asString}"},
+         |             "rcptTo": [{"email": "${ANDRE.asString}"}]
+         |           }
+         |         }
+         |    }
+         |  }, "c1"]]
+         |}""".stripMargin
+
+    `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(requestBob)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+
+    awaitAtMostTenSeconds.untilAsserted(() => assertThat(seq.asJava).hasSize(0))
+  }
+
   @Test
   def pingShouldBeSupported(server: GuiceJamesServer): Unit = {
     val port = server.getProbe(classOf[JmapGuiceProbe]).getJmapPort.getValue
@@ -384,4 +557,31 @@ trait EventSourceContract {
     assertThat(seq.asJava)
       .hasSize(2)
   }
+
+  private def sendEmailToBob(server: GuiceJamesServer): Unit = {
+    val smtpMessageSender: SMTPMessageSender = new SMTPMessageSender(DOMAIN.asString())
+    smtpMessageSender.connect("127.0.0.1", server.getProbe(classOf[SmtpGuiceProbe]).getSmtpPort)
+      .authenticate(ANDRE.asString, ANDRE_PASSWORD)
+      .sendMessage(ANDRE.asString, BOB.asString())
+    smtpMessageSender.close()
+
+    awaitAtMostTenSeconds.until(() => server.getProbe(classOf[SpoolerProbe]).processingFinished())
+  }
+
+  private def prepareDraftMessage(server: GuiceJamesServer) = {
+    val message: Message = Message.Builder
+      .of
+      .setSubject("test")
+      .setSender(BOB.asString)
+      .setFrom(BOB.asString)
+      .setTo(ANDRE.asString)
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+    val bobDraftsPath = MailboxPath.forUser(BOB, DefaultMailboxes.DRAFTS)
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobDraftsPath)
+    val messageId = server.getProbe(classOf[MailboxProbeImpl]).appendMessage(BOB.asString(), bobDraftsPath, AppendCommand.builder()
+      .build(message))
+      .getMessageId
+    messageId
+  }
 }
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/WebPushContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/WebPushContract.scala
index 33b9ef4c6d..599150875e 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/WebPushContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/WebPushContract.scala
@@ -19,6 +19,14 @@
 
 package org.apache.james.jmap.rfc8621.contract
 
+import java.nio.charset.StandardCharsets
+import java.security.KeyPair
+import java.security.interfaces.{ECPrivateKey, ECPublicKey}
+import java.time.temporal.ChronoUnit
+import java.util.Base64
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.atomic.AtomicReference
+
 import com.google.crypto.tink.apps.webpush.WebPushHybridDecrypt
 import com.google.crypto.tink.subtle.EllipticCurves
 import com.google.crypto.tink.subtle.EllipticCurves.CurveType
@@ -33,7 +41,10 @@ import org.apache.james.jmap.core.ResponseObject.SESSION_STATE
 import org.apache.james.jmap.http.UserCredential
 import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, ACCOUNT_ID, ANDRE, ANDRE_PASSWORD, BOB, BOB_PASSWORD, DOMAIN, authScheme, baseRequestSpecBuilder}
 import org.apache.james.jmap.rfc8621.contract.tags.CategoryTags
-import org.apache.james.mailbox.model.MailboxPath
+import org.apache.james.mailbox.DefaultMailboxes
+import org.apache.james.mailbox.MessageManager.AppendCommand
+import org.apache.james.mailbox.model.{MailboxConstants, MailboxPath, MessageId}
+import org.apache.james.mime4j.dom.Message
 import org.apache.james.modules.MailboxProbeImpl
 import org.apache.james.modules.protocols.SmtpGuiceProbe
 import org.apache.james.utils.{DataProbeImpl, SMTPMessageSender, SpoolerProbe, UpdatableTickingClock}
@@ -51,14 +62,6 @@ import org.mockserver.model.{HttpRequest, HttpResponse}
 import org.mockserver.verify.VerificationTimes
 import play.api.libs.json.{JsObject, JsString, Json}
 
-import java.nio.charset.StandardCharsets
-import java.security.KeyPair
-import java.security.interfaces.{ECPrivateKey, ECPublicKey}
-import java.time.temporal.ChronoUnit
-import java.util.Base64
-import java.util.concurrent.TimeUnit
-import java.util.concurrent.atomic.AtomicReference
-
 trait WebPushContract {
   private lazy val awaitAtMostTenSeconds: ConditionFactory = Awaitility.`with`
     .pollInterval(ONE_HUNDRED_MILLISECONDS)
@@ -239,6 +242,128 @@ trait WebPushContract {
     }
   }
 
+  @Test
+  def shouldPushEmailDeliveryChangeWhenUserReceivesEmail(server: GuiceJamesServer, pushServer: ClientAndServer): Unit = {
+    setupPushSubscriptionForBob(pushServer)
+
+    // WHEN bob receives a mail
+    sendEmailToBob(server)
+
+    // THEN bob has a EmailDelivery stateChange on the push gateway
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      pushServer.verify(HttpRequest.request()
+        .withPath(PUSH_URL_PATH)
+        .withBody(json(
+          s"""{
+             |    "@type": "StateChange",
+             |    "changed": {
+             |        "$ACCOUNT_ID": {
+             |          "EmailDelivery": "$${json-unit.any-string}"
+             |        }
+             |    }
+             |}""".stripMargin)),
+        VerificationTimes.atLeast(1))
+    }
+  }
+
+  @Test
+  def shouldNotPushEmailDeliveryChangeWhenUserCreatesDraftEmail(server: GuiceJamesServer, pushServer: ClientAndServer): Unit = {
+    setupPushSubscriptionForBob(pushServer)
+
+    // WHEN bob create a draft mail
+    val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).getMailboxId("#private", BOB.asString(), MailboxConstants.INBOX)
+    val request =
+      s"""{
+         |  "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [
+         |    ["Email/set", {
+         |      "accountId": "$ACCOUNT_ID",
+         |      "create": {
+         |        "aaaaaa":{
+         |          "mailboxIds": {
+         |             "${mailboxId.serialize}": true
+         |          },
+         |          "to": [{"email": "rcpt1@apache.org"}, {"email": "rcpt2@apache.org"}],
+         |          "from": [{"email": "${BOB.asString}"}]
+         |        }
+         |      }
+         |    }, "c1"]]
+         |}""".stripMargin
+
+    `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(request)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+
+    // THEN bob should not have a EmailDelivery stateChange on the push gateway
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      pushServer.verify(HttpRequest.request()
+        .withPath(PUSH_URL_PATH)
+        .withBody(json(
+          s"""{
+             |    "@type": "StateChange",
+             |    "changed": {
+             |        "$ACCOUNT_ID": {
+             |          "EmailDelivery": "$${json-unit.any-string}"
+             |        }
+             |    }
+             |}""".stripMargin)),
+        VerificationTimes.never())
+    }
+  }
+
+  @Test
+  def shouldNotPushEmailDeliveryChangeWhenUserSendsEmail(server: GuiceJamesServer, pushServer: ClientAndServer): Unit = {
+    val messageId: MessageId = prepareDraftMessage(server)
+    setupPushSubscriptionForBob(pushServer)
+
+    // WHEN Bob sends an email to Andre
+    val requestBob =
+      s"""{
+         |  "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail", "urn:ietf:params:jmap:submission"],
+         |  "methodCalls": [
+         |     ["EmailSubmission/set", {
+         |       "accountId": "$ACCOUNT_ID",
+         |       "create": {
+         |         "k1490": {
+         |           "emailId": "${messageId.serialize}",
+         |           "envelope": {
+         |             "mailFrom": {"email": "${BOB.asString}"},
+         |             "rcptTo": [{"email": "${ANDRE.asString}"}]
+         |           }
+         |         }
+         |    }
+         |  }, "c1"]]
+         |}""".stripMargin
+
+    `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(requestBob)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+
+    // THEN bob should not have a EmailDelivery stateChange on the push gateway
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      pushServer.verify(HttpRequest.request()
+        .withPath(PUSH_URL_PATH)
+        .withBody(json(
+          s"""{
+             |    "@type": "StateChange",
+             |    "changed": {
+             |        "$ACCOUNT_ID": {
+             |          "EmailDelivery": "$${json-unit.any-string}"
+             |        }
+             |    }
+             |}""".stripMargin)),
+        VerificationTimes.never())
+    }
+  }
+
   @Test
   def webPushShouldNotPushToPushServerWhenExpiredSubscription(server: GuiceJamesServer, pushServer: ClientAndServer, clock: UpdatableTickingClock): Unit = {
     // Setup mock-server for callback
@@ -534,4 +659,95 @@ trait WebPushContract {
            |    }
            |}""".stripMargin)
   }
+
+  private def prepareDraftMessage(server: GuiceJamesServer) = {
+    val message: Message = Message.Builder
+      .of
+      .setSubject("test")
+      .setSender(BOB.asString)
+      .setFrom(BOB.asString)
+      .setTo(ANDRE.asString)
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+    val bobDraftsPath = MailboxPath.forUser(BOB, DefaultMailboxes.DRAFTS)
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobDraftsPath)
+    val messageId: MessageId = server.getProbe(classOf[MailboxProbeImpl]).appendMessage(BOB.asString(), bobDraftsPath, AppendCommand.builder()
+      .build(message))
+      .getMessageId
+    messageId
+  }
+
+  private def setupPushSubscriptionForBob(pushServer: ClientAndServer) = {
+    // Setup mock-server for callback
+    val bodyRequestOnPushServer: AtomicReference[String] = setupPushServerCallback(pushServer)
+
+    // WHEN bob creates a push subscription
+    val pushSubscriptionId: String = `given`
+      .body(
+        s"""{
+           |    "using": ["urn:ietf:params:jmap:core"],
+           |    "methodCalls": [
+           |      [
+           |        "PushSubscription/set",
+           |        {
+           |            "create": {
+           |                "4f29": {
+           |                  "deviceClientId": "a889-ffea-910",
+           |                  "url": "${getPushServerUrl(pushServer)}",
+           |                  "types": ["EmailDelivery"]
+           |                }
+           |              }
+           |        },
+           |        "c1"
+           |      ]
+           |    ]
+           |  }""".stripMargin)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .extract()
+      .jsonPath()
+      .get("methodResponses[0][1].created.4f29.id")
+
+    // THEN a validation code is sent
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      pushServer.verify(HttpRequest.request()
+        .withPath(PUSH_URL_PATH)
+        .withBody(json(
+          s"""{
+             |    "@type": "PushVerification",
+             |    "pushSubscriptionId": "$pushSubscriptionId",
+             |    "verificationCode": "$${json-unit.any-string}"
+             |}""".stripMargin)),
+        VerificationTimes.atLeast(1))
+    }
+
+    // GIVEN bob retrieves the validation code from the mock server
+    val verificationCode: String = Json.parse(bodyRequestOnPushServer.get()).asInstanceOf[JsObject]
+      .value("verificationCode")
+      .asInstanceOf[JsString]
+      .value
+
+    // WHEN bob updates the validation code via JMAP
+    val updateVerificationCodeResponse: String = updateValidateVerificationCode(pushSubscriptionId, verificationCode)
+
+    // THEN it succeed
+    assertThatJson(updateVerificationCodeResponse)
+      .isEqualTo(
+        s"""{
+           |    "sessionState": "${SESSION_STATE.value}",
+           |    "methodResponses": [
+           |        [
+           |            "PushSubscription/set",
+           |            {
+           |                "updated": {
+           |                    "$pushSubscriptionId": {}
+           |                }
+           |            },
+           |            "c1"
+           |        ]
+           |    ]
+           |}""".stripMargin)
+  }
 }
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/WebSocketContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/WebSocketContract.scala
index 0cf1d3280c..6980fc1e71 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/WebSocketContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/WebSocketContract.scala
@@ -20,6 +20,7 @@ package org.apache.james.jmap.rfc8621.contract
 
 import java.net.{ProtocolException, URI}
 import java.nio.charset.StandardCharsets
+import java.util.concurrent.TimeUnit
 
 import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
 import org.apache.james.GuiceJamesServer
@@ -32,9 +33,13 @@ import org.apache.james.mailbox.MessageManager.AppendCommand
 import org.apache.james.mailbox.model.MailboxACL.Right
 import org.apache.james.mailbox.model.{MailboxACL, MailboxPath}
 import org.apache.james.mime4j.dom.Message
+import org.apache.james.modules.protocols.SmtpGuiceProbe
 import org.apache.james.modules.{ACLProbeImpl, MailboxProbeImpl}
-import org.apache.james.utils.DataProbeImpl
+import org.apache.james.utils.{DataProbeImpl, SMTPMessageSender, SpoolerProbe}
 import org.assertj.core.api.Assertions.{assertThat, assertThatThrownBy}
+import org.awaitility.Awaitility
+import org.awaitility.Durations.ONE_HUNDRED_MILLISECONDS
+import org.awaitility.core.ConditionFactory
 import org.junit.jupiter.api.{BeforeEach, Test, Timeout}
 import play.api.libs.json.{JsString, Json}
 import reactor.core.scala.publisher.SMono
@@ -52,6 +57,11 @@ import sttp.ws.{WebSocket, WebSocketFrame}
 import scala.jdk.CollectionConverters._
 
 trait WebSocketContract {
+  private lazy val awaitAtMostTenSeconds: ConditionFactory = Awaitility.`with`
+    .pollInterval(ONE_HUNDRED_MILLISECONDS)
+    .and.`with`.pollDelay(ONE_HUNDRED_MILLISECONDS)
+    .await
+    .atMost(10, TimeUnit.SECONDS)
   private lazy val backend: SttpBackend[Identity, WebSockets] = OkHttpSyncBackend()
   private lazy implicit val monadError: MonadError[Identity] = IdMonad
 
@@ -645,7 +655,7 @@ trait WebSocketContract {
     val emailState: State = jmapGuiceProbe.getLatestEmailState(accountId)
 
     val globalState: String = PushState.fromOption(Some(UuidState.fromJava(mailboxState)), Some(UuidState.fromJava(emailState))).get.value
-    val stateChange: String = s"""{"@type":"StateChange","changed":{"$ACCOUNT_ID":{"Email":"${emailState.getValue}","EmailDelivery":"${emailState.getValue}","Mailbox":"${mailboxState.getValue}"}},"pushState":"$globalState"}""".stripMargin
+    val stateChange: String = s"""{"@type":"StateChange","changed":{"$ACCOUNT_ID":{"Email":"${emailState.getValue}","Mailbox":"${mailboxState.getValue}"}},"pushState":"$globalState"}""".stripMargin
 
     assertThat(response.toOption.get.asJava)
       .hasSize(2) // state change notification + API response
@@ -829,13 +839,101 @@ trait WebSocketContract {
     val mailboxState: State = jmapGuiceProbe.getLatestMailboxState(accountId)
 
     val globalState: String = PushState.fromOption(Some(UuidState.fromJava(mailboxState)), Some(UuidState.fromJava(emailState))).get.value
-    val stateChange: String = s"""{"@type":"StateChange","changed":{"$ACCOUNT_ID":{"Email":"${emailState.getValue}","EmailDelivery":"${emailState.getValue}","Mailbox":"${mailboxState.getValue}"}},"pushState":"$globalState"}""".stripMargin
+    val stateChange: String = s"""{"@type":"StateChange","changed":{"$ACCOUNT_ID":{"Email":"${emailState.getValue}","Mailbox":"${mailboxState.getValue}"}},"pushState":"$globalState"}""".stripMargin
 
     assertThat(response.toOption.get.asJava)
       .hasSize(2) // state change notification + API response
       .contains(stateChange)
   }
 
+  @Test
+  @Timeout(180)
+  def shouldPushEmailDeliveryChangeWhenUserReceivesEmail(server: GuiceJamesServer): Unit = {
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+
+    val response: Either[String, List[String]] =
+      authenticatedRequest(server)
+        .response(asWebSocket[Identity, List[String]] {
+          ws =>
+            ws.send(WebSocketFrame.text(
+              """{
+                |  "@type": "WebSocketPushEnable",
+                |  "dataTypes": null
+                |}""".stripMargin))
+
+            Thread.sleep(100)
+
+            // Andre send mail to Bob
+            sendEmailToBob(server)
+
+            List(
+              ws.receive()
+                .map { case t: Text =>
+                  t.payload
+                })
+        })
+        .send(backend)
+        .body
+
+    // Bob should receive EmailDelivery state change
+    assertThat(response.toOption.get.asJava)
+      .hasSize(1) // state change notification
+      .allMatch(s => s.contains("EmailDelivery"))
+  }
+
+  @Test
+  @Timeout(180)
+  def shouldNotPushEmailDeliveryChangeWhenCreateDraftMail(server: GuiceJamesServer): Unit = {
+    val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+
+    val response: Either[String, List[String]] =
+      authenticatedRequest(server)
+        .response(asWebSocket[Identity, List[String]] {
+          ws =>
+            ws.send(WebSocketFrame.text(
+              """{
+                |  "@type": "WebSocketPushEnable",
+                |  "dataTypes": null
+                |}""".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": [
+                 |    ["Email/set", {
+                 |      "accountId": "$ACCOUNT_ID",
+                 |      "create": {
+                 |        "aaaaaa":{
+                 |          "mailboxIds": {
+                 |             "${mailboxId.serialize}": true
+                 |          }
+                 |        }
+                 |      }
+                 |    }, "c1"]]
+                 |}""".stripMargin))
+
+            List(
+              ws.receive()
+                .map { case t: Text =>
+                  t.payload
+                },
+              ws.receive()
+                .map { case t: Text =>
+                  t.payload
+                })
+        })
+        .send(backend)
+        .body
+
+    assertThat(response.toOption.get.asJava)
+      .hasSize(2) // state change notification + API response
+      .noneMatch(s => s.contains("EmailDelivery"))
+  }
+
   @Test
   @Timeout(180)
   def pushEnableShouldUpdatePreviousSubscriptions(server: GuiceJamesServer): Unit = {
@@ -1124,4 +1222,14 @@ trait WebSocketContract {
     basicRequest.get(Uri.apply(new URI(s"ws://127.0.0.1:$port/jmap/ws")))
       .header("Accept", ACCEPT_RFC8621_VERSION_HEADER)
   }
+
+  private def sendEmailToBob(server: GuiceJamesServer): Unit = {
+    val smtpMessageSender: SMTPMessageSender = new SMTPMessageSender(DOMAIN.asString())
+    smtpMessageSender.connect("127.0.0.1", server.getProbe(classOf[SmtpGuiceProbe]).getSmtpPort)
+      .authenticate(ANDRE.asString, ANDRE_PASSWORD)
+      .sendMessage(ANDRE.asString, BOB.asString())
+    smtpMessageSender.close()
+
+    awaitAtMostTenSeconds.until(() => server.getProbe(classOf[SpoolerProbe]).processingFinished())
+  }
 }
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 600437e738..7d3b1bb020 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
@@ -139,7 +139,7 @@ case class MailboxChangeListener @Inject() (@Named(InjectionKeys.JMAP) eventBus:
   private def emailStateMap(emailChange: EmailChange): Map[TypeName, State] =
     (Map(EmailTypeName -> UuidState.fromJava(emailChange.getState)) ++
       Some(UuidState.fromJava(emailChange.getState))
-        .filter(_ => !emailChange.getCreated.isEmpty)
+        .filter(_ => emailChange.isDelivery && !emailChange.getCreated.isEmpty)
         .map(emailDeliveryState => Map(EmailDeliveryTypeName -> emailDeliveryState))
         .getOrElse(Map())).toMap
 }


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


[james-project] 04/05: JAMES-3861 Handle Added event message format migration

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 5a7b5c1c4324d38f4c26e4581796b3afb94cc74a
Author: Quan Tran <hq...@linagora.com>
AuthorDate: Wed Nov 23 09:47:38 2022 +0700

    JAMES-3861 Handle Added event message format migration
    
    `previousAddedFormatShouldBeWellDeserialized` test already exists for this backward
---
 .../org/apache/james/event/json/MailboxEventSerializer.scala      | 8 +++++---
 .../java/org/apache/james/event/json/AddedSerializationTest.java  | 1 -
 2 files changed, 5 insertions(+), 4 deletions(-)

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 433c6c58f6..23cf3d8298 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
@@ -69,7 +69,7 @@ private object DTO {
   }
 
   case class Added(eventId: EventId, sessionId: SessionId, user: Username, path: MailboxPath, mailboxId: MailboxId,
-                   added: Map[MessageUid, DTOs.MessageMetaData], isDelivery: IsDelivery) extends Event {
+                   added: Map[MessageUid, DTOs.MessageMetaData], isDelivery: Option[IsDelivery]) extends Event {
     override def toJava: JavaEvent = new JavaAdded(
       sessionId,
       user,
@@ -77,7 +77,9 @@ private object DTO {
       mailboxId,
       new JavaTreeMap[MessageUid, JavaMessageMetaData](added.view.mapValues(_.toJava).toMap.asJava),
       eventId,
-      isDelivery.value)
+      fallbackIsDelivery())
+
+    def fallbackIsDelivery(): Boolean = isDelivery.exists(_.value)
   }
 
   case class Expunged(eventId: EventId, sessionId: SessionId, user: Username, path: MailboxPath, mailboxId: MailboxId,
@@ -166,7 +168,7 @@ private object ScalaConverter {
     path = MailboxPath.fromJava(event.getMailboxPath),
     mailboxId = event.getMailboxId,
     added = event.getAdded.asScala.view.mapValues(DTOs.MessageMetaData.fromJava).toMap,
-    isDelivery = IsDelivery(event.isDelivery))
+    isDelivery = Option(IsDelivery(event.isDelivery)))
 
   private def toScala(event: JavaExpunged): DTO.Expunged = DTO.Expunged(
     eventId = event.getEventId,
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 78cfac48f2..40df84bb4c 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
@@ -154,7 +154,6 @@ class AddedSerializationTest {
             "      }" +
             "    }," +
             "    \"sessionId\": 42," +
-            "    \"isDelivery\": false," +
             "    \"user\": \"user\"" +
             "  }" +
             "}";


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


[james-project] 03/05: JAMES-3861 LocalDelivery sets the `isDelivery` to true

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 5793e95a63b34d729d18da41208a448c09afba75
Author: Quan Tran <hq...@linagora.com>
AuthorDate: Tue Nov 22 17:30:01 2022 +0700

    JAMES-3861 LocalDelivery sets the `isDelivery` to true
---
 .../james/mailbox/store/StoreMessageManager.java      | 19 ++++++++++++-------
 .../mailets/delivery/MailboxAppenderImpl.java         |  1 +
 2 files changed, 13 insertions(+), 7 deletions(-)

diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java
index 4beb9e6165..4967109955 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java
@@ -327,7 +327,8 @@ public class StoreMessageManager implements MessageManager {
             session,
             appendCommand.isRecent(),
             appendCommand.getFlags(),
-            appendCommand.getMaybeParsedMessage()));
+            appendCommand.getMaybeParsedMessage(),
+            appendCommand.isDelivery()));
     }
 
     @Override
@@ -338,7 +339,8 @@ public class StoreMessageManager implements MessageManager {
             session,
             appendCommand.isRecent(),
             appendCommand.getFlags(),
-            appendCommand.getMaybeParsedMessage());
+            appendCommand.getMaybeParsedMessage(),
+            appendCommand.isDelivery());
     }
 
     @Override
@@ -379,7 +381,7 @@ public class StoreMessageManager implements MessageManager {
                             return finalFile.length();
                         }
                     }, propertyBuilder,
-                    getFlags(mailboxSession, isRecent, flagsToBeSet), bodyStartOctet, unparsedMimeMessqage, headers));
+                    getFlags(mailboxSession, isRecent, flagsToBeSet), bodyStartOctet, unparsedMimeMessqage, headers, !IS_DELIVERY));
             }
         } catch (IOException | MimeException e) {
             throw new MailboxException("Unable to parse message", e);
@@ -395,7 +397,8 @@ public class StoreMessageManager implements MessageManager {
         }
     }
 
-    private Mono<AppendResult> appendMessage(Content msgIn, Date internalDate, final MailboxSession mailboxSession, boolean isRecent, Flags flagsToBeSet, Optional<Message> maybeMessage) {
+    private Mono<AppendResult> appendMessage(Content msgIn, Date internalDate, final MailboxSession mailboxSession, boolean isRecent, Flags flagsToBeSet,
+                                             Optional<Message> maybeMessage, boolean isDelivery) {
         return Mono.fromCallable(() -> {
             if (!isWriteable(mailboxSession)) {
                 throw new ReadOnlyException(getMailboxPath());
@@ -411,7 +414,7 @@ public class StoreMessageManager implements MessageManager {
 
                 return createAndDispatchMessage(computeInternalDate(internalDate),
                     mailboxSession, msgIn, propertyBuilder,
-                    getFlags(mailboxSession, isRecent, flagsToBeSet), bodyStartOctet, maybeMessage, headers);
+                    getFlags(mailboxSession, isRecent, flagsToBeSet), bodyStartOctet, maybeMessage, headers, isDelivery);
             } catch (IOException | MimeException e) {
                 throw new MailboxException("Unable to parse message", e);
             }
@@ -513,7 +516,9 @@ public class StoreMessageManager implements MessageManager {
         return bodyStartOctet;
     }
 
-    private Mono<AppendResult> createAndDispatchMessage(Date internalDate, MailboxSession mailboxSession, Content content, PropertyBuilder propertyBuilder, Flags flags, int bodyStartOctet, Optional<Message> maybeMessage, HeaderImpl headers) throws MailboxException {
+    private Mono<AppendResult> createAndDispatchMessage(Date internalDate, MailboxSession mailboxSession, Content content, PropertyBuilder propertyBuilder,
+                                                        Flags flags, int bodyStartOctet, Optional<Message> maybeMessage, HeaderImpl headers,
+                                                        boolean isDelivery) throws MailboxException {
         int size = (int) content.size();
         QuotaRoot quotaRoot = quotaRootResolver.getQuotaRoot(mailbox);
         return Mono.from(quotaManager.getQuotasReactive(quotaRoot))
@@ -526,7 +531,7 @@ public class StoreMessageManager implements MessageManager {
                             .mailboxSession(mailboxSession)
                             .mailbox(mailbox)
                             .addMetaData(data.getLeft())
-                            .isDelivery(!IS_DELIVERY)
+                            .isDelivery(isDelivery)
                             .build(),
                         new MailboxIdRegistrationKey(mailbox.getMailboxId()))
                         .thenReturn(computeAppendResult(data, mailbox))),
diff --git a/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/delivery/MailboxAppenderImpl.java b/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/delivery/MailboxAppenderImpl.java
index 0f38b8ae4a..5ab493f0ae 100644
--- a/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/delivery/MailboxAppenderImpl.java
+++ b/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/delivery/MailboxAppenderImpl.java
@@ -88,6 +88,7 @@ public class MailboxAppenderImpl implements MailboxAppender {
             .flatMap(mailbox -> Mono.from(mailbox.appendMessageReactive(
                 MessageManager.AppendCommand.builder()
                     .recent()
+                    .delivery()
                     .build(extractContent(mail)),
                 session)));
     }


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