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 2021/06/06 13:59:20 UTC

[james-project] branch master updated (6139ad9 -> b489cb9)

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 6139ad9  JAMES-2683 SimpleConnectionPool should be a guice singleton
     new fd3294e  JAMES-3589 Reasonable tests regarding mailet container execution
     new b489cb9  JAMES-3589 Disable currently failing test

The 2 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:
 .../apache/james/mailets/flow/AddRecipient.java}   |   21 +-
 .../james/mailets/flow/ClearRecipientsMailet.java} |    8 +-
 .../flow/CollectMailAttributeMailet.java}          |   30 +-
 .../mailets/flow/CollectingExecutionMailet.java    |   27 +-
 .../mailets/flow/CollectingExecutionMailetBis.java |   27 +-
 .../mailets/flow/CountingExecutionMailet.java      |   25 +-
 .../mailets/flow/CountingExecutionMailetBis.java   |   25 +-
 .../flow/CountingExecutionTerminatingMailet.java   |   26 +-
 .../flow/EnsureNotDisposed.java}                   |   25 +-
 .../james/mailets/flow/ExecutionFlowTest.java      | 1088 ++++++++++++++++++++
 .../flow/FirstRecipientCountingExecutions.java}    |   25 +-
 .../java/org/apache/james/mailets/flow/None.java   |   14 +-
 .../apache/james/mailets/flow/NoneWithNull.java    |   13 +-
 13 files changed, 1233 insertions(+), 121 deletions(-)
 copy server/{protocols/jmap-draft/src/main/java/org/apache/james/jmap/mailet/SentByJmap.java => mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/AddRecipient.java} (72%)
 copy server/{container/guice/memory-guice/src/test/java/org/apache/james/ErrorMailet.java => mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/ClearRecipientsMailet.java} (87%)
 copy server/mailet/integration-testing/src/test/java/org/apache/james/{transport/mailets/OneRuntimeExceptionMailet.java => mailets/flow/CollectMailAttributeMailet.java} (62%)
 copy mailet/standard/src/main/java/org/apache/james/transport/mailets/debug/Identity.java => server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/CollectingExecutionMailet.java (69%)
 copy mailet/standard/src/main/java/org/apache/james/transport/mailets/debug/Identity.java => server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/CollectingExecutionMailetBis.java (69%)
 copy mailet/standard/src/main/java/org/apache/james/transport/mailets/debug/Identity.java => server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/CountingExecutionMailet.java (76%)
 copy mailet/standard/src/main/java/org/apache/james/transport/mailets/debug/Identity.java => server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/CountingExecutionMailetBis.java (76%)
 copy mailet/standard/src/main/java/org/apache/james/transport/mailets/debug/Identity.java => server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/CountingExecutionTerminatingMailet.java (72%)
 copy server/mailet/integration-testing/src/test/java/org/apache/james/{transport/mailets/OneRuntimeErrorMailet.java => mailets/flow/EnsureNotDisposed.java} (69%)
 create mode 100644 server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/ExecutionFlowTest.java
 copy server/mailet/integration-testing/src/test/java/org/apache/james/{transport/mailets/OneRuntimeExceptionMatcher.java => mailets/flow/FirstRecipientCountingExecutions.java} (71%)
 copy mailet/standard/src/main/java/org/apache/james/transport/matchers/All.java => server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/None.java (88%)
 copy mailet/standard/src/main/java/org/apache/james/transport/matchers/All.java => server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/NoneWithNull.java (88%)

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


[james-project] 02/02: JAMES-3589 Disable currently failing test

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 b489cb909a4e5dd74cf1eb7d4934eb95a4786ddf
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Sat May 22 00:11:02 2021 +0700

    JAMES-3589 Disable currently failing test
    
    Mail.duplicate does not copy state, matched mail is sent back to `root`
    and mailet/matcher prior and at this stage are executed twice. Which feels
    like 'unexpected'
    
    Preserving the state leads to other Camel related issues like lifecycle
    management of the exchange, routing issues, etc.. I did not win that battle.
---
 .../java/org/apache/james/mailets/flow/ExecutionFlowTest.java | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/ExecutionFlowTest.java b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/ExecutionFlowTest.java
index 55b2fbe..6899a1e 100644
--- a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/ExecutionFlowTest.java
+++ b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/ExecutionFlowTest.java
@@ -54,6 +54,7 @@ import org.apache.james.utils.SpoolerProbe;
 import org.apache.james.utils.TestIMAPClient;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
 import org.junit.jupiter.api.io.TempDir;
@@ -86,6 +87,8 @@ public class ExecutionFlowTest {
         }
     }
 
