此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Integration 6.4.4spring-doc.cadn.net.cn

邮件支持

本节介绍如何在 Spring 集成中处理邮件消息。spring-doc.cadn.net.cn

您需要将此依赖项包含在您的项目中:spring-doc.cadn.net.cn

<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-mail</artifactId>
    <version>6.3.10-SNAPSHOT</version>
</dependency>
compile "org.springframework.integration:spring-integration-mail:6.3.10-SNAPSHOT"

jakarta.mail:jakarta.mail-api必须通过特定于供应商的实施来包含。spring-doc.cadn.net.cn

邮件发送通道适配器

Spring 集成通过MailSendingMessageHandler. 它委托给 Spring 的JavaMailSender,如下例所示:spring-doc.cadn.net.cn

 JavaMailSender mailSender = context.getBean("mailSender", JavaMailSender.class);

 MailSendingMessageHandler mailSendingHandler = new MailSendingMessageHandler(mailSender);

MailSendingMessageHandler具有各种使用 Spring 的MailMessage抽象化。 如果收到的消息的有效负载已经是MailMessage实例,则直接发送。 因此,我们通常建议您在此 consumer 前面加上一个 transformer for importantMailMessage施工要求。 但是, Spring 集成支持一些简单的消息 Map 策略。 例如,如果消息负载是一个字节数组,则映射到一个附件。 对于简单的基于文本的电子邮件,您可以提供基于字符串的消息负载。 在这种情况下,一个MailMessage是使用String作为文本内容。 如果您使用的消息负载类型,其toString()方法返回适当的邮件文本内容,请考虑添加 Spring 集成的ObjectToStringTransformer在出站邮件适配器之前(有关更多详细信息,请参阅使用 XML 配置转换器中的示例)。spring-doc.cadn.net.cn

您还可以配置出站MailMessage具有MessageHeaders. 如果可用,值将映射到出站邮件的属性,例如收件人(To、Cc 和 BCc)、fromreply-tosubject. 标头名称由以下常量定义:spring-doc.cadn.net.cn

 MailHeaders.SUBJECT
 MailHeaders.TO
 MailHeaders.CC
 MailHeaders.BCC
 MailHeaders.FROM
 MailHeaders.REPLY_TO
MailHeaders还允许您覆盖相应的MailMessage值。 例如,如果MailMessage.to设置为 '[email protected]' 并且MailHeaders.TOmessage 标头,则它优先并覆盖MailMessage.

邮件接收通道适配器

Spring 集成还通过MailReceivingMessageSource. 它委托给 Spring 集成自己的已配置实例MailReceiver接口。 有两种实现:Pop3MailReceiverImapMailReceiver. 实例化其中任何一个的最简单方法是将邮件存储的 'uri' 绕过到接收方的构造函数,如下例所示:spring-doc.cadn.net.cn

MailReceiver receiver = new Pop3MailReceiver("pop3://usr:pwd@localhost/INBOX");

接收邮件的另一个选项是 IMAPidle命令(如果您的邮件服务器支持)。 Spring 集成提供了ImapIdleChannelAdapter,它本身就是一个消息生成终端节点。 它委托给ImapMailReceiver. 下一节提供了在“mail”模式中使用 Spring 集成的名称空间支持配置两种类型的入站通道适配器的示例。spring-doc.cadn.net.cn

通常,当IMAPMessage.getContent()方法,则呈现某些标头和正文(对于简单的文本电子邮件),如下例所示:spring-doc.cadn.net.cn

To: [email protected]
From: [email protected]
Subject: Test Email

something

使用简单的MimeMessage,getContent()返回邮件正文 (something在前面的示例中)。spring-doc.cadn.net.cn

从版本 2.2 开始,框架急切地获取 IMAP 消息并将它们公开为MimeMessage. 这会产生不希望的副作用,即更改getContent()行为。 版本 4.3 中引入的 Mail Mapping 增强功能进一步加剧了这种不一致性,因为当提供标头映射器时,有效负载由IMAPMessage.getContent()方法。 这意味着 IMAP 内容会有所不同,具体取决于是否提供了标头映射器。spring-doc.cadn.net.cn

