diff --git a/spring-integration-mail/src/main/java/org/springframework/integration/mail/AbstractMailReceiver.java b/spring-integration-mail/src/main/java/org/springframework/integration/mail/AbstractMailReceiver.java index 88fc719e46..b963e8f914 100755 --- a/spring-integration-mail/src/main/java/org/springframework/integration/mail/AbstractMailReceiver.java +++ b/spring-integration-mail/src/main/java/org/springframework/integration/mail/AbstractMailReceiver.java @@ -54,12 +54,23 @@ import org.springframework.util.Assert; */ public abstract class AbstractMailReceiver extends IntegrationObjectSupport implements MailReceiver, DisposableBean{ - public final static String SI_USER_FLAG = "spring-integration-mail-adapter"; + public final static String DEFAULT_SI_USER_FLAG = "spring-integration-mail-adapter"; + + /** + * 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; protected final Log logger = LogFactory.getLog(this.getClass()); private final URLName url; + private final Object folderMonitor = new Object(); + private volatile String protocol; private volatile int maxFetchSize = -1; @@ -84,8 +95,7 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl protected volatile boolean initialized; - private final Object folderMonitor = new Object(); - + private volatile String userFlag = DEFAULT_SI_USER_FLAG; public AbstractMailReceiver() { this.url = null; @@ -186,6 +196,20 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl return this.shouldDeleteMessages; } + protected String getUserFlag() { + return userFlag; + } + + /** + * Set the name of the flag to use to flag messages when the server does + * not support \Recent but supports user flags; default {@value #DEFAULT_SI_USER_FLAG}. + * @param userFlag the flag. + * @since 4.2.2 + */ + public void setUserFlag(String userFlag) { + this.userFlag = userFlag; + } + protected Folder getFolder() { return this.folder; } @@ -316,15 +340,17 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl if (!recentFlagSupported){ if (flags != null && flags.contains(Flags.Flag.USER)){ if (logger.isDebugEnabled()){ - logger.debug("USER flags are supported by this mail server. Flagging message with '" + SI_USER_FLAG + "' user flag"); + logger.debug("USER flags are supported by this mail server. Flagging message with '" + + this.userFlag + "' user flag"); } Flags siFlags = new Flags(); - siFlags.add(SI_USER_FLAG); + siFlags.add(this.userFlag); message.setFlags(siFlags, true); } else { if (logger.isDebugEnabled()){ - logger.debug("USER flags are not supported by this mail server. Flagging message with system flag"); + logger.debug("USER flags are not supported by this mail server. " + + "Flagging message with system flag"); } message.setFlag(Flags.Flag.FLAGGED, true); } diff --git a/spring-integration-mail/src/main/java/org/springframework/integration/mail/ImapMailReceiver.java b/spring-integration-mail/src/main/java/org/springframework/integration/mail/ImapMailReceiver.java index 9b1cf1d4d8..125c8166bc 100755 --- a/spring-integration-mail/src/main/java/org/springframework/integration/mail/ImapMailReceiver.java +++ b/spring-integration-mail/src/main/java/org/springframework/integration/mail/ImapMailReceiver.java @@ -313,9 +313,10 @@ public class ImapMailReceiver extends AbstractMailReceiver { NotTerm notFlagged = null; if (folder.getPermanentFlags().contains(Flags.Flag.USER)) { logger.debug("This email server does not support RECENT flag, but it does support " + - "USER flags which will be used to prevent duplicates during email fetch."); + "USER flags which will be used to prevent duplicates during email fetch." + + " This receiver instance uses flag: " + getUserFlag()); Flags siFlags = new Flags(); - siFlags.add(AbstractMailReceiver.SI_USER_FLAG); + siFlags.add(getUserFlag()); notFlagged = new NotTerm(new FlagTerm(siFlags, true)); } else { diff --git a/spring-integration-mail/src/test/java/org/springframework/integration/mail/ImapMailReceiverTests.java b/spring-integration-mail/src/test/java/org/springframework/integration/mail/ImapMailReceiverTests.java index 2131835739..c9be6efc77 100644 --- a/spring-integration-mail/src/test/java/org/springframework/integration/mail/ImapMailReceiverTests.java +++ b/spring-integration-mail/src/test/java/org/springframework/integration/mail/ImapMailReceiverTests.java @@ -81,7 +81,6 @@ import org.springframework.integration.mail.ImapIdleChannelAdapter.ImapIdleExcep import org.springframework.integration.mail.PoorMansMailServer.ImapServer; import org.springframework.integration.mail.config.ImapIdleChannelAdapterParserTests; import org.springframework.integration.test.support.LongRunningIntegrationTest; -import org.springframework.integration.test.util.SocketUtils; import org.springframework.integration.test.util.TestUtils; import org.springframework.messaging.PollableChannel; import org.springframework.scheduling.TaskScheduler; @@ -103,9 +102,7 @@ public class ImapMailReceiverTests { private final AtomicInteger failed = new AtomicInteger(0); - private final static int imapIdlePort = SocketUtils.findAvailableServerSocket(); - - private final static ImapServer imapIdleServer = PoorMansMailServer.imap(imapIdlePort); + private final static ImapServer imapIdleServer = PoorMansMailServer.imap(0); @BeforeClass @@ -123,15 +120,9 @@ public class ImapMailReceiverTests { } @Test - public void testIdleWithServer() throws Exception { - Properties mailProps = new Properties(); - mailProps.put("mail.debug", "true"); - mailProps.put("mail.imap.connectionpool.debug", "true"); - ImapMailReceiver receiver = new ImapMailReceiver("imap://user:pw@localhost:" + imapIdlePort + "/INBOX"); - receiver.setJavaMailProperties(mailProps); - receiver.setMaxFetchSize(1); - receiver.setShouldDeleteMessages(false); - receiver.setShouldMarkMessagesAsRead(true); + public void testIdleWithServerCustomSearch() throws Exception { + ImapMailReceiver receiver = new ImapMailReceiver("imap://user:pw@localhost:" + imapIdleServer.getPort() + + "/INBOX"); receiver.setSearchTermStrategy(new SearchTermStrategy() { @Override @@ -146,9 +137,30 @@ public class ImapMailReceiverTests { } } }); + testIdleWithServerGuts(receiver); + } + + @Test + public void testIdleWithServerDefaultSearch() throws Exception { + ImapMailReceiver receiver = new ImapMailReceiver("imap://user:pw@localhost:" + imapIdleServer.getPort() + + "/INBOX"); + testIdleWithServerGuts(receiver); + assertTrue(imapIdleServer.assertReceived("searchWithUserFlag")); + } + + public void testIdleWithServerGuts(ImapMailReceiver receiver) throws MessagingException { + imapIdleServer.resetServer(); + Properties mailProps = new Properties(); + mailProps.put("mail.debug", "true"); + mailProps.put("mail.imap.connectionpool.debug", "true"); + receiver.setJavaMailProperties(mailProps); + receiver.setMaxFetchSize(1); + receiver.setShouldDeleteMessages(false); + receiver.setShouldMarkMessagesAsRead(true); receiver.setCancelIdleInterval(8); ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); setUpScheduler(receiver, taskScheduler); + receiver.setUserFlag("testSIUserFlag"); receiver.afterPropertiesSet(); Log logger = spy(TestUtils.getPropertyValue(receiver, "logger", Log.class)); new DirectFieldAccessor(receiver).setPropertyValue("logger", logger); @@ -167,6 +179,7 @@ public class ImapMailReceiverTests { assertNull(channel.receive(10000)); // no new message after second and third idle verify(logger).debug("Canceling IDLE"); taskScheduler.shutdown(); + assertTrue(imapIdleServer.assertReceived("storeUserFlag")); } @Test @@ -464,9 +477,9 @@ public class ImapMailReceiverTests { doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { - DirectFieldAccessor accesor = new DirectFieldAccessor((invocation.getMock())); + DirectFieldAccessor accessor = new DirectFieldAccessor((invocation.getMock())); IMAPFolder folder = mock(IMAPFolder.class); - accesor.setPropertyValue("folder", folder); + accessor.setPropertyValue("folder", folder); when(folder.hasNewMessages()).thenReturn(true); return null; } diff --git a/spring-integration-mail/src/test/java/org/springframework/integration/mail/ImapMailSearchTermsTests.java b/spring-integration-mail/src/test/java/org/springframework/integration/mail/ImapMailSearchTermsTests.java index 84516b7341..2f9a3f1ee9 100644 --- a/spring-integration-mail/src/test/java/org/springframework/integration/mail/ImapMailSearchTermsTests.java +++ b/spring-integration-mail/src/test/java/org/springframework/integration/mail/ImapMailSearchTermsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 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. @@ -21,6 +21,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import javax.mail.Flags; @@ -45,7 +46,21 @@ public class ImapMailSearchTermsTests { @Test public void validateSearchTermsWhenShouldMarkAsReadNoExistingFlags() throws Exception { + String userFlag = AbstractMailReceiver.DEFAULT_SI_USER_FLAG; ImapMailReceiver receiver = new ImapMailReceiver(); + validateSearchTermsWhenShouldMarkAsReadNoExistingFlagsGuts(userFlag, receiver); + } + + @Test + public void validateSearchTermsWhenShouldMarkAsReadNoExistingFlagsCustom() throws Exception { + String userFlag = "foo"; + ImapMailReceiver receiver = new ImapMailReceiver(); + receiver.setUserFlag(userFlag); + validateSearchTermsWhenShouldMarkAsReadNoExistingFlagsGuts(userFlag, receiver); + } + + public void validateSearchTermsWhenShouldMarkAsReadNoExistingFlagsGuts(String userFlag, ImapMailReceiver receiver) + throws NoSuchFieldException, IllegalAccessException, InvocationTargetException { receiver.setShouldMarkMessagesAsRead(true); receiver.setBeanFactory(mock(BeanFactory.class)); @@ -62,9 +77,10 @@ public class ImapMailSearchTermsTests { assertTrue(searchTerms instanceof NotTerm); NotTerm notTerm = (NotTerm) searchTerms; Flags siFlags = new Flags(); - siFlags.add(AbstractMailReceiver.SI_USER_FLAG); - notTerm.getTerm().equals(siFlags); + siFlags.add(userFlag); + assertEquals(siFlags, ((FlagTerm)notTerm.getTerm()).getFlags()); } + @Test public void validateSearchTermsWhenShouldMarkAsReadWithExistingFlags() throws Exception { ImapMailReceiver receiver = new ImapMailReceiver(); @@ -91,7 +107,7 @@ public class ImapMailSearchTermsTests { assertTrue(((FlagTerm)notTerm.getTerm()).getFlags().contains(Flag.ANSWERED)); notTerm = (NotTerm) terms[1]; Flags siFlags = new Flags(); - siFlags.add(AbstractMailReceiver.SI_USER_FLAG); + siFlags.add(AbstractMailReceiver.DEFAULT_SI_USER_FLAG); assertTrue(((FlagTerm)notTerm.getTerm()).getFlags().contains(siFlags)); } @@ -114,4 +130,5 @@ public class ImapMailSearchTermsTests { SearchTerm searchTerms = (SearchTerm) compileSearchTerms.invoke(receiver, flags); assertTrue(searchTerms instanceof NotTerm); } + } diff --git a/spring-integration-mail/src/test/java/org/springframework/integration/mail/PoorMansMailServer.java b/spring-integration-mail/src/test/java/org/springframework/integration/mail/PoorMansMailServer.java index 5c309844ce..31904aefdf 100644 --- a/spring-integration-mail/src/test/java/org/springframework/integration/mail/PoorMansMailServer.java +++ b/spring-integration-mail/src/test/java/org/springframework/integration/mail/PoorMansMailServer.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-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. @@ -23,7 +23,9 @@ import java.io.OutputStreamWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -209,6 +211,13 @@ public class PoorMansMailServer { super(port); } + @Override + public void resetServer() { + super.resetServer(); + this.seen = false; + this.idled = false; + } + @Override protected MailHandler mailHandler(Socket socket) { return new ImapHandler(socket); @@ -255,19 +264,18 @@ public class PoorMansMailServer { else { write("* OK"); } + write("* OK [PERMANENTFLAGS (\\Deleted \\Seen \\*)]"); // \* - user flags allowed write(tag + "OK SELECT completed"); } else if (line.endsWith("EXAMINE INBOX")) { write(tag + "OK"); } else if (line.endsWith("SEARCH FROM bar@baz UNSEEN ALL")) { - if (seen) { - write("* SEARCH"); - } - else { - write("* SEARCH 1"); - } - write(tag + "OK SEARCH completed"); + searchReply(tag); + } + else if (line.endsWith("SEARCH NOT (DELETED) NOT (SEEN) NOT (KEYWORD testSIUserFlag) ALL")) { + searchReply(tag); + assertions.add("searchWithUserFlag"); } else if (line.contains("FETCH 1 (ENVELOPE")) { write("* 1 FETCH (RFC822.SIZE 6909 INTERNALDATE \"27-May-2013 09:45:41 +0000\" " @@ -312,6 +320,10 @@ public class PoorMansMailServer { else if (line.contains("NOOP")) { write(tag + "OK NOOP completed"); } + else if(line.endsWith("STORE 1 +FLAGS (testSIUserFlag)")) { + write(tag + "OK STORE completed"); + assertions.add("storeUserFlag"); + } else if (line.endsWith("IDLE")) { write("+ idling"); idleTag = tag; @@ -341,6 +353,16 @@ public class PoorMansMailServer { } } + public void searchReply(String tag) throws IOException { + if (seen) { + write("* SEARCH"); + } + else { + write("* SEARCH 1"); + } + write(tag + "OK SEARCH completed"); + } + } } @@ -351,6 +373,8 @@ public class PoorMansMailServer { private final ExecutorService exec = Executors.newCachedThreadPool(); + protected final Set assertions = new HashSet(); + protected final List messages = new ArrayList(); private volatile boolean listening; @@ -361,6 +385,10 @@ public class PoorMansMailServer { exec.execute(this); } + public int getPort() { + return this.socket.getLocalPort(); + } + public boolean isListening() { return listening; } @@ -369,6 +397,14 @@ public class PoorMansMailServer { return messages; } + public void resetServer() { + this.assertions.clear(); + } + + public boolean assertReceived(String assertion) { + return this.assertions.contains(assertion); + } + @Override public void run() { try { diff --git a/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/ImapIdleIntegrationTests.java b/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/ImapIdleIntegrationTests.java index 90e2c0e6ae..9c8ee94ba0 100644 --- a/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/ImapIdleIntegrationTests.java +++ b/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/ImapIdleIntegrationTests.java @@ -64,6 +64,7 @@ public class ImapIdleIntegrationTests { doAnswer(new Answer() { // ensures that waitFornewMessages call blocks after a first execution // to emulate the behavior of IDLE + @Override public Object answer(InvocationOnMock invocation) throws Throwable { if (block.get()) { Thread.sleep(5000); @@ -103,7 +104,7 @@ public class ImapIdleIntegrationTests { assertTrue(txProcessorLatch.await(10, TimeUnit.SECONDS)); adapter.stop(); - context.destroy(); + context.close(); } diff --git a/src/reference/asciidoc/mail.adoc b/src/reference/asciidoc/mail.adoc index 59e47de441..63cf4bea5a 100644 --- a/src/reference/asciidoc/mail.adoc +++ b/src/reference/asciidoc/mail.adoc @@ -15,12 +15,12 @@ It delegates to a configured instance of Spring's `JavaMailSender`: `MailSendingMessageHandler` has various mapping strategies that use Spring's `MailMessage` abstraction. If the received Message's payload is already a `MailMessage` instance, it will be sent directly. -Therefore, it is generally recommended to precede this consumer with a Transformer for non-trivial MailMessage construction requirements. +Therefore, it is generally recommended to precede this consumer with a Transformer for non-trivial `MailMessage` construction requirements. However, a few simple Message mapping strategies are supported out-of-the-box. For example, if the message payload is a byte array, then that will be mapped to an attachment. For simple text-based emails, you can provide a String-based Message payload. In that case, a MailMessage will be created with that String as the text content. -If you are working with a Message payload type whose toString() method returns appropriate mail text content, then consider adding Spring Integration's _ObjectToStringTransformer_ prior to the outbound Mail adapter (see the example within <> for more detail). +If you are working with a Message payload type whose `toString()`` method returns appropriate mail text content, then consider adding Spring Integration's _ObjectToStringTransformer_ prior to the outbound Mail adapter (see the example within <> for more detail). The outbound MailMessage may also be configured with certain values from the `MessageHeaders`. If available, values will be mapped to the outbound mail's properties, such as the recipients (TO, CC, and BCC), the from/reply-to, and the subject. @@ -36,7 +36,7 @@ The header names are defined by the following constants: ---- NOTE: `MailHeaders` also allows you to override corresponding `MailMessage` values. -For example: If `MailMessage.to` is set to 'foo@bar.com' and `MailHeaders.TO` Message header is provided it will take precedence and override the corresponding value in `MailMessage` +For example: If `MailMessage.to` is set to 'foo@bar.com' and `MailHeaders.TO` Message header is provided it will take precedence and override the corresponding value in `MailMessage`. [[mail-inbound]] === Mail-Receiving Channel Adapter @@ -86,7 +86,8 @@ Alternatively, provide the host, username, and password: host="somehost" username="someuser" password="somepassword"/> ---- -NOTE: Keep in mind, as with any outbound Channel Adapter, if the referenced channel is a PollableChannel, a sub-element should be provided with either an interval-trigger or cron-trigger. +NOTE: Keep in mind, as with any outbound Channel Adapter, if the referenced channel is a `PollableChannel`, +a `` sub-element should be provided (see ). When using the namespace support, a _header-enricher_ Message Transformer is also available. This simplifies the application of the headers mentioned above to any Message prior to sending to the Mail Outbound Channel Adapter. @@ -153,10 +154,18 @@ IMPORTANT: If your username contains the '@' character use '%40' instead of '@' ---- -By default, the `ImapMailReceiver` will search for Messages based on the default `SearchTerm` which is _All mails that are RECENT (if supported), that are NOT ANSWERED, that are NOT DELETED, that are NOT SEEN and have not -been processed by this mail receiver (enabled by the use of the custom USER flag or simply NOT FLAGGED if not supported)_. -Since version 2.2, the `SearchTerm` used by the `ImapMailReceiver` is fully configurable via the `SearchTermStrategy` which you can inject via the `search-term-strategy` attribute. -`SearchTermStrategy` is a simple strategy interface with a single method that allows you to create an instance of the `SearchTerm` that will be used by the `ImapMailReceiver`. +[[search-term]] +By default, the `ImapMailReceiver` will search for Messages based on the default `SearchTerm` which is _All mails that +are RECENT (if supported), that are NOT ANSWERED, that are NOT DELETED, that are NOT SEEN and have not +been processed by this mail receiver (enabled by the use of the custom USER flag or simply NOT FLAGGED if not +supported)_. +The custom user flag is `spring-integration-mail-adapter` but can be configured. +Since version 2.2, the `SearchTerm` used by the `ImapMailReceiver` is fully configurable via the `SearchTermStrategy` +which you can inject via the `search-term-strategy` attribute. +`SearchTermStrategy` is a simple strategy interface with a single method that allows you to create an instance of the +`SearchTerm` that will be used by the `ImapMailReceiver`. + +See <> regarding message flagging. [source,java] ---- @@ -206,24 +215,27 @@ Again, below notes are based on GMAIL. With Java Mail 1.4.1 if `mail.imaps.timeout` property is set for a relatively short period of time (e.g., ~ 5 min) then `IMAPFolder.idle()` will throw `FolderClosedException` after this timeout. However if this property is not set (should be indefinite) the behavior that was observed is that `IMAPFolder.idle()` method never returns nor it throws an exception. It will however reconnect automatically if connection was lost for a short period of time (e.g., under 10 min), but if connection was lost for a long period of time (e.g., over 10 min), then`IMAPFolder.idle()` will not throw `FolderClosedException` nor it will re-establish connection and will remain in the blocked state indefinitely, thus leaving you no possibility to reconnect without restarting the adapter. -So the only way to make re-connect to work with Java Mail 1.4.1 is to set `mail.imaps.timeout` property explicitly to some value, but it also means that such value shoudl be relatively short (under 10 min) and the connection should be re-estabished relatively quickly. +So the only way to make re-connect to work with Java Mail 1.4.1 is to set `mail.imaps.timeout` property explicitly to some value, but it also means that such value should be relatively short (under 10 min) and the connection should be re-established relatively quickly. Again, it may be different with other providers. With Java Mail 1.4.3 there was significant improvements to the API ensuring that there will always be a condition which will force `IMAPFolder.idle()` method to return via `StoreClosedException` or `FolderClosedException` or simply return, thus allowing us to proceed with auto-reconnect. Currently auto-reconnect will run infinitely making attempts to reconnect every 10 sec. -IMPORTANT: In both configurations `channel` and `should-delete-messages` are the _REQUIRED_     attributes. +IMPORTANT: In both configurations `channel` and `should-delete-messages` are the _REQUIRED_ attributes. The important thing to understand is why `should-delete-messages` is required. -    The issue is with the POP3 protocol, which does NOT have any knowledge of messages that were READ. -It can only know what's been read      within a single session. -This means that when your POP3 mail adapter is running, emails are successfully consumed as as they become available during each poll     and no single email message will be delivered more then once. -However, as soon as you restart your adapter and begin a new session     all the email messages that might have been retrieved in the previous session will be retrieved again. +The issue is with the POP3 protocol, which does NOT have any knowledge of messages that were READ. +It can only know what's been read within a single session. +This means that when your POP3 mail adapter is running, emails are successfully consumed as as they become available during each poll +and no single email message will be delivered more then once. +However, as soon as you restart your adapter and begin a new session all the email messages that might have been retrieved in the previous session will be retrieved again. That is the nature of POP3. -Some might argue     that `should-delete-messages` should be TRUE by default. -In other words, there are two valid and mutually exclusive use cases      which make it very hard to pick a single "best" default. -You may want to configure your adapter as the only email receiver in which     case you want to be able to restart such adapter without fear that messages that were delivered before will not be redelivered again.      In this case setting `should-delete-messages` to TRUE would make most sense. -However, you may have another use case where      you may want to have multiple adapters that simply monitor email servers and their content. -In other words you just want to 'peek but not touch'.      Then setting `should-delete-messages` to FALSE would be much more appropriate. -So since it is hard to choose what should be     the right default value for the `should-delete-messages` attribute, we simply made it a required attribute, to be set by the user. +Some might argue that `should-delete-messages` should be TRUE by default. +In other words, there are two valid and mutually exclusive use cases which make it very hard to pick a single "best" default. +You may want to configure your adapter as the only email receiver in which case you want to be able to restart such adapter without fear that messages that were delivered before will not be redelivered again. +In this case setting `should-delete-messages` to TRUE would make most sense. +However, you may have another use case where you may want to have multiple adapters that simply monitor email servers and their content. +In other words you just want to 'peek but not touch'. +Then setting `should-delete-messages` to FALSE would be much more appropriate. +So since it is hard to choose what should be the right default value for the `should-delete-messages` attribute, we simply made it a required attribute, to be set by the user. Leaving it up to the user also means, you will be less likely to end up with unintended behavior. NOTE: When configuring a polling email adapter's _should-mark-messages-as-read_ attribute, be aware of the protocol you are configuring to retrieve messages. @@ -237,7 +249,7 @@ This can cause messages to be lost. You may wish to consider using transaction synchronization instead - see <> ===== -The also accepts the 'error-channel' attribute. +The `` also accepts the 'error-channel' attribute. If a downstream exception is thrown and an 'error-channel' is specified, a MessagingException message containing the failed message and original exception, will be sent to this channel. Otherwise, if the downstream channels are synchronous, any such exception will simply be logged as a warning by the channel adapter. @@ -245,6 +257,22 @@ NOTE: Beginning with the 3.0 release, the IMAP idle adapter emits application ev This allows applications to detect and act on those exceptions. The events can be obtained using an `` or any `ApplicationListener` configured to receive an `ImapIdleExceptionEvent` or one of its super classes. +[[imap-seen]] +=== Marking IMAP Messages When \Recent is Not Supported + +If `shouldMarkMessagesAsRead` is true, the IMAP adapters set the `\Seen` flag. + +In addition, when an email server does not support the `\Recent` flag, the IMAP adapters mark messages with a user +flag (`spring-integration-mail-adapter` by default) as long as the server supports user flags. +If not, `Flag.FLAGGED` is set to `true`. +These flags are applied regardless of the `shouldMarkMessagesRead` setting. + +As discussed in <>, the default `SearchTermStrategy` will ignore messages so flagged. + +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. + [[mail-filtering]] === Email Message Filtering @@ -286,8 +314,8 @@ In other while our adapter may peek at the email it also lets the email server k === Transaction Synchronization Transaction synchronization for inbound adapters allows you to take different actions after a transaction commits, or rolls back. -Transaction synchronization is enabled by adding a element to the poller for the polled , or to the . -Even if there is no 'real' transaction involved, you can still enable this feature by using a`PseudoTransactionManager` with the element. +Transaction synchronization is enabled by adding a `` element to the poller for the polled ``, or to the ``. +Even if there is no 'real' transaction involved, you can still enable this feature by using a `PseudoTransactionManager` with the `` element. For more information, see <>. Because of the many different mail servers, and specifically the limitations that some have, at this time we only provide a strategy for these transaction synchronizations.