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

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

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