从版本 5.0 开始,源自 IMAP 源的邮件根据IMAPMessage.getContent()行为,无论是否提供标头映射器。 如果您不使用标头映射器,并且希望恢复到以前仅渲染主体的行为,请将simpleContentboolean 属性设置为true. 现在,无论是否使用标头映射器,此属性都控制渲染。 现在,当提供标头映射器时,它允许仅正文渲染。spring-doc.cadn.net.cn

从版本 5.2 开始,autoCloseFolder选项。 将其设置为false不会在提取后自动关闭文件夹,而是使用IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE标头(请参阅MessageHeaderAccessor应用程序接口有关更多信息)将填充到从通道适配器发送到 Producer 的每条消息中。 这不适用于Pop3MailReceiver因为它依赖于打开和关闭文件夹来获取新消息。 目标应用程序负责调用close()在此标头上,只要下游流中需要:spring-doc.cadn.net.cn

Closeable closeableResource = StaticMessageHeaderAccessor.getCloseableResource(mailMessage);
if (closeableResource != null) {
    closeableResource.close();
}

在解析包含附件的电子邮件的多部分内容时需要与服务器通信的情况下,保持文件夹打开非常有用。 这close()IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCEheader 委托给AbstractMailReceiver以关闭文件夹expungeoption (如果shouldDeleteMessages分别在AbstractMailReceiver.spring-doc.cadn.net.cn

从版本 5.4 开始,现在可以返回MimeMessage原样,没有任何转换或 Eager 内容加载。 此功能通过以下选项组合启用:否headerMapper,则simpleContentproperty 为falseautoCloseFolderproperty 为false. 这MimeMessage作为生成的 Spring 消息的有效负载存在。 在这种情况下,唯一填充的标头是上面提到的IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE对于在处理MimeMessage已完成。spring-doc.cadn.net.cn

从版本 5.5.11 开始,文件夹将在AbstractMailReceiver.receive()如果未收到任何消息或所有消息都被独立于autoCloseFolder旗。 在这种情况下,对于可能的 logic around 周围没有可生成的任何下游IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE页眉。spring-doc.cadn.net.cn

从版本 6.0.5 开始,ImapIdleChannelAdapter不再执行异步消息发布。 这对于阻止下游邮件处理的空闲侦听器循环(例如,使用大附件)是必要的,因为邮件文件夹必须保持打开状态。 如果需要异步切换,则ExecutorChannel可用作此通道适配器的输出通道。spring-doc.cadn.net.cn

入站邮件映射

默认情况下,入站适配器生成的消息的有效负载是原始的MimeMessage. 您可以使用该对象来查询标题和内容。 从版本 4.3 开始,您可以提供HeaderMapper<MimeMessage>将标头映射到MessageHeaders. 为方便起见, Spring 集成提供了一个DefaultMailHeaderMapper为此目的。 它映射以下标头:spring-doc.cadn.net.cn

启用消息映射后,有效负载取决于邮件消息及其实现。 电子邮件内容通常由DataHandlerMimeMessage.spring-doc.cadn.net.cn

对于text/*email 中,有效负载是一个StringcontentTypeheader 与mail_contentType.spring-doc.cadn.net.cn

对于带有嵌入式jakarta.mail.Part实例、DataHandler通常会渲染一个Part对象。 这些对象不是Serializable并且不适合使用替代技术进行电子监管,例如Kryo. 因此,默认情况下,启用映射时,此类有效负载将呈现为 rawbyte[]包含Part数据。 示例PartMessageMultipart. 这contentTypeheader 为application/octet-stream在这种情况下。 要更改此行为并接收MultipartObject payload 中,将embeddedPartsAsBytesfalseMailReceiver. 对于DataHandler,内容将呈现为byte[]替换为contentType的标头application/octet-stream.spring-doc.cadn.net.cn

当您不提供标头映射器时,消息负载是MimeMessage主办单位jakarta.mail. 该框架提供了一个MailToStringTransformer,您可以使用策略将邮件内容转换为String:spring-doc.cadn.net.cn

   ...
   .transform(Mail.toStringTransformer())
   ...
@Bean
@Transformer(inputChannel="...", outputChannel="...")
public Transformer transformer() {
    return new MailToStringTransformer();
}
   ...
   transform(Mail.toStringTransformer())
   ...
<int-mail:mail-to-string-transformer ... >

从版本 4.3 开始,transformer 将 embeddedPart实例(以及Multipart实例,这些实例之前已处理过)。 transformer 是AbstractMailTransformer这将映射前面列表中的 address 和 subject 标头。 如果您希望对消息执行其他转换,请考虑子类化AbstractMailTransformer.spring-doc.cadn.net.cn

从版本 5.4 开始,当没有headerMapper提供,autoCloseFolderfalsesimpleContentfalseMimeMessage在生成的 Spring 消息的有效负载中按原样返回。 这样,MimeMessage在流的后面被引用时按需加载。 上述所有转换仍然有效。spring-doc.cadn.net.cn

Mail 命名空间支持

Spring 集成为与邮件相关的配置提供了一个命名空间。 要使用它,请配置以下架构位置:spring-doc.cadn.net.cn

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:int-mail="http://www.springframework.org/schema/integration/mail"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/integration/mail
    https://www.springframework.org/schema/integration/mail/spring-integration-mail.xsd">

To configure an outbound channel adapter, provide the channel from which to receive and the MailSender, as the following example shows:spring-doc.cadn.net.cn

<int-mail:outbound-channel-adapter channel="outboundMail"
    mail-sender="mailSender"/>

Alternatively, you can provide the host, username, and password, as the following example shows:spring-doc.cadn.net.cn

<int-mail:outbound-channel-adapter channel="outboundMail"
    host="somehost" username="someuser" password="somepassword"/>

Starting with version 5.1.3, the host,username ane mail-sender can be omitted, if java-mail-properties is provided. However, the hostusername has to be configured with appropriate Java mail properties, e.g. for SMTP:spring-doc.cadn.net.cn

[email protected]
mail.smtp.host=smtp.gmail.com
mail.smtp.port=587
As with any outbound Channel Adapter, if the referenced channel is a PollableChannel, you should provide a <poller> element (see Endpoint Namespace Support).

When you use the namespace support, you can also use a header-enricher message transformer. Doing so simplifies the application of the headers mentioned earlier to any message prior to sending to the mail outbound channel adapter.spring-doc.cadn.net.cn

The following example assumes the payload is a Java bean with appropriate getters for the specified properties, but you can use any SpEL expression:spring-doc.cadn.net.cn

<int-mail:header-enricher input-channel="expressionsInput" default-overwrite="false">
	<int-mail:to expression="payload.to"/>
	<int-mail:cc expression="payload.cc"/>
	<int-mail:bcc expression="payload.bcc"/>
	<int-mail:from expression="payload.from"/>
	<int-mail:reply-to expression="payload.replyTo"/>
	<int-mail:subject expression="payload.subject" overwrite="true"/>
</int-mail:header-enricher>

Alternatively, you can use the value attribute to specify a literal. You also can specify default-overwrite and individual overwrite attributes to control the behavior with existing headers.spring-doc.cadn.net.cn

To configure an inbound channel adapter, you have the choice between polling or event-driven (assuming your mail server supports IMAP idle — if not, then polling is the only option). A polling channel adapter requires the store URI and the channel to which to send inbound messages. The URI may begin with pop3imap. The following example uses an imap URI:spring-doc.cadn.net.cn

<int-mail:inbound-channel-adapter id="imapAdapter"
      store-uri="imaps://[username]:[password]@imap.gmail.com/INBOX"
      java-mail-properties="javaMailProperties"
      channel="receiveChannel"
      should-delete-messages="true"
      should-mark-messages-as-read="true"
      auto-startup="true">
      <int:poller max-messages-per-poll="1" fixed-rate="5000"/>
</int-mail:inbound-channel-adapter>

If you do have IMAP idle support, you may want to configure the imap-idle-channel-adapter element instead. Since the idle command enables event-driven notifications, no poller is necessary for this adapter. It sends a message to the specified channel as soon as it receives the notification that new mail is available. The following example configures an IMAP idle mail channel:spring-doc.cadn.net.cn

<int-mail:imap-idle-channel-adapter id="customAdapter"
      store-uri="imaps://[username]:[password]@imap.gmail.com/INBOX"
      channel="receiveChannel"
      auto-startup="true"
      should-delete-messages="false"
      should-mark-messages-as-read="true"
      java-mail-properties="javaMailProperties"/>

You can provide javaMailProperties by creating and populating a regular java.utils.Properties object — for example, by using the util namespace provided by Spring.spring-doc.cadn.net.cn

If your username contains the '@' character, use '%40' instead of '@' to avoid parsing errors from the underlying JavaMail API.

The following example shows how to configure a java.util.Properties object:spring-doc.cadn.net.cn

<util:properties id="javaMailProperties">
  <prop key="mail.imap.socketFactory.class">javax.net.ssl.SSLSocketFactory</prop>
  <prop key="mail.imap.socketFactory.fallback">false</prop>
  <prop key="mail.store.protocol">imaps</prop>
  <prop key="mail.debug">false</prop>
</util:properties>

默认情况下,ImapMailReceiver searches for messages based on the default SearchTerm, which is all mail messages that:spring-doc.cadn.net.cn

自定义用户标志为spring-integration-mail-adapter,但您可以对其进行配置。 从 2.2 版本开始,SearchTermImapMailReceiver可完全配置SearchTermStrategy,您可以使用search-term-strategy属性。 一个SearchTermStrategy是一个策略接口,其中包含一个方法,允许您创建SearchTermImapMailReceiver. 下面的清单显示了SearchTermStrategy接口:spring-doc.cadn.net.cn

public interface SearchTermStrategy {

    SearchTerm generateSearchTerm(Flags supportedFlags, Folder folder);

}

以下示例依赖于TestSearchTermStrategy而不是默认的SearchTermStrategy:spring-doc.cadn.net.cn

<mail:imap-idle-channel-adapter id="customAdapter"
			store-uri="imap:something"
			…
			search-term-strategy="searchTermStrategy"/>

<bean id="searchTermStrategy"
  class="o.s.i.mail.config.ImapIdleChannelAdapterParserTests.TestSearchTermStrategy"/>
重要:IMAP PEEK

从版本 4.1.1 开始,IMAP 邮件接收器使用mail.imap.peekmail.imaps.peekJavaMail 属性(如果指定)。 以前,接收器会忽略该属性,并始终将PEEK旗。 现在,如果您将此属性显式设置为false,则消息 ISE 标记为\Seen无论shouldMarkMessagesRead. 如果未指定,则保留之前的行为(peek 为true).spring-doc.cadn.net.cn

IMAP 协议idle和 Lost Connections

使用 IMAP 时idlechannel 适配器,则与服务器的连接可能会丢失(例如,由于网络故障),并且由于 JavaMail 文档明确指出实际的 IMAP API 是实验性的,因此了解 API 中的差异以及在配置 IMAP 时如何处理它们非常重要idle适配器。 目前,Spring 集成邮件适配器是使用 JavaMail 1.4.1 和 JavaMail 1.4.3 进行测试的。 根据所使用的方法,您必须特别注意一些需要设置的 JavaMail 属性,以便自动重新连接。spring-doc.cadn.net.cn

Gmail 观察到以下行为,但应该为您提供一些有关如何解决与其他提供商重新连接问题的提示。 但是,我们始终欢迎反馈。 同样,以下说明基于 Gmail。

在 JavaMail 1.4.1 中,如果将mail.imaps.timeout属性设置为相对较短的时间(在我们的测试中约为 5 分钟),IMAPFolder.idle()抛出FolderClosedException在此超时之后。 但是,如果未设置此属性(它应该是无限期的),则IMAPFolder.idle()method 永远不会返回,也永远不会引发异常。 但是,如果连接在短时间内丢失(在我们的测试中不到 10 分钟),它会自动重新连接。 但是,如果连接长时间丢失(超过 10 分钟),IMAPFolder.idle(),不抛出FolderClosedException并且不会重新建立连接,而是无限期地保持 blocked 状态,因此如果不重新启动适配器,则无法重新连接。 因此,使 JavaMail 1.4.1 的重新连接工作的唯一方法是将mail.imaps.timeoutproperty 显式地附加到某个值,但这也意味着这个值应该相对较短(不到 10 分钟),并且应该相对较快地重新建立连接。 同样,它与 Gmail 以外的提供商可能有所不同。 在 JavaMail 中,1.4.3 对 API 进行了重大改进,确保始终存在强制IMAPFolder.idle()method 返回StoreClosedExceptionFolderClosedException或者直接返回,从而让您继续进行自动重新连接。 目前,自动重新连接无限运行,每 10 秒尝试一次重新连接。spring-doc.cadn.net.cn

在这两种配置中,channelshould-delete-messages是必需的属性。 您应该了解原因should-delete-messages是必需的。 问题出在 POP3 协议上,该协议不知道已读取的消息。 它只能知道在单个会话中读取了什么。 这意味着,当您的 POP3 邮件适配器运行时,电子邮件将成功使用,因为它们在每次轮询期间都可用,并且不会多次发送任何一封电子邮件。 但是,一旦您重新启动适配器并开始新会话,就会再次检索可能在上一个会话中检索到的所有电子邮件。 这就是 POP3 的本质。 有些人可能会争辩说should-delete-messages应该是true默认情况下。 换句话说,有两个有效且互斥的用法,这使得选择单个最佳默认值变得非常困难。 您可能希望将适配器配置为唯一的电子邮件接收器,在这种情况下,您希望能够重新启动适配器,而不必担心以前传递的消息不会再次传递。 在这种情况下,将should-delete-messagestrue最有意义。 但是,您可能还有另一个用例,您可能希望让多个适配器监控电子邮件服务器及其内容。 换句话说,您想要 “窥视但不触摸”。 然后设置should-delete-messagesfalse这要合适得多。 因此,由于很难为should-delete-messages属性,我们将其设为由您设置的必需属性。 由您决定也意味着您不太可能最终出现意外行为。
配置轮询电子邮件适配器的should-mark-messages-as-read属性,您应该了解为检索消息而配置的协议。 例如,POP3 不支持此标志,这意味着将其设置为任一值都不起作用,因为邮件不会标记为已读。

在以静默方式断开连接的情况下,会定期在后台运行空闲取消任务(通常会立即处理新的 IDLE)。 为了控制此间隔,请使用cancelIdleInterval选项;默认 120 (2 分钟)。 RFC 2177 建议的间隔不超过 29 分钟。spring-doc.cadn.net.cn

您应该了解,这些作(将消息标记为已读和删除消息)是在收到消息之后但在处理消息之前执行的。 这可能会导致消息丢失。spring-doc.cadn.net.cn

您可能希望考虑改用事务同步。 请参阅 事务同步spring-doc.cadn.net.cn

<imap-idle-channel-adapter/>也接受 'error-channel' 属性。 如果引发下游异常并指定了 'error-channel',则MessagingException包含失败消息和原始异常的消息将发送到此通道。 否则,如果下游通道是同步的,则通道适配器会将任何此类异常记录为警告。spring-doc.cadn.net.cn

从 3.0 版本开始,IMAPidleadapter 发出应用程序事件(特别是ImapIdleExceptionEvent实例)。 这允许应用程序检测并处理这些异常。 您可以使用<int-event:inbound-channel-adapter>或任何ApplicationListener配置为接收ImapIdleExceptionEvent或它的超级类之一。

在以下情况下标记 IMAP 邮件\Recent不支持

如果shouldMarkMessagesAsRead为 true,则 IMAP 适配器会将\Seen旗。spring-doc.cadn.net.cn

此外,当电子邮件服务器不支持\Recent标志时,IMAP 适配器会使用用户标志标记邮件(默认情况下,spring-integration-mail-adapter),只要服务器支持用户标志。 如果没有,Flag.FLAGGED设置为true. 这些标志的 API 与shouldMarkMessagesRead设置。spring-doc.cadn.net.cn

null 中所述,默认的SearchTermStrategy忽略已标记的消息。spring-doc.cadn.net.cn

从版本 4.2.2 开始,您可以使用setUserFlagMailReceiver. 这样做可以让多个接收者使用不同的标志(只要邮件服务器支持用户标志)。 这user-flag属性在为适配器配置命名空间时可用。spring-doc.cadn.net.cn

电子邮件筛选

很多时候,您可能会遇到过滤传入消息的要求(例如,您只想阅读Subject行)。 您可以通过将入站邮件适配器与基于表达式的Filter. 尽管它会奏效,但这种方法也有缺点。 由于邮件在通过入站邮件适配器后会被过滤,因此所有此类邮件都将被标记为已读 (SEEN) 或 unreads(取决于should-mark-messages-as-read属性)。 但是,在现实中,将消息标记为SEEN仅当它们通过筛选条件时。 这类似于在预览窗格中滚动浏览所有邮件时查看电子邮件客户端,但仅将实际打开并读取为 的邮件进行标记SEEN.spring-doc.cadn.net.cn

Spring Integration 2.0.4 引入了mail-filter-expression属性inbound-channel-adapterimap-idle-channel-adapter. 此属性允许您提供作为 SPEL 和正则表达式组合的表达式。 例如,如果您只想读取主题行中包含 'Spring Integration' 的电子邮件,则可以配置mail-filter-expression属性,如下所示:mail-filter-expression="subject matches '(?i).Spring Integration.".spring-doc.cadn.net.cn

因为jakarta.mail.internet.MimeMessage是 SPEL 评估上下文的根上下文,您可以通过MimeMessage,包括消息的实际正文。 这一点特别重要,因为读取消息的正文通常会导致此类消息被标记为SEEN默认情况下。 但是,由于我们现在将PEEK标志设置为 'true',则只有明确标记为SEEN标记为已读。spring-doc.cadn.net.cn

因此,在以下示例中,此适配器仅输出与筛选表达式匹配的消息,并且仅将这些消息标记为已读:spring-doc.cadn.net.cn

<int-mail:imap-idle-channel-adapter id="customAdapter"
	store-uri="imaps://some_google_address:${password}@imap.gmail.com/INBOX"
	channel="receiveChannel"
	should-mark-messages-as-read="true"
	java-mail-properties="javaMailProperties"
	mail-filter-expression="subject matches '(?i).*Spring Integration.*'"/>

在前面的示例中,由于mail-filter-expression属性,则此适配器仅生成主题行中包含 'Spring Integration' 的消息。spring-doc.cadn.net.cn

另一个合理的问题是,在下一个轮询或空闲事件上会发生什么,或者当这样的适配器重新启动时会发生什么。 可以过滤重复的 Massages 吗?换句话说,如果在上次检索时,您有 5 封新邮件,但只有 1 封通过了过滤器,那么其他 4 封邮件会发生什么情况? 他们会在下一次轮询或空闲时再次执行筛选逻辑吗? 毕竟,他们没有被标记为SEEN. The answer is no. They would not be subject to duplicate processing due to another flag (RECENT) that is set by the email server and is used by the Spring Integration mail search filter. Folder implementations set this flag to indicate that this message is new to this folder. That is, it has arrived since the last time this folder was opened. In other words, while our adapter may peek at the email, it also lets the email server know that such email was touched and should therefore be marked as RECENT by the email server.spring-doc.cadn.net.cn

Transaction Synchronization

Transaction synchronization for inbound adapters lets you take different actions after a transaction commits or rolls back. You can enable transaction synchronization by adding a <transactional/> element to the poller for the polled <inbound-adapter/> or to the <imap-idle-inbound-adapter/>. Even if there is no 'real' transaction involved, you can still enable this feature by using a PseudoTransactionManager使用<transactional/> element. For more information, see Transaction Synchronization.spring-doc.cadn.net.cn

Because of the different mail servers and specifically the limitations that some have, at this time we provide only a strategy for these transaction synchronizations. You can send the messages to some other Spring Integration components or invoke a custom bean to perform some action. For example, to move an IMAP message to a different folder after the transaction commits, you might use something similar to the following:spring-doc.cadn.net.cn

<int-mail:imap-idle-channel-adapter id="customAdapter"
    store-uri="imaps://something.com:[email protected]/INBOX"
    channel="receiveChannel"
    auto-startup="true"
    should-delete-messages="false"
    java-mail-properties="javaMailProperties">
    <int:transactional synchronization-factory="syncFactory"/>
</int-mail:imap-idle-channel-adapter>

<int:transaction-synchronization-factory id="syncFactory">
    <int:after-commit expression="@syncProcessor.process(payload)"/>
</int:transaction-synchronization-factory>

<bean id="syncProcessor" class="thing1.thing2.Mover"/>

The following example shows what the Mover class might look like:spring-doc.cadn.net.cn

public class Mover {

    public void process(MimeMessage message) throws Exception {
        Folder folder = message.getFolder();
        folder.open(Folder.READ_WRITE);
        String messageId = message.getMessageID();
        Message[] messages = folder.getMessages();
        FetchProfile contentsProfile = new FetchProfile();
        contentsProfile.add(FetchProfile.Item.ENVELOPE);
        contentsProfile.add(FetchProfile.Item.CONTENT_INFO);
        contentsProfile.add(FetchProfile.Item.FLAGS);
        folder.fetch(messages, contentsProfile);
        // find this message and mark for deletion
        for (int i = 0; i < messages.length; i++) {
            if (((MimeMessage) messages[i]).getMessageID().equals(messageId)) {
                messages[i].setFlag(Flags.Flag.DELETED, true);
                break;
            }
        }

        Folder somethingFolder = store.getFolder("SOMETHING");
        somethingFolder.appendMessages(new MimeMessage[]{message});
        folder.expunge();
        folder.close(true);
        somethingFolder.close(false);
    }
}
For the message to be still available for manipulation after the transaction, should-delete-messages must be set to 'false'.

Configuring channel adapters with the Java DSL

To configure mail component in Java DSL, the framework provides a o.s.i.mail.dsl.Mail factory, which can be used like this:spring-doc.cadn.net.cn

@SpringBootApplication
public class MailApplication {

    public static void main(String[] args) {
        new SpringApplicationBuilder(MailApplication.class)
            .web(false)
            .run(args);
    }

    @Bean
    public IntegrationFlow imapMailFlow() {
        return IntegrationFlow
                .from(Mail.imapInboundAdapter("imap://user:pw@host:port/INBOX")
                            .searchTermStrategy(this::fromAndNotSeenTerm)
                            .userFlag("testSIUserFlag")
                            .simpleContent(true)
                            .javaMailProperties(p -> p.put("mail.debug", "false")),
                    e -> e.autoStartup(true)
                            .poller(p -> p.fixedDelay(1000)))
                .channel(MessageChannels.queue("imapChannel"))
                .get();
    }

    @Bean
    public IntegrationFlow sendMailFlow() {
        return IntegrationFlow.from("sendMailChannel")
                .enrichHeaders(Mail.headers()
                        .subjectFunction(m -> "foo")
                        .from("foo@bar")
                        .toFunction(m -> new String[] { "bar@baz" }))
                .handle(Mail.outboundAdapter("gmail")
                            .port(smtpServer.getPort())
                            .credentials("user", "pw")
                            .protocol("smtp"),
                    e -> e.id("sendMailEndpoint"))
                .get();
    }
}