+    @Disabled("JAMES-3589 Mail.duplicate does not copy state, matched mail is sent back to `root` and mailet/matcher prior " +
+        "and at this stage are executed twice.")
     @Test
     public void partialMatchShouldLeadToSingleExecutionOfMailet(@TempDir File temporaryFolder) throws Exception {
         jamesServer = TemporaryJamesServer.builder()
@@ -120,6 +123,8 @@ public class ExecutionFlowTest {
         assertThat(CountingExecutionMailet.executionCount()).isEqualTo(1);
     }
 
+    @Disabled("JAMES-3589 Mail.duplicate does not copy state, matched mail is sent back to `root` and mailet/matcher prior " +
+        "and at this stage are executed twice.")
     @Test
     public void partialMatchShouldLeadToSingleExecutionOfMatcher(@TempDir File temporaryFolder) throws Exception {
         jamesServer = TemporaryJamesServer.builder()
@@ -154,6 +159,8 @@ public class ExecutionFlowTest {
         assertThat(FirstRecipientCountingExecutions.executionCount()).isEqualTo(1);
     }
 
+    @Disabled("JAMES-3589 Mail.duplicate does not copy state, matched mail is sent back to `root` and mailet/matcher prior " +
+        "and at this stage are executed twice.")
     @Test
     public void partialMatchShouldLeadToSingleExecutionOfUpstreamMailet(@TempDir File temporaryFolder) throws Exception {
         jamesServer = TemporaryJamesServer.builder()
@@ -192,6 +199,8 @@ public class ExecutionFlowTest {
         assertThat(CountingExecutionMailet.executionCount()).isEqualTo(1);
     }
 
+    @Disabled("JAMES-3589 Mail.duplicate does not copy state, matched mail is sent back to `root` and mailet/matcher prior " +
+        "and at this stage are executed twice.")
     @Test
     public void partialMatchShouldLeadToSingleExecutionOfUpstreamRootMailets(@TempDir File temporaryFolder) throws Exception {
         jamesServer = TemporaryJamesServer.builder()
@@ -277,6 +286,8 @@ public class ExecutionFlowTest {
         assertThat(CollectMailAttributeMailet.encounteredAttributes()).isEmpty();
     }
 
+    @Disabled("JAMES-3589 Mail.duplicate does not copy state, matched mail is sent back to `root`. As execution" +
+        "is resumed with mutations, mutations are visible to upstream stages.")
     @Test
     public void mutationsOfDownstreamMailetsShouldNotAffectUpStreamMailetsUponSplit(@TempDir File temporaryFolder) throws Exception {
         jamesServer = TemporaryJamesServer.builder()

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


[james-project] 01/02: JAMES-3589 Reasonable tests regarding mailet container execution

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 fd3294e9f992048355a531dc0a815b8fc88adbc1
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Sat May 22 00:04:06 2021 +0700

    JAMES-3589 Reasonable tests regarding mailet container execution
    
    We expect:
     - A mailet to be executed once for each recipients upon partial match and not several time
     - A terminating mailet should not abort processing of previously split mails
     - Mail modifications upon partial match should be retained
     - After a partial match, downstream recipients should not be executed twice for a given recipient
    
     The tests pass b replacing Camel logic by simple Java code:
    
    ```
     public class CamelMailetProcessor extends AbstractStateMailetProcessor implements CamelContextAware {
         private static final Logger LOGGER = LoggerFactory.getLogger(CamelMailetProcessor.class);
    
         private CamelContext context;
    
         private ProducerTemplate producerTemplate;
    
         private final MetricFactory metricFactory;
         private List<MatcherMailetPair> pairs;
    
         public CamelMailetProcessor(MetricFactory metricFactory) {
             this.metricFactory = metricFactory;
         }
    
         @Override
         public void service(Mail mail) throws MessagingException {
             pairs.stream()
                 .reduce(ImmutableList.of(mail), (mails, pair) -> {
                     if (mails.size() > 0) {
                         return executePair(mails, pair);
                     }
                     return ImmutableList.of();
                 }, (a, b) -> {
                     throw new NotImplementedException("Fold left implementation. Should never be called");
                 });
         }
    
         private ImmutableList<Mail> executePair(ImmutableList<Mail> mails, MatcherMailetPair pair) {
             MatcherSplitter matcherSplitter = new MatcherSplitter(metricFactory, this, pair);
             ImmutableList<Mail> afterMatching = mails.stream()
                 .flatMap(Throwing.<Mail, Stream<Mail>>function(m -> matcherSplitter.split(m).stream()).sneakyThrow())
                 .collect(Guavate.toImmutableList());
             afterMatching
                 .stream().filter(mail -> mail.removeAttribute(MATCHER_MATCHED_ATTRIBUTE).isPresent())
                 .forEach(Throwing.<Mail>consumer(m -> new CamelProcessor(metricFactory, this, pair.getMailet())
                     .processMail(m)).sneakyThrow());
    
             afterMatching.stream()
                 .filter(mail -> !mail.getState().equals(getState()))
                 .filter(mail -> !mail.getState().equals(Mail.GHOST))
                 .forEach(Throwing.consumer(this::toProcessor).sneakyThrow());
    
             return afterMatching.stream()
                 .filter(mail -> mail.getState().equals(getState()))
                 .collect(Guavate.toImmutableList());
         }
    
         // Few boiler plate methods
     }
    ```
---
 .../apache/james/mailets/flow/AddRecipient.java    |   40 +
 .../james/mailets/flow/ClearRecipientsMailet.java  |   32 +
 .../mailets/flow/CollectMailAttributeMailet.java   |   48 +
 .../mailets/flow/CollectingExecutionMailet.java    |   44 +
 .../mailets/flow/CollectingExecutionMailetBis.java |   44 +
 .../mailets/flow/CountingExecutionMailet.java      |   42 +
 .../mailets/flow/CountingExecutionMailetBis.java   |   42 +
 .../flow/CountingExecutionTerminatingMailet.java   |   45 +
 .../james/mailets/flow/EnsureNotDisposed.java      |   51 +
 .../james/mailets/flow/ExecutionFlowTest.java      | 1077 ++++++++++++++++++++
 .../flow/FirstRecipientCountingExecutions.java     |   49 +
 .../java/org/apache/james/mailets/flow/None.java   |   34 +
 .../apache/james/mailets/flow/NoneWithNull.java    |   33 +
 13 files changed, 1581 insertions(+)

diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/AddRecipient.java b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/AddRecipient.java
new file mode 100644
index 0000000..d0acc4f
--- /dev/null
+++ b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/AddRecipient.java
@@ -0,0 +1,40 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mailets.flow;
+
+import static org.apache.james.mailets.configuration.Constants.RECIPIENT2;
+
+import javax.mail.internet.AddressException;
+
+import org.apache.james.core.MailAddress;
+import org.apache.mailet.Mail;
+import org.apache.mailet.base.GenericMailet;
+
+import com.google.common.collect.ImmutableList;
+
+public class AddRecipient extends GenericMailet {
+    @Override
+    public void service(Mail mail) throws AddressException {
+        mail.setRecipients(ImmutableList.<MailAddress>builder()
+            .add(new MailAddress(RECIPIENT2))
+            .addAll(mail.getRecipients())
+            .build());
+    }
+}
diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/ClearRecipientsMailet.java b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/ClearRecipientsMailet.java
new file mode 100644
index 0000000..100751e
--- /dev/null
+++ b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/ClearRecipientsMailet.java
@@ -0,0 +1,32 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mailets.flow;
+
+import org.apache.mailet.Mail;
+import org.apache.mailet.base.GenericMailet;
+
+import com.google.common.collect.ImmutableList;
+
+public class ClearRecipientsMailet extends GenericMailet {
+    @Override
+    public void service(Mail mail) {
+        mail.setRecipients(ImmutableList.of());
+    }
+}
diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/CollectMailAttributeMailet.java b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/CollectMailAttributeMailet.java
new file mode 100644
index 0000000..66ecb79
--- /dev/null
+++ b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/CollectMailAttributeMailet.java
@@ -0,0 +1,48 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mailets.flow;
+
+import java.util.List;
+import java.util.concurrent.ConcurrentLinkedDeque;
+
+import org.apache.mailet.AttributeName;
+import org.apache.mailet.Mail;
+import org.apache.mailet.base.GenericMailet;
+import org.testcontainers.shaded.com.google.common.collect.ImmutableList;
+
+public class CollectMailAttributeMailet extends GenericMailet {
+    public static final String MY_ATTRIBUTE = "myAttribute";
+    private static final ConcurrentLinkedDeque<String> encounteredAttributes = new ConcurrentLinkedDeque<>();
+
+    public static void reset() {
+        encounteredAttributes.clear();
+    }
+
+    public static List<String> encounteredAttributes() {
+        return ImmutableList.copyOf(encounteredAttributes);
+    }
+
+    @Override
+    public void service(Mail mail) {
+        mail.getAttribute(AttributeName.of(MY_ATTRIBUTE))
+            .ifPresent(attribute -> encounteredAttributes.add((String) attribute.getValue()
+                .value()));
+    }
+}
diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/CollectingExecutionMailet.java b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/CollectingExecutionMailet.java
new file mode 100644
index 0000000..eea9d07
--- /dev/null
+++ b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/CollectingExecutionMailet.java
@@ -0,0 +1,44 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mailets.flow;
+
+import java.util.List;
+import java.util.concurrent.ConcurrentLinkedDeque;
+
+import org.apache.james.core.MailAddress;
+import org.apache.mailet.Mail;
+import org.apache.mailet.base.GenericMailet;
+
+public class CollectingExecutionMailet extends GenericMailet {
+    private static final ConcurrentLinkedDeque<MailAddress> executedFor = new ConcurrentLinkedDeque<>();
+
+    public static void reset() {
+        executedFor.clear();
+    }
+
+    public static List<MailAddress> executionFor() {
+        return org.testcontainers.shaded.com.google.common.collect.ImmutableList.copyOf(executedFor);
+    }
+
+    @Override
+    public void service(Mail mail) {
+        executedFor.addAll(mail.getRecipients());
+    }
+}
diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/CollectingExecutionMailetBis.java b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/CollectingExecutionMailetBis.java
new file mode 100644
index 0000000..3b1e158
--- /dev/null
+++ b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/CollectingExecutionMailetBis.java
@@ -0,0 +1,44 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mailets.flow;
+
+import java.util.List;
+import java.util.concurrent.ConcurrentLinkedDeque;
+
+import org.apache.james.core.MailAddress;
+import org.apache.mailet.Mail;
+import org.apache.mailet.base.GenericMailet;
+
+public class CollectingExecutionMailetBis extends GenericMailet {
+    private static final ConcurrentLinkedDeque<MailAddress> executedFor = new ConcurrentLinkedDeque<>();
+
+    public static void reset() {
+        executedFor.clear();
+    }
+
+    public static List<MailAddress> executionFor() {
+        return org.testcontainers.shaded.com.google.common.collect.ImmutableList.copyOf(executedFor);
+    }
+
+    @Override
+    public void service(Mail mail) {
+        executedFor.addAll(mail.getRecipients());
+    }
+}
diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/CountingExecutionMailet.java b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/CountingExecutionMailet.java
new file mode 100644
index 0000000..955593d
--- /dev/null
+++ b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/CountingExecutionMailet.java
@@ -0,0 +1,42 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mailets.flow;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.mailet.Mail;
+import org.apache.mailet.base.GenericMailet;
+
+public class CountingExecutionMailet extends GenericMailet {
+    private static final AtomicLong executionCount = new AtomicLong();
+
+    public static void reset() {
+        executionCount.set(0L);
+    }
+
+    public static long executionCount() {
+        return executionCount.get();
+    }
+
+    @Override
+    public void service(Mail mail) {
+        executionCount.incrementAndGet();
+    }
+}
diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/CountingExecutionMailetBis.java b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/CountingExecutionMailetBis.java
new file mode 100644
index 0000000..1543c93
--- /dev/null
+++ b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/CountingExecutionMailetBis.java
@@ -0,0 +1,42 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mailets.flow;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.mailet.Mail;
+import org.apache.mailet.base.GenericMailet;
+
+public class CountingExecutionMailetBis extends GenericMailet {
+    private static final AtomicLong executionCount = new AtomicLong();
+
+    public static void reset() {
+        executionCount.set(0L);
+    }
+
+    public static long executionCount() {
+        return executionCount.get();
+    }
+
+    @Override
+    public void service(Mail mail) {
+        executionCount.incrementAndGet();
+    }
+}
diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/CountingExecutionTerminatingMailet.java b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/CountingExecutionTerminatingMailet.java
new file mode 100644
index 0000000..e1d4ea1
--- /dev/null
+++ b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/CountingExecutionTerminatingMailet.java
@@ -0,0 +1,45 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mailets.flow;
+
+import static org.apache.mailet.Mail.GHOST;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.mailet.Mail;
+import org.apache.mailet.base.GenericMailet;
+
+public class CountingExecutionTerminatingMailet extends GenericMailet {
+    private static final AtomicLong executionCount = new AtomicLong();
+
+    public static void reset() {
+        executionCount.set(0L);
+    }
+
+    public static long executionCount() {
+        return executionCount.get();
+    }
+
+    @Override
+    public void service(Mail mail) {
+        executionCount.incrementAndGet();
+        mail.setState(GHOST);
+    }
+}
diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/EnsureNotDisposed.java b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/EnsureNotDisposed.java
new file mode 100644
index 0000000..8a395f9
--- /dev/null
+++ b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/EnsureNotDisposed.java
@@ -0,0 +1,51 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mailets.flow;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.mail.MessagingException;
+
+import org.apache.mailet.Mail;
+import org.apache.mailet.base.GenericMailet;
+
+public class EnsureNotDisposed extends GenericMailet {
+    private static final AtomicLong executionCount = new AtomicLong();
+
+    public static void reset() {
+        executionCount.set(0L);
+    }
+
+    public static long executionCount() {
+        return executionCount.get();
+    }
+
+    @Override
+    public void service(Mail mail) throws MessagingException {
+        try {
+            Thread.sleep(10);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+        assertThat(mail.getMessage()).isNotNull();
+    }
+}
diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/ExecutionFlowTest.java b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/ExecutionFlowTest.java
new file mode 100644
index 0000000..55b2fbe
--- /dev/null
+++ b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/ExecutionFlowTest.java
@@ -0,0 +1,1077 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mailets.flow;
+
+import static org.apache.james.MemoryJamesServerMain.SMTP_AND_IMAP_MODULE;
+import static org.apache.james.mailets.configuration.Constants.DEFAULT_DOMAIN;
+import static org.apache.james.mailets.configuration.Constants.FROM;
+import static org.apache.james.mailets.configuration.Constants.LOCALHOST_IP;
+import static org.apache.james.mailets.configuration.Constants.PASSWORD;
+import static org.apache.james.mailets.configuration.Constants.RECIPIENT;
+import static org.apache.james.mailets.configuration.Constants.RECIPIENT2;
+import static org.apache.james.mailets.configuration.Constants.awaitAtMostOneMinute;
+import static org.apache.james.utils.TestIMAPClient.INBOX;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.File;
+
+import org.apache.james.core.MailAddress;
+import org.apache.james.mailets.TemporaryJamesServer;
+import org.apache.james.mailets.configuration.CommonProcessors;
+import org.apache.james.mailets.configuration.MailetConfiguration;
+import org.apache.james.mailets.configuration.MailetContainer;
+import org.apache.james.mailets.configuration.ProcessorConfiguration;
+import org.apache.james.modules.protocols.ImapGuiceProbe;
+import org.apache.james.modules.protocols.SmtpGuiceProbe;
+import org.apache.james.transport.mailets.NoopMailet;
+import org.apache.james.transport.mailets.Null;
+import org.apache.james.transport.mailets.PostmasterAlias;
+import org.apache.james.transport.mailets.SetMailAttribute;
+import org.apache.james.transport.mailets.ToProcessor;
+import org.apache.james.transport.matchers.All;
+import org.apache.james.transport.matchers.RecipientIs;
+import org.apache.james.transport.matchers.RelayLimit;
+import org.apache.james.utils.DataProbeImpl;
+import org.apache.james.utils.SMTPMessageSender;
+import org.apache.james.utils.SpoolerProbe;
+import org.apache.james.utils.TestIMAPClient;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.api.io.TempDir;
+
+import com.google.common.collect.ImmutableList;
+
+public class ExecutionFlowTest {
+    @RegisterExtension
+    public SMTPMessageSender smtpMessageSender = new SMTPMessageSender(DEFAULT_DOMAIN);
+    @RegisterExtension
+    public TestIMAPClient testIMAPClient = new TestIMAPClient();
+
+    private TemporaryJamesServer jamesServer;
+
+    @BeforeEach
+    public void test() {
+        CountingExecutionMailet.reset();
+        CountingExecutionMailetBis.reset();
+        CountingExecutionTerminatingMailet.reset();
+        CollectingExecutionMailet.reset();
+        CollectingExecutionMailetBis.reset();
+        CollectMailAttributeMailet.reset();
+        FirstRecipientCountingExecutions.reset();
+    }
+
+    @AfterEach
+    public void tearDown() {
+        if (jamesServer != null) {
+            jamesServer.shutdown();
+        }
+    }
+
+    @Test
+    public void partialMatchShouldLeadToSingleExecutionOfMailet(@TempDir File temporaryFolder) throws Exception {
+        jamesServer = TemporaryJamesServer.builder()
+            .withBase(SMTP_AND_IMAP_MODULE)
+            .withMailetContainer(MailetContainer.builder()
+                .putProcessor(ProcessorConfiguration.transport()
+                        .addMailet(MailetConfiguration.BCC_STRIPPER)
+                        .addMailet(MailetConfiguration.builder()
+                            .matcher(RecipientIs.class)
+                            .matcherCondition(RECIPIENT)
+                            .mailet(CountingExecutionMailet.class)
+                            .build())
+                        .addMailet(MailetConfiguration.LOCAL_DELIVERY))
+                .putProcessor(CommonProcessors.error())
+                .putProcessor(CommonProcessors.root()))
+            .build(temporaryFolder);
+        jamesServer.start();
+        jamesServer.getProbe(DataProbeImpl.class)
+            .fluent()
+            .addDomain(DEFAULT_DOMAIN)
+            .addUser(FROM, PASSWORD)
+            .addUser(RECIPIENT, PASSWORD);
+
+        smtpMessageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+            .authenticate(FROM, PASSWORD)
+            .sendMessage(FROM, ImmutableList.of(FROM, RECIPIENT));
+
+        testIMAPClient.connect(LOCALHOST_IP, jamesServer.getProbe(ImapGuiceProbe.class).getImapPort())
+            .login(RECIPIENT, PASSWORD)
+            .select(INBOX)
+            .awaitMessage(awaitAtMostOneMinute);
+        assertThat(CountingExecutionMailet.executionCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void partialMatchShouldLeadToSingleExecutionOfMatcher(@TempDir File temporaryFolder) throws Exception {
+        jamesServer = TemporaryJamesServer.builder()
+            .withBase(SMTP_AND_IMAP_MODULE)
+            .withMailetContainer(MailetContainer.builder()
+                .putProcessor(ProcessorConfiguration.transport()
+                        .addMailet(MailetConfiguration.BCC_STRIPPER)
+                        .addMailet(MailetConfiguration.builder()
+                            .matcher(FirstRecipientCountingExecutions.class)
+                            .mailet(CountingExecutionMailet.class)
+                            .build())
+                        .addMailet(MailetConfiguration.LOCAL_DELIVERY))
+                .putProcessor(CommonProcessors.error())
+                .putProcessor(CommonProcessors.root()))
+            .build(temporaryFolder);
+        jamesServer.start();
+        jamesServer.getProbe(DataProbeImpl.class)
+            .fluent()
+            .addDomain(DEFAULT_DOMAIN)
+            .addUser(FROM, PASSWORD)
+            .addUser(RECIPIENT, PASSWORD);
+
+        smtpMessageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+            .authenticate(FROM, PASSWORD)
+            .sendMessage(FROM, ImmutableList.of(FROM, RECIPIENT));
+
+
+        Thread.sleep(100); // queue delays might cause the processing not to start straight at the end of the SMTP session
+        awaitAtMostOneMinute.untilAsserted(() -> assertThat(
+            jamesServer.getProbe(SpoolerProbe.class).processingFinished())
+            .isTrue());
+        assertThat(FirstRecipientCountingExecutions.executionCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void partialMatchShouldLeadToSingleExecutionOfUpstreamMailet(@TempDir File temporaryFolder) throws Exception {
+        jamesServer = TemporaryJamesServer.builder()
+            .withBase(SMTP_AND_IMAP_MODULE)
+            .withMailetContainer(MailetContainer.builder()
+                .putProcessor(ProcessorConfiguration.transport()
+                        .addMailet(MailetConfiguration.BCC_STRIPPER)
+                        .addMailet(MailetConfiguration.builder()
+                            .matcher(All.class)
+                            .mailet(CountingExecutionMailet.class)
+                            .build())
+                        .addMailet(MailetConfiguration.builder()
+                            .matcher(RecipientIs.class)
+                            .matcherCondition(RECIPIENT)
+                            .mailet(NoopMailet.class)
+                            .build())
+                        .addMailet(MailetConfiguration.LOCAL_DELIVERY))
+                .putProcessor(CommonProcessors.error())
+                .putProcessor(CommonProcessors.root()))
+            .build(temporaryFolder);
+        jamesServer.start();
+        jamesServer.getProbe(DataProbeImpl.class)
+            .fluent()
+            .addDomain(DEFAULT_DOMAIN)
+            .addUser(FROM, PASSWORD)
+            .addUser(RECIPIENT, PASSWORD);
+
+        smtpMessageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+            .authenticate(FROM, PASSWORD)
+            .sendMessage(FROM, ImmutableList.of(FROM, RECIPIENT));
+
+        testIMAPClient.connect(LOCALHOST_IP, jamesServer.getProbe(ImapGuiceProbe.class).getImapPort())
+            .login(RECIPIENT, PASSWORD)
+            .select(INBOX)
+            .awaitMessage(awaitAtMostOneMinute);
+        assertThat(CountingExecutionMailet.executionCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void partialMatchShouldLeadToSingleExecutionOfUpstreamRootMailets(@TempDir File temporaryFolder) throws Exception {
+        jamesServer = TemporaryJamesServer.builder()
+            .withBase(SMTP_AND_IMAP_MODULE)
+            .withMailetContainer(MailetContainer.builder()
+                .putProcessor(ProcessorConfiguration.transport()
+                        .addMailet(MailetConfiguration.BCC_STRIPPER)
+                        .addMailet(MailetConfiguration.builder()
+                            .matcher(RecipientIs.class)
+                            .matcherCondition(RECIPIENT)
+                            .mailet(NoopMailet.class)
+                            .build())
+                        .addMailet(MailetConfiguration.LOCAL_DELIVERY))
+                .putProcessor(CommonProcessors.error())
+                .putProcessor( ProcessorConfiguration.root()
+                    .enableJmx(false)
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(All.class)
+                        .mailet(PostmasterAlias.class))
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(RelayLimit.class)
+                        .matcherCondition("30")
+                        .mailet(Null.class))
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(All.class)
+                        .mailet(CountingExecutionMailet.class)
+                        .build())
+                    .addMailet(MailetConfiguration.TO_TRANSPORT)))
+            .build(temporaryFolder);
+        jamesServer.start();
+        jamesServer.getProbe(DataProbeImpl.class)
+            .fluent()
+            .addDomain(DEFAULT_DOMAIN)
+            .addUser(FROM, PASSWORD)
+            .addUser(RECIPIENT, PASSWORD);
+
+        smtpMessageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+            .authenticate(FROM, PASSWORD)
+            .sendMessage(FROM, ImmutableList.of(FROM, RECIPIENT));
+
+        testIMAPClient.connect(LOCALHOST_IP, jamesServer.getProbe(ImapGuiceProbe.class).getImapPort())
+            .login(RECIPIENT, PASSWORD)
+            .select(INBOX)
+            .awaitMessage(awaitAtMostOneMinute);
+        assertThat(CountingExecutionMailet.executionCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void mutationsOfDownstreamMailetsShouldNotAffectUpStreamMailets(@TempDir File temporaryFolder) throws Exception {
+        jamesServer = TemporaryJamesServer.builder()
+            .withBase(SMTP_AND_IMAP_MODULE)
+            .withMailetContainer(MailetContainer.builder()
+                .putProcessor(ProcessorConfiguration.transport()
+                        .addMailet(MailetConfiguration.BCC_STRIPPER)
+                        .addMailet(MailetConfiguration.builder()
+                            .matcher(All.class)
+                            .mailet(CollectMailAttributeMailet.class)
+                            .build())
+                        .addMailet(MailetConfiguration.builder()
+                            .matcher(All.class)
+                            .mailet(SetMailAttribute.class)
+                            .addProperty(CollectMailAttributeMailet.MY_ATTRIBUTE, "value1")
+                            .build())
+                        .addMailet(MailetConfiguration.LOCAL_DELIVERY))
+                .putProcessor(CommonProcessors.error())
+                .putProcessor(CommonProcessors.root()))
+            .build(temporaryFolder);
+        jamesServer.start();
+        jamesServer.getProbe(DataProbeImpl.class)
+            .fluent()
+            .addDomain(DEFAULT_DOMAIN)
+            .addUser(FROM, PASSWORD)
+            .addUser(RECIPIENT, PASSWORD);
+
+        smtpMessageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+            .authenticate(FROM, PASSWORD)
+            .sendMessage(FROM, ImmutableList.of(FROM, RECIPIENT));
+
+        testIMAPClient.connect(LOCALHOST_IP, jamesServer.getProbe(ImapGuiceProbe.class).getImapPort())
+            .login(RECIPIENT, PASSWORD)
+            .select(INBOX)
+            .awaitMessage(awaitAtMostOneMinute);
+        assertThat(CollectMailAttributeMailet.encounteredAttributes()).isEmpty();
+    }
+
+    @Test
+    public void mutationsOfDownstreamMailetsShouldNotAffectUpStreamMailetsUponSplit(@TempDir File temporaryFolder) throws Exception {
+        jamesServer = TemporaryJamesServer.builder()
+            .withBase(SMTP_AND_IMAP_MODULE)
+            .withMailetContainer(MailetContainer.builder()
+                .putProcessor(ProcessorConfiguration.transport()
+                        .addMailet(MailetConfiguration.BCC_STRIPPER)
+                        .addMailet(MailetConfiguration.builder()
+                            .matcher(All.class)
+                            .mailet(CollectMailAttributeMailet.class)
+                            .build())
+                        .addMailet(MailetConfiguration.builder()
+                            .matcher(All.class)
+                            .mailet(SetMailAttribute.class)
+                            .addProperty(CollectMailAttributeMailet.MY_ATTRIBUTE, "value1")
+                            .build())
+                        .addMailet(MailetConfiguration.builder()
+                            .matcher(RecipientIs.class)
+                            .matcherCondition(RECIPIENT)
+                            .mailet(NoopMailet.class)
+                            .build())
+                        .addMailet(MailetConfiguration.LOCAL_DELIVERY))
+                .putProcessor(CommonProcessors.error())
+                .putProcessor(CommonProcessors.root()))
+            .build(temporaryFolder);
+        jamesServer.start();
+        jamesServer.getProbe(DataProbeImpl.class)
+            .fluent()
+            .addDomain(DEFAULT_DOMAIN)
+            .addUser(FROM, PASSWORD)
+            .addUser(RECIPIENT, PASSWORD);
+
+        smtpMessageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+            .authenticate(FROM, PASSWORD)
+            .sendMessage(FROM, ImmutableList.of(FROM, RECIPIENT));
+
+        testIMAPClient.connect(LOCALHOST_IP, jamesServer.getProbe(ImapGuiceProbe.class).getImapPort())
+            .login(RECIPIENT, PASSWORD)
+            .select(INBOX)
+            .awaitMessage(awaitAtMostOneMinute);
+        assertThat(CollectMailAttributeMailet.encounteredAttributes()).isEmpty();
+    }
+
+    @Test
+    public void totalMatchShouldNotSplitMail(@TempDir File temporaryFolder) throws Exception {
+        jamesServer = TemporaryJamesServer.builder()
+            .withBase(SMTP_AND_IMAP_MODULE)
+            .withMailetContainer(MailetContainer.builder()
+                .putProcessor(ProcessorConfiguration.transport()
+                    .addMailet(MailetConfiguration.BCC_STRIPPER)
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(All.class)
+                        .mailet(CollectingExecutionMailet.class)
+                        .build())
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(All.class)
+                        .mailet(CountingExecutionMailet.class)
+                        .build())
+                    .addMailet(MailetConfiguration.LOCAL_DELIVERY))
+                .putProcessor(CommonProcessors.error())
+                .putProcessor(CommonProcessors.root()))
+            .build(temporaryFolder);
+        jamesServer.start();
+        jamesServer.getProbe(DataProbeImpl.class)
+            .fluent()
+            .addDomain(DEFAULT_DOMAIN)
+            .addUser(FROM, PASSWORD)
+            .addUser(RECIPIENT, PASSWORD);
+
+        smtpMessageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+            .authenticate(FROM, PASSWORD)
+            .sendMessage(FROM, ImmutableList.of(FROM, RECIPIENT));
+
+        testIMAPClient.connect(LOCALHOST_IP, jamesServer.getProbe(ImapGuiceProbe.class).getImapPort())
+            .login(RECIPIENT, PASSWORD)
+            .select(INBOX)
+            .awaitMessage(awaitAtMostOneMinute);
+        assertThat(CountingExecutionMailet.executionCount()).isEqualTo(1);
+        assertThat(CollectingExecutionMailet.executionFor())
+            .hasSize(2)
+            .containsOnly(new MailAddress(FROM), new MailAddress(RECIPIENT));
+    }
+
+    @Test
+    public void noMatchShouldNotExecuteMailet(@TempDir File temporaryFolder) throws Exception {
+        jamesServer = TemporaryJamesServer.builder()
+            .withBase(SMTP_AND_IMAP_MODULE)
+            .withMailetContainer(MailetContainer.builder()
+                .putProcessor(ProcessorConfiguration.transport()
+                    .addMailet(MailetConfiguration.BCC_STRIPPER)
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(None.class)
+                        .mailet(CountingExecutionMailet.class)
+                        .build())
+                    .addMailet(MailetConfiguration.LOCAL_DELIVERY))
+                .putProcessor(CommonProcessors.error())
+                .putProcessor(CommonProcessors.root()))
+            .build(temporaryFolder);
+        jamesServer.start();
+        jamesServer.getProbe(DataProbeImpl.class)
+            .fluent()
+            .addDomain(DEFAULT_DOMAIN)
+            .addUser(FROM, PASSWORD)
+            .addUser(RECIPIENT, PASSWORD);
+
+        smtpMessageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+            .authenticate(FROM, PASSWORD)
+            .sendMessage(FROM, ImmutableList.of(FROM, RECIPIENT));
+
+        testIMAPClient.connect(LOCALHOST_IP, jamesServer.getProbe(ImapGuiceProbe.class).getImapPort())
+            .login(RECIPIENT, PASSWORD)
+            .select(INBOX)
+            .awaitMessage(awaitAtMostOneMinute);
+        assertThat(CountingExecutionMailet.executionCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void noMatchWithNullShouldNotExecuteMailet(@TempDir File temporaryFolder) throws Exception {
+        jamesServer = TemporaryJamesServer.builder()
+            .withBase(SMTP_AND_IMAP_MODULE)
+            .withMailetContainer(MailetContainer.builder()
+                .putProcessor(ProcessorConfiguration.transport()
+                    .addMailet(MailetConfiguration.BCC_STRIPPER)
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(NoneWithNull.class)
+                        .mailet(CountingExecutionMailet.class)
+                        .build())
+                    .addMailet(MailetConfiguration.LOCAL_DELIVERY))
+                .putProcessor(CommonProcessors.error())
+                .putProcessor(CommonProcessors.root()))
+            .build(temporaryFolder);
+        jamesServer.start();
+        jamesServer.getProbe(DataProbeImpl.class)
+            .fluent()
+            .addDomain(DEFAULT_DOMAIN)
+            .addUser(FROM, PASSWORD)
+            .addUser(RECIPIENT, PASSWORD);
+
+        smtpMessageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+            .authenticate(FROM, PASSWORD)
+            .sendMessage(FROM, ImmutableList.of(FROM, RECIPIENT));
+
+        testIMAPClient.connect(LOCALHOST_IP, jamesServer.getProbe(ImapGuiceProbe.class).getImapPort())
+            .login(RECIPIENT, PASSWORD)
+            .select(INBOX)
+            .awaitMessage(awaitAtMostOneMinute);
+        assertThat(CountingExecutionMailet.executionCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void noMatchShouldNotSplitMailet(@TempDir File temporaryFolder) throws Exception {
+        jamesServer = TemporaryJamesServer.builder()
+            .withBase(SMTP_AND_IMAP_MODULE)
+            .withMailetContainer(MailetContainer.builder()
+                .putProcessor(ProcessorConfiguration.transport()
+                    .addMailet(MailetConfiguration.BCC_STRIPPER)
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(None.class)
+                        .mailet(NoopMailet.class)
+                        .build())
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(All.class)
+                        .mailet(CountingExecutionMailet.class)
+                        .build())
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(All.class)
+                        .mailet(CollectingExecutionMailet.class)
+                        .build())
+                    .addMailet(MailetConfiguration.LOCAL_DELIVERY))
+                .putProcessor(CommonProcessors.error())
+                .putProcessor(CommonProcessors.root()))
+            .build(temporaryFolder);
+        jamesServer.start();
+        jamesServer.getProbe(DataProbeImpl.class)
+            .fluent()
+            .addDomain(DEFAULT_DOMAIN)
+            .addUser(FROM, PASSWORD)
+            .addUser(RECIPIENT, PASSWORD);
+
+        smtpMessageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+            .authenticate(FROM, PASSWORD)
+            .sendMessage(FROM, ImmutableList.of(FROM, RECIPIENT));
+
+        testIMAPClient.connect(LOCALHOST_IP, jamesServer.getProbe(ImapGuiceProbe.class).getImapPort())
+            .login(RECIPIENT, PASSWORD)
+            .select(INBOX)
+            .awaitMessage(awaitAtMostOneMinute);
+        assertThat(CountingExecutionMailet.executionCount()).isEqualTo(1);
+        assertThat(CollectingExecutionMailet.executionFor())
+            .hasSize(2)
+            .containsOnly(new MailAddress(FROM), new MailAddress(RECIPIENT));
+    }
+
+    @Test
+    public void noMatchWithNullShouldNotSplitMailet(@TempDir File temporaryFolder) throws Exception {
+        jamesServer = TemporaryJamesServer.builder()
+            .withBase(SMTP_AND_IMAP_MODULE)
+            .withMailetContainer(MailetContainer.builder()
+                .putProcessor(ProcessorConfiguration.transport()
+                    .addMailet(MailetConfiguration.BCC_STRIPPER)
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(NoneWithNull.class)
+                        .mailet(NoopMailet.class)
+                        .build())
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(All.class)
+                        .mailet(CountingExecutionMailet.class)
+                        .build())
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(All.class)
+                        .mailet(CollectingExecutionMailet.class)
+                        .build())
+                    .addMailet(MailetConfiguration.LOCAL_DELIVERY))
+                .putProcessor(CommonProcessors.error())
+                .putProcessor(CommonProcessors.root()))
+            .build(temporaryFolder);
+        jamesServer.start();
+        jamesServer.getProbe(DataProbeImpl.class)
+            .fluent()
+            .addDomain(DEFAULT_DOMAIN)
+            .addUser(FROM, PASSWORD)
+            .addUser(RECIPIENT, PASSWORD);
+
+        smtpMessageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+            .authenticate(FROM, PASSWORD)
+            .sendMessage(FROM, ImmutableList.of(FROM, RECIPIENT));
+
+        testIMAPClient.connect(LOCALHOST_IP, jamesServer.getProbe(ImapGuiceProbe.class).getImapPort())
+            .login(RECIPIENT, PASSWORD)
+            .select(INBOX)
+            .awaitMessage(awaitAtMostOneMinute);
+        assertThat(CountingExecutionMailet.executionCount()).isEqualTo(1);
+        assertThat(CollectingExecutionMailet.executionFor())
+            .hasSize(2)
+            .containsOnly(new MailAddress(FROM), new MailAddress(RECIPIENT));
+    }
+
+    @Test
+    public void nullMailetShouldAbortProcessing(@TempDir File temporaryFolder) throws Exception {
+        jamesServer = TemporaryJamesServer.builder()
+            .withBase(SMTP_AND_IMAP_MODULE)
+            .withMailetContainer(MailetContainer.builder()
+                .putProcessor(ProcessorConfiguration.transport()
+                    .addMailet(MailetConfiguration.BCC_STRIPPER)
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(All.class)
+                        .mailet(Null.class)
+                        .build())
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(All.class)
+                        .mailet(CountingExecutionMailet.class)
+                        .build())
+                    .addMailet(MailetConfiguration.LOCAL_DELIVERY))
+                .putProcessor(CommonProcessors.error())
+                .putProcessor(CommonProcessors.root()))
+            .build(temporaryFolder);
+        jamesServer.start();
+        jamesServer.getProbe(DataProbeImpl.class)
+            .fluent()
+            .addDomain(DEFAULT_DOMAIN)
+            .addUser(FROM, PASSWORD)
+            .addUser(RECIPIENT, PASSWORD);
+
+        smtpMessageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+            .authenticate(FROM, PASSWORD)
+            .sendMessage(FROM, ImmutableList.of(FROM, RECIPIENT));
+
+        Thread.sleep(100); // queue delays might cause the processing not to start straight at the end of the SMTP session
+        awaitAtMostOneMinute.untilAsserted(() -> assertThat(
+            jamesServer.getProbe(SpoolerProbe.class).processingFinished())
+            .isTrue());
+        assertThat(CountingExecutionMailet.executionCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void nullMailetShouldAbortProcessingOnlOfMatchedEmails(@TempDir File temporaryFolder) throws Exception {
+        jamesServer = TemporaryJamesServer.builder()
+            .withBase(SMTP_AND_IMAP_MODULE)
+            .withMailetContainer(MailetContainer.builder()
+                .putProcessor(ProcessorConfiguration.transport()
+                    .addMailet(MailetConfiguration.BCC_STRIPPER)
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(RecipientIs.class)
+                        .matcherCondition(RECIPIENT)
+                        .mailet(Null.class)
+                        .build())
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(All.class)
+                        .mailet(CountingExecutionMailet.class)
+                        .build())
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(All.class)
+                        .mailet(CollectingExecutionMailet.class)
+                        .build())
+                    .addMailet(MailetConfiguration.LOCAL_DELIVERY))
+                .putProcessor(CommonProcessors.error())
+                .putProcessor(CommonProcessors.root()))
+            .build(temporaryFolder);
+        jamesServer.start();
+        jamesServer.getProbe(DataProbeImpl.class)
+            .fluent()
+            .addDomain(DEFAULT_DOMAIN)
+            .addUser(FROM, PASSWORD)
+            .addUser(RECIPIENT, PASSWORD);
+
+        smtpMessageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+            .authenticate(FROM, PASSWORD)
+            .sendMessage(FROM, ImmutableList.of(FROM, RECIPIENT));
+
+        Thread.sleep(100); // queue delays might cause the processing not to start straight at the end of the SMTP session
+        awaitAtMostOneMinute.untilAsserted(() -> assertThat(
+            jamesServer.getProbe(SpoolerProbe.class).processingFinished())
+            .isTrue());
+        assertThat(CountingExecutionMailet.executionCount()).isEqualTo(1);
+        assertThat(CollectingExecutionMailet.executionFor())
+            .hasSize(1)
+            .containsOnly(new MailAddress(FROM));
+    }
+
+    @Test
+    public void clearRecipientsMailetShouldAbortProcessing(@TempDir File temporaryFolder) throws Exception {
+        jamesServer = TemporaryJamesServer.builder()
+            .withBase(SMTP_AND_IMAP_MODULE)
+            .withMailetContainer(MailetContainer.builder()
+                .putProcessor(ProcessorConfiguration.transport()
+                    .addMailet(MailetConfiguration.BCC_STRIPPER)
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(All.class)
+                        .mailet(ClearRecipientsMailet.class)
+                        .build())
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(All.class)
+                        .mailet(CountingExecutionMailet.class)
+                        .build())
+                    .addMailet(MailetConfiguration.LOCAL_DELIVERY))
+                .putProcessor(CommonProcessors.error())
+                .putProcessor(CommonProcessors.root()))
+            .build(temporaryFolder);
+        jamesServer.start();
+        jamesServer.getProbe(DataProbeImpl.class)
+            .fluent()
+            .addDomain(DEFAULT_DOMAIN)
+            .addUser(FROM, PASSWORD)
+            .addUser(RECIPIENT, PASSWORD);
+
+        smtpMessageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+            .authenticate(FROM, PASSWORD)
+            .sendMessage(FROM, ImmutableList.of(FROM, RECIPIENT));
+
+        Thread.sleep(100); // queue delays might cause the processing not to start straight at the end of the SMTP session
+        awaitAtMostOneMinute.untilAsserted(() -> assertThat(
+            jamesServer.getProbe(SpoolerProbe.class).processingFinished())
+            .isTrue());
+        assertThat(CountingExecutionMailet.executionCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void clearRecipientsMailetShouldAbortProcessingOnlOfMatchedEmails(@TempDir File temporaryFolder) throws Exception {
+        jamesServer = TemporaryJamesServer.builder()
+            .withBase(SMTP_AND_IMAP_MODULE)
+            .withMailetContainer(MailetContainer.builder()
+                .putProcessor(ProcessorConfiguration.transport()
+                    .addMailet(MailetConfiguration.BCC_STRIPPER)
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(RecipientIs.class)
+                        .matcherCondition(RECIPIENT)
+                        .mailet(ClearRecipientsMailet.class)
+                        .build())
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(All.class)
+                        .mailet(CountingExecutionMailet.class)
+                        .build())
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(All.class)
+                        .mailet(CollectingExecutionMailet.class)
+                        .build())
+                    .addMailet(MailetConfiguration.LOCAL_DELIVERY))
+                .putProcessor(CommonProcessors.error())
+                .putProcessor(CommonProcessors.root()))
+            .build(temporaryFolder);
+        jamesServer.start();
+        jamesServer.getProbe(DataProbeImpl.class)
+            .fluent()
+            .addDomain(DEFAULT_DOMAIN)
+            .addUser(FROM, PASSWORD)
+            .addUser(RECIPIENT, PASSWORD);
+
+        smtpMessageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+            .authenticate(FROM, PASSWORD)
+            .sendMessage(FROM, ImmutableList.of(FROM, RECIPIENT));
+
+        Thread.sleep(100); // queue delays might cause the processing not to start straight at the end of the SMTP session
+        awaitAtMostOneMinute.untilAsserted(() -> assertThat(
+            jamesServer.getProbe(SpoolerProbe.class).processingFinished())
+            .isTrue());
+        assertThat(CountingExecutionMailet.executionCount()).isEqualTo(1);
+        assertThat(CollectingExecutionMailet.executionFor())
+            .hasSize(1)
+            .containsOnly(new MailAddress(FROM));
+    }
+
+    @Test
+    public void mailetCanEditRecipients(@TempDir File temporaryFolder) throws Exception {
+        jamesServer = TemporaryJamesServer.builder()
+            .withBase(SMTP_AND_IMAP_MODULE)
+            .withMailetContainer(MailetContainer.builder()
+                .putProcessor(ProcessorConfiguration.transport()
+                    .addMailet(MailetConfiguration.BCC_STRIPPER)
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(All.class)
+                        .mailet(AddRecipient.class)
+                        .build())
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(All.class)
+                        .mailet(CollectingExecutionMailet.class)
+                        .build())
+                    .addMailet(MailetConfiguration.LOCAL_DELIVERY))
+                .putProcessor(CommonProcessors.error())
+                .putProcessor(CommonProcessors.root()))
+            .build(temporaryFolder);
+        jamesServer.start();
+        jamesServer.getProbe(DataProbeImpl.class)
+            .fluent()
+            .addDomain(DEFAULT_DOMAIN)
+            .addUser(FROM, PASSWORD)
+            .addUser(RECIPIENT, PASSWORD);
+
+        smtpMessageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+            .authenticate(FROM, PASSWORD)
+            .sendMessage(FROM, ImmutableList.of(FROM, RECIPIENT));
+
+        Thread.sleep(100); // queue delays might cause the processing not to start straight at the end of the SMTP session
+        awaitAtMostOneMinute.untilAsserted(() -> assertThat(
+            jamesServer.getProbe(SpoolerProbe.class).processingFinished())
+            .isTrue());
+        assertThat(CollectingExecutionMailet.executionFor())
+            .hasSize(3)
+            .containsOnly(new MailAddress(FROM), new MailAddress(RECIPIENT), new MailAddress(RECIPIENT2));
+    }
+
+    @Test
+    public void toProcessorShouldSwitchExecutingProcessor(@TempDir File temporaryFolder) throws Exception {
+        jamesServer = TemporaryJamesServer.builder()
+            .withBase(SMTP_AND_IMAP_MODULE)
+            .withMailetContainer(MailetContainer.builder()
+                .putProcessor(ProcessorConfiguration.transport()
+                    .addMailet(MailetConfiguration.BCC_STRIPPER)
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(All.class)
+                        .mailet(ToProcessor.class)
+                        .addProperty("processor", "custom")
+                        .build())
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(All.class)
+                        .mailet(CountingExecutionMailet.class)
+                        .build())
+                    .addMailet(MailetConfiguration.LOCAL_DELIVERY))
+                .putProcessor(ProcessorConfiguration.builder()
+                    .state("custom")
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(All.class)
+                        .mailet(CountingExecutionMailetBis.class)
+                        .build()))
+                .putProcessor(CommonProcessors.error())
+                .putProcessor(CommonProcessors.root()))
+            .build(temporaryFolder);
+        jamesServer.start();
+        jamesServer.getProbe(DataProbeImpl.class)
+            .fluent()
+            .addDomain(DEFAULT_DOMAIN)
+            .addUser(FROM, PASSWORD)
+            .addUser(RECIPIENT, PASSWORD);
+
+        smtpMessageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+            .authenticate(FROM, PASSWORD)
+            .sendMessage(FROM, ImmutableList.of(FROM, RECIPIENT));
+
+        Thread.sleep(100); // queue delays might cause the processing not to start straight at the end of the SMTP session
+        awaitAtMostOneMinute.untilAsserted(() -> assertThat(
+            jamesServer.getProbe(SpoolerProbe.class).processingFinished())
+            .isTrue());
+        assertThat(CountingExecutionMailet.executionCount()).isEqualTo(0);
+        assertThat(CountingExecutionMailetBis.executionCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void toProcessorShouldSupportPartialMatches(@TempDir File temporaryFolder) throws Exception {
+        jamesServer = TemporaryJamesServer.builder()
+            .withBase(SMTP_AND_IMAP_MODULE)
+            .withMailetContainer(MailetContainer.builder()
+                .putProcessor(ProcessorConfiguration.transport()
+                    .addMailet(MailetConfiguration.BCC_STRIPPER)
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(RecipientIs.class)
+                        .matcherCondition(RECIPIENT)
+                        .mailet(ToProcessor.class)
+                        .addProperty("processor", "custom")
+                        .build())
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(All.class)
+                        .mailet(CollectingExecutionMailet.class)
+                        .build())
+                    .addMailet(MailetConfiguration.LOCAL_DELIVERY))
+                .putProcessor(ProcessorConfiguration.builder()
+                    .state("custom")
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(All.class)
+                        .mailet(CollectingExecutionMailetBis.class)
+                        .build()))
+                .putProcessor(CommonProcessors.error())
+                .putProcessor(CommonProcessors.root()))
+            .build(temporaryFolder);
+        jamesServer.start();
+        jamesServer.getProbe(DataProbeImpl.class)
+            .fluent()
+            .addDomain(DEFAULT_DOMAIN)
+            .addUser(FROM, PASSWORD)
+            .addUser(RECIPIENT, PASSWORD);
+
+        smtpMessageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+            .authenticate(FROM, PASSWORD)
+            .sendMessage(FROM, ImmutableList.of(FROM, RECIPIENT));
+
+        Thread.sleep(100); // queue delays might cause the processing not to start straight at the end of the SMTP session
+        awaitAtMostOneMinute.untilAsserted(() -> assertThat(
+            jamesServer.getProbe(SpoolerProbe.class).processingFinished())
+            .isTrue());
+        assertThat(CollectingExecutionMailet.executionFor())
+            .hasSize(1)
+            .containsOnly(new MailAddress(FROM));
+        assertThat(CollectingExecutionMailetBis.executionFor())
+            .hasSize(1)
+            .containsOnly(new MailAddress(RECIPIENT));
+    }
+
+    @Test
+    public void toProcessorSplitShouldNotDisposeContent(@TempDir File temporaryFolder) throws Exception {
+        jamesServer = TemporaryJamesServer.builder()
+            .withBase(SMTP_AND_IMAP_MODULE)
+            .withMailetContainer(MailetContainer.builder()
+                .putProcessor(ProcessorConfiguration.transport()
+                    .addMailet(MailetConfiguration.BCC_STRIPPER)
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(RecipientIs.class)
+                        .matcherCondition(RECIPIENT)
+                        .mailet(ToProcessor.class)
+                        .addProperty("processor", "custom")
+                        .build())
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(All.class)
+                        .mailet(EnsureNotDisposed.class)
+                        .build())
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(All.class)
+                        .mailet(CollectingExecutionMailet.class)
+                        .build())
+                    .addMailet(MailetConfiguration.LOCAL_DELIVERY))
+                .putProcessor(ProcessorConfiguration.builder()
+                    .state("custom")
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(All.class)
+                        .mailet(EnsureNotDisposed.class)
+                        .build())
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(All.class)
+                        .mailet(CollectingExecutionMailetBis.class)
+                        .build()))
+                .putProcessor(CommonProcessors.error())
+                .putProcessor(CommonProcessors.root()))
+            .build(temporaryFolder);
+        jamesServer.start();
+        jamesServer.getProbe(DataProbeImpl.class)
+            .fluent()
+            .addDomain(DEFAULT_DOMAIN)
+            .addUser(FROM, PASSWORD)
+            .addUser(RECIPIENT, PASSWORD);
+
+        smtpMessageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+            .authenticate(FROM, PASSWORD)
+            .sendMessage(FROM, ImmutableList.of(FROM, RECIPIENT));
+
+        Thread.sleep(100); // queue delays might cause the processing not to start straight at the end of the SMTP session
+        awaitAtMostOneMinute.untilAsserted(() -> assertThat(
+            jamesServer.getProbe(SpoolerProbe.class).processingFinished())
+            .isTrue());
+        assertThat(CollectingExecutionMailet.executionFor())
+            .hasSize(1)
+            .containsOnly(new MailAddress(FROM));
+        assertThat(CollectingExecutionMailetBis.executionFor())
+            .hasSize(1)
+            .containsOnly(new MailAddress(RECIPIENT));
+    }
+
+    @Test
+    public void toProcessorShouldSendToErrorWhenNotFound(@TempDir File temporaryFolder) throws Exception {
+        jamesServer = TemporaryJamesServer.builder()
+            .withBase(SMTP_AND_IMAP_MODULE)
+            .withMailetContainer(MailetContainer.builder()
+                .putProcessor(ProcessorConfiguration.transport()
+                    .addMailet(MailetConfiguration.BCC_STRIPPER)
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(RecipientIs.class)
+                        .matcherCondition(RECIPIENT)
+                        .mailet(ToProcessor.class)
+                        .addProperty("processor", "custom")
+                        .build())
+                    .addMailet(MailetConfiguration.LOCAL_DELIVERY))
+                .putProcessor(ProcessorConfiguration.error()
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(All.class)
+                        .mailet(CountingExecutionMailet.class)))
+                .putProcessor(CommonProcessors.root()))
+            .build(temporaryFolder);
+        jamesServer.start();
+        jamesServer.getProbe(DataProbeImpl.class)
+            .fluent()
+            .addDomain(DEFAULT_DOMAIN)
+            .addUser(FROM, PASSWORD)
+            .addUser(RECIPIENT, PASSWORD);
+
+        smtpMessageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+            .authenticate(FROM, PASSWORD)
+            .sendMessage(FROM, ImmutableList.of(FROM, RECIPIENT));
+
+        Thread.sleep(100); // queue delays might cause the processing not to start straight at the end of the SMTP session
+        awaitAtMostOneMinute.untilAsserted(() -> assertThat(
+            jamesServer.getProbe(SpoolerProbe.class).processingFinished())
+            .isTrue());
+        assertThat(CountingExecutionMailet.executionCount())
+            .isEqualTo(1);
+    }
+
+    @Test
+    public void splitShouldNotDisposeContent(@TempDir File temporaryFolder) throws Exception {
+        jamesServer = TemporaryJamesServer.builder()
+            .withBase(SMTP_AND_IMAP_MODULE)
+            .withMailetContainer(MailetContainer.builder()
+                .putProcessor(ProcessorConfiguration.transport()
+                    .addMailet(MailetConfiguration.BCC_STRIPPER)
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(RecipientIs.class)
+                        .matcherCondition(RECIPIENT)
+                        .mailet(NoopMailet.class)
+                        .build())
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(All.class)
+                        .mailet(EnsureNotDisposed.class)
+                        .build())
+                    .addMailet(MailetConfiguration.builder()
+                        .matcher(All.class)
+                        .mailet(CountingExecutionMailet.class)
+                        .build())
+                    .addMailet(MailetConfiguration.LOCAL_DELIVERY))
+                .putProcessor(CommonProcessors.error())
+                .putProcessor(CommonProcessors.root()))
+            .build(temporaryFolder);
+        jamesServer.start();
+        jamesServer.getProbe(DataProbeImpl.class)
+            .fluent()
+            .addDomain(DEFAULT_DOMAIN)
+            .addUser(FROM, PASSWORD)
+            .addUser(RECIPIENT, PASSWORD);
+
+        smtpMessageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+            .authenticate(FROM, PASSWORD)
+            .sendMessage(FROM, ImmutableList.of(FROM, RECIPIENT));
+
+        Thread.sleep(100); // queue delays might cause the processing not to start straight at the end of the SMTP session
+        awaitAtMostOneMinute.untilAsserted(() -> assertThat(
+            jamesServer.getProbe(SpoolerProbe.class).processingFinished())
+            .isTrue());
+        assertThat(CountingExecutionMailet.executionCount())
+            .isEqualTo(2);
+    }
+
+    @Test
+    public void partialMatchShouldLeadToExecutionOfDownStreamMailetsForEachSplitedMails(@TempDir File temporaryFolder) throws Exception {
+        jamesServer = TemporaryJamesServer.builder()
+            .withBase(SMTP_AND_IMAP_MODULE)
+            .withMailetContainer(MailetContainer.builder()
+                .putProcessor(ProcessorConfiguration.transport()
+                        .addMailet(MailetConfiguration.BCC_STRIPPER)
+                        .addMailet(MailetConfiguration.builder()
+                            .matcher(RecipientIs.class)
+                            .matcherCondition(RECIPIENT)
+                            .mailet(NoopMailet.class)
+                            .build())
+                        .addMailet(MailetConfiguration.builder()
+                            .matcher(All.class)
+                            .mailet(CountingExecutionTerminatingMailet.class)
+                            .build()))
+                .putProcessor(CommonProcessors.error())
+                .putProcessor(CommonProcessors.root()))
+            .build(temporaryFolder);
+        jamesServer.start();
+        jamesServer.getProbe(DataProbeImpl.class)
+            .fluent()
+            .addDomain(DEFAULT_DOMAIN)
+            .addUser(FROM, PASSWORD)
+            .addUser(RECIPIENT, PASSWORD);
+
+        smtpMessageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+            .authenticate(FROM, PASSWORD)
+            .sendMessage(FROM, ImmutableList.of(FROM, RECIPIENT));
+
+        Thread.sleep(100); // queue delays might cause the processing not to start straight at the end of the SMTP session
+        awaitAtMostOneMinute.untilAsserted(() -> assertThat(
+                jamesServer.getProbe(SpoolerProbe.class).processingFinished())
+            .isTrue());
+        assertThat(CountingExecutionTerminatingMailet.executionCount()).isEqualTo(2);
+    }
+
+    @Test
+    public void emailModificationsShouldBePreservedOnPartialMatch(@TempDir File temporaryFolder) throws Exception {
+        jamesServer = TemporaryJamesServer.builder()
+            .withBase(SMTP_AND_IMAP_MODULE)
+            .withMailetContainer(MailetContainer.builder()
+                .putProcessor(ProcessorConfiguration.transport()
+                        .addMailet(MailetConfiguration.BCC_STRIPPER)
+                        .addMailet(MailetConfiguration.builder()
+                            .matcher(All.class)
+                            .matcherCondition(RECIPIENT)
+                            .mailet(SetMailAttribute.class)
+                            .addProperty(CollectMailAttributeMailet.MY_ATTRIBUTE, "value1")
+                            .build())
+                        .addMailet(MailetConfiguration.builder()
+                            .matcher(RecipientIs.class)
+                            .matcherCondition(RECIPIENT)
+                            .mailet(SetMailAttribute.class)
+                            .addProperty(CollectMailAttributeMailet.MY_ATTRIBUTE, "value2")
+                            .build())
+                        .addMailet(MailetConfiguration.builder()
+                            .matcher(All.class)
+                            .mailet(CollectMailAttributeMailet.class)
+                            .build()))
+                .putProcessor(CommonProcessors.error())
+                .putProcessor(CommonProcessors.root()))
+            .build(temporaryFolder);
+        jamesServer.start();
+        jamesServer.getProbe(DataProbeImpl.class)
+            .fluent()
+            .addDomain(DEFAULT_DOMAIN)
+            .addUser(FROM, PASSWORD)
+            .addUser(RECIPIENT, PASSWORD);
+
+        smtpMessageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+            .authenticate(FROM, PASSWORD)
+            .sendMessage(FROM, ImmutableList.of(FROM, RECIPIENT));
+
+        Thread.sleep(100); // queue delays might cause the processing not to start straight at the end of the SMTP session
+        awaitAtMostOneMinute.untilAsserted(() -> assertThat(
+                jamesServer.getProbe(SpoolerProbe.class).processingFinished())
+            .isTrue());
+        assertThat(CollectMailAttributeMailet.encounteredAttributes())
+            .hasSize(2)
+            .containsOnly("value1", "value2");
+    }
+
+    @Test
+    public void matcherSplitShouldNotDuplicateRecipients(@TempDir File temporaryFolder) throws Exception {
+        jamesServer = TemporaryJamesServer.builder()
+            .withBase(SMTP_AND_IMAP_MODULE)
+            .withMailetContainer(MailetContainer.builder()
+                .putProcessor(ProcessorConfiguration.transport()
+                        .addMailet(MailetConfiguration.BCC_STRIPPER)
+                        .addMailet(MailetConfiguration.builder()
+                            .matcher(RecipientIs.class)
+                            .matcherCondition(RECIPIENT)
+                            .mailet(NoopMailet.class)
+                            .build())
+                        .addMailet(MailetConfiguration.builder()
+                            .matcher(All.class)
+                            .mailet(CollectingExecutionMailet.class)
+                            .build())
+                        .addMailet(MailetConfiguration.LOCAL_DELIVERY))
+                .putProcessor(CommonProcessors.error())
+                .putProcessor(CommonProcessors.root()))
+            .build(temporaryFolder);
+        jamesServer.start();
+        jamesServer.getProbe(DataProbeImpl.class)
+            .fluent()
+            .addDomain(DEFAULT_DOMAIN)
+            .addUser(FROM, PASSWORD)
+            .addUser(RECIPIENT, PASSWORD);
+
+        smtpMessageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+            .authenticate(FROM, PASSWORD)
+            .sendMessage(FROM, ImmutableList.of(FROM, RECIPIENT));
+
+        testIMAPClient.connect(LOCALHOST_IP, jamesServer.getProbe(ImapGuiceProbe.class).getImapPort())
+            .login(RECIPIENT, PASSWORD)
+            .select(INBOX)
+            .awaitMessage(awaitAtMostOneMinute);
+        assertThat(CollectingExecutionMailet.executionFor())
+            .hasSize(2)
+            .containsOnly(new MailAddress(FROM), new MailAddress(RECIPIENT));
+    }
+}
diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/FirstRecipientCountingExecutions.java b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/FirstRecipientCountingExecutions.java
new file mode 100644
index 0000000..8247528
--- /dev/null
+++ b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/FirstRecipientCountingExecutions.java
@@ -0,0 +1,49 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mailets.flow;
+
+import java.util.Collection;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.james.core.MailAddress;
+import org.apache.mailet.Mail;
+import org.apache.mailet.base.GenericMatcher;
+import org.testcontainers.shaded.com.google.common.collect.ImmutableList;
+
+public class FirstRecipientCountingExecutions extends GenericMatcher {
+    private static final AtomicLong executionCount = new AtomicLong();
+
+    public static void reset() {
+        executionCount.set(0L);
+    }
+
+    public static long executionCount() {
+        return executionCount.get();
+    }
+
+    @Override
+    public Collection<MailAddress> match(Mail mail) {
+        executionCount.incrementAndGet();
+        return mail.getRecipients().stream()
+            .findFirst()
+            .map(ImmutableList::of)
+            .orElse(ImmutableList.<MailAddress>of());
+    }
+}
diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/None.java b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/None.java
new file mode 100644
index 0000000..ade967b
--- /dev/null
+++ b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/None.java
@@ -0,0 +1,34 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mailets.flow;
+
+import java.util.Collection;
+
+import org.apache.james.core.MailAddress;
+import org.apache.mailet.Mail;
+import org.apache.mailet.base.GenericMatcher;
+import org.testcontainers.shaded.com.google.common.collect.ImmutableList;
+
+public class None extends GenericMatcher {
+    @Override
+    public Collection<MailAddress> match(Mail mail) {
+        return ImmutableList.of();
+    }
+}
diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/NoneWithNull.java b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/NoneWithNull.java
new file mode 100644
index 0000000..98b9baa
--- /dev/null
+++ b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/flow/NoneWithNull.java
@@ -0,0 +1,33 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mailets.flow;
+
+import java.util.Collection;
+
+import org.apache.james.core.MailAddress;
+import org.apache.mailet.Mail;
+import org.apache.mailet.base.GenericMatcher;
+
+public class NoneWithNull extends GenericMatcher {
+    @Override
+    public Collection<MailAddress> match(Mail mail) {
+        return null;
+    }
+}

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