INT-3861: Mail Namespace Support for userFlag
JIRA: https://jira.spring.io/browse/INT-3861 Allow the custom `userFlag` to be configured using the namespace. Polishing according PR comments
This commit is contained in:
committed by
Artem Bilan
parent
33a40f6cae
commit
f80c0db0df
@@ -52,20 +52,15 @@ import org.springframework.util.Assert;
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Gary Russell
|
||||
*/
|
||||
public abstract class AbstractMailReceiver extends IntegrationObjectSupport implements MailReceiver, DisposableBean{
|
||||
|
||||
public final static String DEFAULT_SI_USER_FLAG = "spring-integration-mail-adapter";
|
||||
public abstract class AbstractMailReceiver extends IntegrationObjectSupport implements MailReceiver, DisposableBean {
|
||||
|
||||
/**
|
||||
* Default user flag for marking messages as seen by this receiver:
|
||||
* {@value #DEFAULT_SI_USER_FLAG}.
|
||||
* @deprecated - this constant will be removed in 4.3; see
|
||||
* {@link #setUserFlag(String)} and {@link #DEFAULT_SI_USER_FLAG}
|
||||
*/
|
||||
@Deprecated
|
||||
public final static String SI_USER_FLAG = DEFAULT_SI_USER_FLAG;
|
||||
public final static String DEFAULT_SI_USER_FLAG = "spring-integration-mail-adapter";
|
||||
|
||||
protected final Log logger = LogFactory.getLog(this.getClass());
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private final URLName url;
|
||||
|
||||
@@ -155,7 +150,7 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl
|
||||
}
|
||||
|
||||
protected Properties getJavaMailProperties() {
|
||||
return javaMailProperties;
|
||||
return this.javaMailProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -197,7 +192,7 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl
|
||||
}
|
||||
|
||||
protected String getUserFlag() {
|
||||
return userFlag;
|
||||
return this.userFlag;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -286,7 +281,7 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("attempting to receive mail from folder [" + this.getFolder().getFullName() + "]");
|
||||
}
|
||||
Message[] messages = this.searchForNewMessages();
|
||||
Message[] messages = searchForNewMessages();
|
||||
if (this.maxFetchSize > 0 && messages.length > this.maxFetchSize) {
|
||||
Message[] reducedMessages = new Message[this.maxFetchSize];
|
||||
System.arraycopy(messages, 0, reducedMessages, 0, this.maxFetchSize);
|
||||
@@ -296,16 +291,16 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl
|
||||
logger.debug("found " + messages.length + " new messages");
|
||||
}
|
||||
if (messages.length > 0) {
|
||||
this.fetchMessages(messages);
|
||||
fetchMessages(messages);
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Received " + messages.length + " messages");
|
||||
}
|
||||
|
||||
Message[] filteredMessages = this.filterMessagesThruSelector(messages);
|
||||
Message[] filteredMessages = filterMessagesThruSelector(messages);
|
||||
|
||||
this.postProcessFilteredMessages(filteredMessages);
|
||||
postProcessFilteredMessages(filteredMessages);
|
||||
|
||||
return filteredMessages;
|
||||
}
|
||||
@@ -316,10 +311,10 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl
|
||||
}
|
||||
|
||||
private void postProcessFilteredMessages(Message[] filteredMessages) throws MessagingException {
|
||||
this.setMessageFlags(filteredMessages);
|
||||
setMessageFlags(filteredMessages);
|
||||
|
||||
if (this.shouldDeleteMessages()) {
|
||||
this.deleteMessages(filteredMessages);
|
||||
if (shouldDeleteMessages()) {
|
||||
deleteMessages(filteredMessages);
|
||||
}
|
||||
// Copy messages to cause an eager fetch
|
||||
for (int i = 0; i < filteredMessages.length; i++) {
|
||||
@@ -331,7 +326,7 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl
|
||||
private void setMessageFlags(Message[] filteredMessages) throws MessagingException {
|
||||
boolean recentFlagSupported = false;
|
||||
|
||||
Flags flags = this.getFolder().getPermanentFlags();
|
||||
Flags flags = getFolder().getPermanentFlags();
|
||||
|
||||
if (flags != null){
|
||||
recentFlagSupported = flags.contains(Flags.Flag.RECENT);
|
||||
@@ -355,7 +350,7 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl
|
||||
message.setFlag(Flags.Flag.FLAGGED, true);
|
||||
}
|
||||
}
|
||||
this.setAdditionalFlags(message);
|
||||
setAdditionalFlags(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,7 +433,7 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl
|
||||
protected void onInit() throws Exception {
|
||||
super.onInit();
|
||||
this.folderOpenMode = Folder.READ_WRITE;
|
||||
this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.getBeanFactory());
|
||||
this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(getBeanFactory());
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -62,6 +62,7 @@ public class ImapIdleChannelAdapterParser extends AbstractChannelAdapterParser {
|
||||
private BeanDefinition parseImapMailReceiver(Element element, ParserContext parserContext) {
|
||||
BeanDefinitionBuilder receiverBuilder = BeanDefinitionBuilder.genericBeanDefinition(ImapMailReceiver.class);
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(receiverBuilder, element, "search-term-strategy");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(receiverBuilder, element, "user-flag");
|
||||
Object source = parserContext.extractSource(element);
|
||||
String uri = element.getAttribute("store-uri");
|
||||
if (StringUtils.hasText(uri)) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.integration.mail.config;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.BeanMetadataElement;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
@@ -26,7 +28,6 @@ import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
|
||||
import org.springframework.integration.mail.MailReceivingMessageSource;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.xml.DomUtils;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
/**
|
||||
* Parser for the <inbound-channel-adapter> element of Spring Integration's 'mail' namespace.
|
||||
@@ -52,6 +53,7 @@ public class MailInboundChannelAdapterParser extends AbstractPollingInboundChann
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(receiverBuilder, element, "store-uri");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(receiverBuilder, element, "protocol");
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(receiverBuilder, element, "search-term-strategy");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(receiverBuilder, element, "user-flag");
|
||||
String session = element.getAttribute("session");
|
||||
if (StringUtils.hasText(session)) {
|
||||
if (element.hasAttribute("java-mail-properties") || element.hasAttribute("authenticator")) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -42,6 +42,7 @@ import org.springframework.util.StringUtils;
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Gary Russell
|
||||
* @since 1.0.3
|
||||
*/
|
||||
public class MailReceiverFactoryBean implements FactoryBean<MailReceiver>, DisposableBean, BeanFactoryAware {
|
||||
@@ -74,6 +75,8 @@ public class MailReceiverFactoryBean implements FactoryBean<MailReceiver>, Dispo
|
||||
|
||||
private volatile SearchTermStrategy searchTermStrategy;
|
||||
|
||||
private volatile String userFlag;
|
||||
|
||||
private volatile BeanFactory beanFactory;
|
||||
|
||||
public void setStoreUri(String storeUri) {
|
||||
@@ -120,11 +123,16 @@ public class MailReceiverFactoryBean implements FactoryBean<MailReceiver>, Dispo
|
||||
this.searchTermStrategy = searchTermStrategy;
|
||||
}
|
||||
|
||||
public void setUserFlag(String userFlag) {
|
||||
this.userFlag = userFlag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MailReceiver getObject() throws Exception {
|
||||
if (this.receiver == null) {
|
||||
this.receiver = this.createReceiver();
|
||||
@@ -132,10 +140,12 @@ public class MailReceiverFactoryBean implements FactoryBean<MailReceiver>, Dispo
|
||||
return this.receiver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return (this.receiver != null) ? this.receiver.getClass() : MailReceiver.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSingleton() {
|
||||
return true;
|
||||
}
|
||||
@@ -165,7 +175,7 @@ public class MailReceiverFactoryBean implements FactoryBean<MailReceiver>, Dispo
|
||||
AbstractMailReceiver receiver = isPop3 ? new Pop3MailReceiver(this.storeUri) : new ImapMailReceiver(this.storeUri);
|
||||
if (this.session != null) {
|
||||
Assert.isNull(this.javaMailProperties, "JavaMail Properties are not allowed when a Session has been provided.");
|
||||
Assert.isNull(this.authenticator, "A JavaMail Authenticator is not allowed when a Session has been provied.");
|
||||
Assert.isNull(this.authenticator, "A JavaMail Authenticator is not allowed when a Session has been provided.");
|
||||
receiver.setSession(this.session);
|
||||
}
|
||||
if (this.searchTermStrategy != null) {
|
||||
@@ -185,6 +195,9 @@ public class MailReceiverFactoryBean implements FactoryBean<MailReceiver>, Dispo
|
||||
}
|
||||
receiver.setMaxFetchSize(this.maxFetchSize);
|
||||
receiver.setSelectorExpression(selectorExpression);
|
||||
if (StringUtils.hasText(this.userFlag)) {
|
||||
receiver.setUserFlag(this.userFlag);
|
||||
}
|
||||
|
||||
if (isPop3) {
|
||||
if (this.isShouldMarkMessagesAsRead() && this.logger.isWarnEnabled()) {
|
||||
@@ -201,6 +214,7 @@ public class MailReceiverFactoryBean implements FactoryBean<MailReceiver>, Dispo
|
||||
return receiver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
if (this.receiver != null && this.receiver instanceof DisposableBean) {
|
||||
((DisposableBean) this.receiver).destroy();
|
||||
|
||||
@@ -253,6 +253,16 @@
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="user-flag" type="xsd:string" use="optional">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Specify the user flag to mark messages read by the adapter when the mail store does not support
|
||||
the RECENT flag but does support user flags (keywords). This flag is set regardless of the
|
||||
'should-mark-messages-as-read' attribute. It can be used in the 'search-term-strategy' (and
|
||||
is by default) to ignore already processed messages. Default: 'spring-integration-mail-adapter'.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:element name="mail-to-string-transformer">
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
http://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
http://www.springframework.org/schema/integration/mail
|
||||
http://www.springframework.org/schema/integration/mail/spring-integration-mail.xsd">
|
||||
|
||||
|
||||
<integration:message-history/>
|
||||
|
||||
<context:property-placeholder properties-ref="configProperties"/>
|
||||
@@ -36,21 +36,22 @@
|
||||
channel="channel"
|
||||
auto-startup="false"
|
||||
should-delete-messages="true"/>
|
||||
|
||||
|
||||
<mail:imap-idle-channel-adapter id="simpleAdapterWithErrorChannel"
|
||||
store-uri="imap:foo"
|
||||
channel="channel"
|
||||
error-channel="errorChannel"
|
||||
auto-startup="false"
|
||||
should-delete-messages="true"/>
|
||||
|
||||
|
||||
<mail:imap-idle-channel-adapter id="simpleAdapterMarkAsRead"
|
||||
store-uri="imap:foo"
|
||||
channel="channel"
|
||||
auto-startup="false"
|
||||
should-delete-messages="true"
|
||||
user-flag="flagged"
|
||||
should-mark-messages-as-read="true"/>
|
||||
|
||||
|
||||
<mail:imap-idle-channel-adapter id="simpleAdapterMarkAsReadFalse"
|
||||
store-uri="imap:foo"
|
||||
channel="channel"
|
||||
@@ -63,9 +64,9 @@
|
||||
channel="channel"
|
||||
auto-startup="false"
|
||||
java-mail-properties="javaMailProperties"
|
||||
should-delete-messages="${mail.delete}"
|
||||
should-delete-messages="${mail.delete}"
|
||||
search-term-strategy="searchTermStrategy"/>
|
||||
|
||||
|
||||
<mail:imap-idle-channel-adapter id="transactionalAdapter"
|
||||
store-uri="imap:foo"
|
||||
channel="channel"
|
||||
@@ -97,4 +98,4 @@
|
||||
|
||||
<integration:bridge input-channel="autoChannel" output-channel="nullChannel" />
|
||||
|
||||
</beans>
|
||||
</beans>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -30,15 +30,16 @@ import javax.mail.search.SearchTerm;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.integration.mail.ImapIdleChannelAdapter;
|
||||
import org.springframework.integration.mail.ImapMailReceiver;
|
||||
import org.springframework.integration.mail.SearchTermStrategy;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
@@ -120,6 +121,7 @@ public class ImapIdleChannelAdapterParserTests {
|
||||
assertEquals(2, properties.size());
|
||||
assertEquals(Boolean.TRUE, receiverAccessor.getPropertyValue("shouldDeleteMessages"));
|
||||
assertEquals(Boolean.TRUE, receiverAccessor.getPropertyValue("shouldMarkMessagesAsRead"));
|
||||
assertEquals("flagged", receiverAccessor.getPropertyValue("userFlag"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -191,6 +193,7 @@ public class ImapIdleChannelAdapterParserTests {
|
||||
}
|
||||
public static class TestSearchTermStrategy implements SearchTermStrategy {
|
||||
|
||||
@Override
|
||||
public SearchTerm generateSearchTerm(Flags supportedFlags, Folder folder) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -91,9 +91,9 @@
|
||||
<mail:inbound-channel-adapter id="imapWithoutStoreUri" channel="testChannel" protocol="imap" should-delete-messages="false" auto-startup="false"/>
|
||||
|
||||
<mail:imap-idle-channel-adapter id="imapIdleWithoutStoreUri" channel="testChannel" should-delete-messages="false" auto-startup="false"/>
|
||||
|
||||
|
||||
<mail:inbound-channel-adapter id="pop3ShouldMarkAsReadTrue" channel="testChannel" protocol="pop3" should-delete-messages="false" auto-startup="false" should-mark-messages-as-read="true"/>
|
||||
|
||||
|
||||
<mail:inbound-channel-adapter id="pop3ShouldMarkAsReadFalse" channel="testChannel" protocol="pop3" should-delete-messages="false" auto-startup="false" should-mark-messages-as-read="false"/>
|
||||
|
||||
<mail:inbound-channel-adapter id="imapShouldMarkAsReadTrue" channel="testChannel" protocol="imap" should-delete-messages="false" auto-startup="false" should-mark-messages-as-read="true"/>
|
||||
@@ -106,7 +106,8 @@
|
||||
|
||||
<!-- INT-2800 -->
|
||||
|
||||
<mail:inbound-channel-adapter id="imapWithSearch" channel="testChannel" protocol="imap" should-delete-messages="false" auto-startup="false"
|
||||
<mail:inbound-channel-adapter id="imapWithSearch" channel="testChannel" protocol="imap"
|
||||
should-delete-messages="false" auto-startup="false" user-flag="flagged"
|
||||
search-term-strategy="searchTermStrategy"/>
|
||||
|
||||
<bean id="searchTermStrategy" class="org.mockito.Mockito" factory-method="mock">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -37,7 +37,6 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
|
||||
import org.springframework.integration.mail.AbstractMailReceiver;
|
||||
import org.springframework.integration.mail.ImapIdleChannelAdapter;
|
||||
@@ -45,12 +44,14 @@ import org.springframework.integration.mail.ImapMailReceiver;
|
||||
import org.springframework.integration.mail.Pop3MailReceiver;
|
||||
import org.springframework.integration.mail.SearchTermStrategy;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Gary Russell
|
||||
* @since 1.0.5
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@@ -278,7 +279,8 @@ public class InboundChannelAdapterParserTests {
|
||||
public void inboundChannelAdapterRequiresShouldDeleteMessages() {
|
||||
try {
|
||||
new ClassPathXmlApplicationContext(
|
||||
"org/springframework/integration/mail/config/InboundChannelAdapterParserTests-invalidContext.xml");
|
||||
"org/springframework/integration/mail/config/InboundChannelAdapterParserTests-invalidContext.xml")
|
||||
.close();
|
||||
fail("expected a parser error");
|
||||
}
|
||||
catch(BeanDefinitionStoreException e) {
|
||||
@@ -293,16 +295,19 @@ public class InboundChannelAdapterParserTests {
|
||||
public void imapWithSearchTermStrategy() {
|
||||
AbstractMailReceiver receiver = this.getReceiver("imapWithSearch");
|
||||
assertEquals(ImapMailReceiver.class, receiver.getClass());
|
||||
Object sts = new DirectFieldAccessor(receiver).getPropertyValue("searchTermStrategy");
|
||||
DirectFieldAccessor receiverAccessor = new DirectFieldAccessor(receiver);
|
||||
Object sts = receiverAccessor.getPropertyValue("searchTermStrategy");
|
||||
assertNotNull(sts);
|
||||
assertSame(context.getBean(SearchTermStrategy.class), sts);
|
||||
assertEquals("flagged", receiverAccessor.getPropertyValue("userFlag"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pop3WithSearchTermStrategy() {
|
||||
try {
|
||||
new ClassPathXmlApplicationContext(
|
||||
"org/springframework/integration/mail/config/InboundChannelAdapterParserTests-pop3Search-context.xml");
|
||||
"org/springframework/integration/mail/config/InboundChannelAdapterParserTests-pop3Search-context.xml")
|
||||
.close();
|
||||
fail("expected a parser error");
|
||||
}
|
||||
catch(BeanCreationException e) {
|
||||
|
||||
@@ -271,7 +271,7 @@ As discussed in <<search-term>>, the default `SearchTermStrategy` will ignore me
|
||||
|
||||
Starting with _version 4.2.2_, the name of the user flag can be set using `setUserFlag` on the `MailReceiver` - this
|
||||
allows multiple receivers to use a different flag (as long as the mail server supports user flags).
|
||||
Changing the user flag is not currently supported by the XML namespace.
|
||||
The attribute `user-flag` is available when configuring the adapter with the namespace.
|
||||
|
||||
[[mail-filtering]]
|
||||
=== Email Message Filtering
|
||||
|
||||
@@ -20,3 +20,9 @@ It was completely ignored; the gateway's reply goes to the next chain element, o
|
||||
if the gateway is the last element.
|
||||
This condition is now detected and disallowed.
|
||||
If you have such configuration, simply remove the `reply-channel`.
|
||||
|
||||
==== Mail Changes
|
||||
|
||||
The customizable `userFlag` added in 4.2.2 to provide customization of the flag used to denote that the mail has been
|
||||
seen is now available using the XML namespace.
|
||||
See <<imap-seen>> for more information.
|
||||
|
||||
Reference in New Issue
Block a user