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 2023/02/13 01:43:12 UTC

[james-project] branch master updated: JAMES-3419 JMAP EmailBodyPart individual headers (#1433)

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


The following commit(s) were added to refs/heads/master by this push:
     new 3192f76b48 JAMES-3419 JMAP EmailBodyPart individual headers (#1433)
3192f76b48 is described below

commit 3192f76b486f37be6f417103dfe7ab199dc13e4d
Author: Benoit TELLIER <bt...@linagora.com>
AuthorDate: Mon Feb 13 08:43:06 2023 +0700

    JAMES-3419 JMAP EmailBodyPart individual headers (#1433)
---
 .../rfc8621/contract/EmailGetMethodContract.scala  | 57 ++++++++++++++++++++++
 .../james/jmap/json/EmailGetSerializer.scala       |  9 ++--
 .../scala/org/apache/james/jmap/mail/Email.scala   | 16 +++---
 .../org/apache/james/jmap/mail/EmailBodyPart.scala | 36 ++++++++------
 .../org/apache/james/jmap/mail/EmailGet.scala      | 14 +++---
 .../apache/james/jmap/method/EmailGetMethod.scala  | 14 ++++--
 .../apache/james/jmap/routes/DownloadRoutes.scala  | 10 ++--
 7 files changed, 115 insertions(+), 41 deletions(-)

diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailGetMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailGetMethodContract.scala
index bce366b985..e3d77f3573 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailGetMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailGetMethodContract.scala
@@ -3615,6 +3615,63 @@ trait EmailGetMethodContract {
          |}""".stripMargin)
   }
 
+  @Test
+  def bodyStructureShouldSupportSpecificHeaders(server: GuiceJamesServer): Unit = {
+    val path = MailboxPath.inbox(BOB)
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(path)
+    val message: Message = Message.Builder
+      .of
+      .setSubject("test")
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+    val messageId: MessageId = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessage(BOB.asString, path, AppendCommand.from(message))
+      .getMessageId
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Email/get",
+         |    {
+         |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "ids": ["${messageId.serialize}"],
+         |      "properties":["bodyStructure"],
+         |      "bodyProperties":["partId", "blobId", "size", "name", "type", "charset", "disposition", "cid", "header:Subject:asText"]
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+    val response = `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(request)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].state")
+      .inPath("methodResponses[0][1].list[0]")
+      .isEqualTo(
+      s"""{
+         |  "id": "${messageId.serialize}",
+         |  "bodyStructure": {
+         |    "header:Subject:asText": "test",
+         |    "charset": "UTF-8",
+         |    "size": 8,
+         |    "partId": "1",
+         |    "blobId": "1_1",
+         |    "type": "text/plain"
+         |  }
+         |}""".stripMargin)
+  }
+
   @Test
   def bodyStructureForSimpleMultipart(server: GuiceJamesServer): Unit = {
     val path = MailboxPath.inbox(BOB)
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailGetSerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailGetSerializer.scala
index a07953d846..bb8f29ac81 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailGetSerializer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailGetSerializer.scala
@@ -44,7 +44,8 @@ object EmailBodyPartToSerialize {
     language = part.language,
     location = part.location,
     name = part.name,
-    subParts = part.subParts.map(list => list.map(EmailBodyPartToSerialize.from)))
+    subParts = part.subParts.map(list => list.map(EmailBodyPartToSerialize.from)),
+    specificHeaders = part.specificHeaders)
 }
 
 case class EmailBodyPartToSerialize(partId: PartId,
@@ -58,7 +59,8 @@ case class EmailBodyPartToSerialize(partId: PartId,
                                     cid: Option[Cid],
                                     language: Option[Languages],
                                     location: Option[Location],
-                                    subParts: Option[List[EmailBodyPartToSerialize]])
+                                    subParts: Option[List[EmailBodyPartToSerialize]],
+                                    specificHeaders: Map[String, Option[EmailHeaderValue]])
 
 object EmailGetSerializer {
   private implicit val mailboxIdWrites: Writes[MailboxId] = mailboxId => JsString(mailboxId.serialize)
@@ -147,7 +149,8 @@ object EmailGetSerializer {
       (__ \ "cid").writeNullable[Cid] and
       (__ \ "language").writeNullable[Languages] and
       (__ \ "location").writeNullable[Location] and
-      (__ \ "subParts").lazyWriteNullable(implicitly[Writes[List[EmailBodyPartToSerialize]]])
+      (__ \ "subParts").lazyWriteNullable(implicitly[Writes[List[EmailBodyPartToSerialize]]]) and
+        JsPath.write[Map[String, Option[EmailHeaderValue]]]
     )(unlift(EmailBodyPartToSerialize.unapply))
 
   private implicit val bodyPartWrites: Writes[EmailBodyPart] = part => bodyPartWritesToSerializeWrites.writes(EmailBodyPartToSerialize.from(part))
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala
index bc6c039658..decafec662 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala
@@ -272,10 +272,10 @@ object EmailHeaders {
       sentAt = extractDate(mime4JMessage, "Date").map(date => UTCDate.from(date, zoneId)))
   }
 
-  def extractSpecificHeaders(properties: Option[Properties])(zoneId: ZoneId, mime4JMessage: Message) = {
+  def extractSpecificHeaders(properties: Option[Properties])(zoneId: ZoneId, header: org.apache.james.mime4j.dom.Header) = {
     properties.getOrElse(Properties.empty()).value
       .flatMap(property => SpecificHeaderRequest.from(property).toOption)
-      .map(_.retrieveHeader(zoneId, mime4JMessage))
+      .map(_.retrieveHeader(zoneId, header))
       .toMap
   }
 
@@ -490,7 +490,7 @@ private class EmailHeaderViewFactory @Inject()(zoneIdProvider: ZoneIdProvider) e
           size = sanitizeSize(firstMessage.getSize),
           keywords = keywords),
         header = EmailHeaders.from(zoneIdProvider.get())(mime4JMessage),
-        specificHeaders = EmailHeaders.extractSpecificHeaders(request.properties)(zoneIdProvider.get(), mime4JMessage))
+        specificHeaders = EmailHeaders.extractSpecificHeaders(request.properties)(zoneIdProvider.get(), mime4JMessage.getHeader))
     }
   }
 }
@@ -509,7 +509,7 @@ private class EmailFullViewFactory @Inject()(zoneIdProvider: ZoneIdProvider, pre
         .map(Success(_))
         .getOrElse(Failure(new IllegalArgumentException("No message supplied")))
       mime4JMessage <- Email.parseAsMime4JMessage(firstMessage)
-      bodyStructure <- EmailBodyPart.of(messageId, mime4JMessage)
+      bodyStructure <- EmailBodyPart.of(request.bodyProperties, zoneIdProvider.get(), messageId, mime4JMessage)
       bodyValues <- extractBodyValues(htmlTextExtractor)(bodyStructure, request)
       blobId <- BlobId.of(messageId)
       preview <- Try(previewFactory.fromMime4JMessage(mime4JMessage))
@@ -534,7 +534,7 @@ private class EmailFullViewFactory @Inject()(zoneIdProvider: ZoneIdProvider, pre
           htmlBody = bodyStructure.htmlBody,
           attachments = bodyStructure.attachments,
           bodyValues = bodyValues),
-        specificHeaders = EmailHeaders.extractSpecificHeaders(request.properties)(zoneIdProvider.get(), mime4JMessage))
+        specificHeaders = EmailHeaders.extractSpecificHeaders(request.properties)(zoneIdProvider.get(), mime4JMessage.getHeader))
     }
   }
 
@@ -693,7 +693,7 @@ private class EmailFastViewReader @Inject()(messageIdManager: MessageIdManager,
           hasAttachment = HasAttachment(fastView.hasAttachment),
           preview = fastView.getPreview),
         header = EmailHeaders.from(zoneIdProvider.get())(mime4JMessage),
-        specificHeaders = EmailHeaders.extractSpecificHeaders(request.properties)(zoneIdProvider.get(), mime4JMessage))
+        specificHeaders = EmailHeaders.extractSpecificHeaders(request.properties)(zoneIdProvider.get(), mime4JMessage.getHeader))
     }
   }
 }
@@ -784,8 +784,8 @@ private class EmailFastViewWithAttachmentsMetadataReader @Inject()(messageIdMana
           hasAttachment = HasAttachment(fastView.hasAttachment),
           preview = fastView.getPreview),
         header = EmailHeaders.from(zoneIdProvider.get())(mime4JMessage),
-        specificHeaders = EmailHeaders.extractSpecificHeaders(request.properties)(zoneIdProvider.get(), mime4JMessage),
-        attachments = AttachmentsMetadata(firstMessage.getLoadedAttachments.asScala.toList.map(EmailBodyPart.fromAttachment(_, mime4JMessage))))
+        specificHeaders = EmailHeaders.extractSpecificHeaders(request.properties)(zoneIdProvider.get(), mime4JMessage.getHeader),
+        attachments = AttachmentsMetadata(firstMessage.getLoadedAttachments.asScala.toList.map(EmailBodyPart.fromAttachment(request.bodyProperties, zoneIdProvider.get(), _, mime4JMessage))))
     }
   }
 }
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailBodyPart.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailBodyPart.scala
index f3af9e778f..142c244bb8 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailBodyPart.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailBodyPart.scala
@@ -20,6 +20,7 @@
 package org.apache.james.jmap.mail
 
 import java.io.OutputStream
+import java.time.ZoneId
 
 import cats.implicits._
 import com.google.common.io.CountingOutputStream
@@ -73,16 +74,16 @@ object EmailBodyPart {
   val defaultProperties: Properties = Properties("partId", "blobId", "size", "name", "type", "charset", "disposition", "cid", "language", "location")
   val allowedProperties: Properties = defaultProperties ++ Properties("subParts", "headers")
 
-  def of(messageId: MessageId, message: MessageResult): Try[EmailBodyPart] = {
+  def of(properties: Option[Properties], zoneId: ZoneId, messageId: MessageId, message: MessageResult): Try[EmailBodyPart] = {
     val defaultMessageBuilder = new DefaultMessageBuilder
     defaultMessageBuilder.setMimeEntityConfig(MimeConfig.PERMISSIVE)
     defaultMessageBuilder.setDecodeMonitor(DecodeMonitor.SILENT)
 
     val mime4JMessage = Try(defaultMessageBuilder.parseMessage(message.getFullContent.getInputStream))
-    mime4JMessage.flatMap(of(messageId, _))
+    mime4JMessage.flatMap(of(properties, zoneId, messageId, _))
   }
 
-  def fromAttachment(attachment: MessageAttachmentMetadata, entity: Message): EmailBodyPart = {
+  def fromAttachment(properties: Option[Properties], zoneId: ZoneId, attachment: MessageAttachmentMetadata, entity: Message): EmailBodyPart = {
     def parseDisposition(attachment: MessageAttachmentMetadata): Option[Disposition] =
       if (attachment.isInline) {
         Option(Disposition.INLINE)
@@ -105,45 +106,48 @@ object EmailBodyPart {
       language = Option.empty,
       location = Option.empty,
       subParts = Option.empty,
-      entity = entity)
+      entity = entity,
+      specificHeaders = EmailHeaders.extractSpecificHeaders(properties)(zoneId, entity.getHeader))
   }
 
-  def of(messageId: MessageId, message: Message): Try[EmailBodyPart] =
-    of(messageId, PartId(1), message).map(_._1)
+  def of(properties: Option[Properties], zoneId: ZoneId, messageId: MessageId, message: Message): Try[EmailBodyPart] =
+    of(properties, zoneId, messageId, PartId(1), message).map(_._1)
 
-  private def of(messageId: MessageId, partId: PartId, entity: Entity): Try[(EmailBodyPart, PartId)] =
+  private def of(properties: Option[Properties], zoneId: ZoneId, messageId: MessageId, partId: PartId, entity: Entity): Try[(EmailBodyPart, PartId)] =
     entity.getBody match {
       case multipart: Multipart =>
         val scanResults: Try[List[(Option[EmailBodyPart], PartId)]] = multipart.getBodyParts
           .asScala.toList
-          .scanLeft[Try[(Option[EmailBodyPart], PartId)]](Success((None, partId)))(traverse(messageId))
+          .scanLeft[Try[(Option[EmailBodyPart], PartId)]](Success((None, partId)))(traverse(properties, zoneId, messageId))
           .sequence
         val highestPartIdValidation: Try[PartId] = scanResults.map(list => list.map(_._2).reverse.headOption.getOrElse(partId))
         val childrenValidation: Try[List[EmailBodyPart]] = scanResults.map(list => list.flatMap(_._1))
 
         zip(childrenValidation, highestPartIdValidation)
             .flatMap {
-              case (children, highestPartId) => of(None, partId, entity, Some(children))
+              case (children, highestPartId) => of(properties, zoneId, None, partId, entity, Some(children))
                 .map(part => (part, highestPartId))
             }
       case _ => BlobId.of(messageId, partId)
-          .flatMap(blobId => of(Some(blobId), partId, entity, None))
+          .flatMap(blobId => of(properties, zoneId, Some(blobId), partId, entity, None))
           .map(part => (part, partId))
     }
 
-  private def traverse(messageId: MessageId)(acc: Try[(Option[EmailBodyPart], PartId)], entity: Entity): Try[(Option[EmailBodyPart], PartId)] = {
+  private def traverse(properties: Option[Properties], zoneId: ZoneId, messageId: MessageId)(acc: Try[(Option[EmailBodyPart], PartId)], entity: Entity): Try[(Option[EmailBodyPart], PartId)] = {
     acc.flatMap {
       case (_, previousPartId) =>
         val partId = previousPartId.next
 
-        of(messageId, partId, entity)
+        of(properties, zoneId, messageId, partId, entity)
           .map({
             case (part, partId) => (Some(part), partId)
           })
     }
   }
 
-  private def of(blobId: Option[BlobId],
+  private def of(properties: Option[Properties],
+                 zoneId: ZoneId,
+                 blobId: Option[BlobId],
                  partId: PartId,
                  entity: Entity,
                  subParts: Option[List[EmailBodyPart]]): Try[EmailBodyPart] =
@@ -162,7 +166,8 @@ object EmailBodyPart {
           location = headerValue(entity, "Content-Location")
             .map(Location),
           subParts = subParts,
-          entity = entity))
+          entity = entity,
+          specificHeaders = EmailHeaders.extractSpecificHeaders(properties)(zoneId, entity.getHeader)))
 
   private def headerValue(entity: Entity, headerName: String): Option[String] = entity.getHeader
     .getFields(headerName)
@@ -259,7 +264,8 @@ case class EmailBodyPart(partId: PartId,
                          language: Option[Languages],
                          location: Option[Location],
                          subParts: Option[List[EmailBodyPart]],
-                         entity: Entity) {
+                         entity: Entity,
+                         specificHeaders: Map[String, Option[EmailHeaderValue]]) {
 
   def bodyContent: Try[Option[EmailBodyValue]] = entity.getBody match {
     case textBody: Mime4JTextBody =>
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailGet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailGet.scala
index 60d0dc9cab..a479e1f8f8 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailGet.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailGet.scala
@@ -94,15 +94,15 @@ case class EmailGetResponse(accountId: AccountId,
                             notFound: EmailNotFound)
 
 case class SpecificHeaderRequest(property: NonEmptyString, headerName: String, parseOption: Option[ParseOption], isAll: Boolean = false) {
-  def retrieveHeader(zoneId: ZoneId, message: Message): (String, Option[EmailHeaderValue]) =
+  def retrieveHeader(zoneId: ZoneId, header: org.apache.james.mime4j.dom.Header): (String, Option[EmailHeaderValue]) =
     if (isAll) {
-      extractAllHeaders(zoneId, message)
+      extractAllHeaders(zoneId, header)
     } else {
-      extractLastHeader(zoneId, message)
+      extractLastHeader(zoneId, header)
     }
 
-  private def extractAllHeaders(zoneId: ZoneId, message: Message) = {
-    val fields: List[Field] = Option(message.getHeader.getFields(headerName))
+  private def extractAllHeaders(zoneId: ZoneId, header: org.apache.james.mime4j.dom.Header) = {
+    val fields: List[Field] = Option(header.getFields(headerName))
       .map(_.asScala.toList)
       .getOrElse(List())
 
@@ -110,8 +110,8 @@ case class SpecificHeaderRequest(property: NonEmptyString, headerName: String, p
     (property.value, Some(AllHeaderValues(fields.map(toHeader(zoneId, option)))))
   }
 
-  private def extractLastHeader(zoneId: ZoneId, message: Message) = {
-    val field: Option[Field] = Option(message.getHeader.getFields(headerName))
+  private def extractLastHeader(zoneId: ZoneId, header: org.apache.james.mime4j.dom.Header) = {
+    val field: Option[Field] = Option(header.getFields(headerName))
       .map(_.asScala)
       .flatMap(fields => fields.reverse.headOption)
 
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailGetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailGetMethod.scala
index a9cc31b665..568b661e9f 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailGetMethod.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailGetMethod.scala
@@ -130,11 +130,17 @@ class EmailGetMethod @Inject() (readerFactory: EmailViewReaderFactory,
     request.bodyProperties match {
       case None => Right(EmailBodyPart.defaultProperties)
       case Some(properties) =>
-        val invalidProperties = properties -- EmailBodyPart.allowedProperties
-        if (invalidProperties.isEmpty()) {
-          Right(properties)
+        val invalidProperties: Set[NonEmptyString] = properties.value
+          .flatMap(property => SpecificHeaderRequest.from(property)
+            .fold(
+              invalidProperty => Some(invalidProperty),
+              _ => None
+            )) -- EmailBodyPart.allowedProperties.value
+
+        if (invalidProperties.nonEmpty) {
+          Left(new IllegalArgumentException(s"The following bodyProperties [${invalidProperties.map(p => p.value).mkString(", ")}] do not exist."))
         } else {
-          Left(new IllegalArgumentException(s"The following bodyProperties [${invalidProperties.format()}] do not exist."))
+          Right(properties)
         }
     }
 
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/DownloadRoutes.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/DownloadRoutes.scala
index 0b54980f25..63317105da 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/DownloadRoutes.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/DownloadRoutes.scala
@@ -37,7 +37,7 @@ import org.apache.james.jmap.http.Authenticator
 import org.apache.james.jmap.http.rfc8621.InjectionKeys
 import org.apache.james.jmap.json.ResponseSerializer
 import org.apache.james.jmap.mail.{BlobId, EmailBodyPart, PartId}
-import org.apache.james.jmap.method.AccountNotFoundException
+import org.apache.james.jmap.method.{AccountNotFoundException, ZoneIdProvider}
 import org.apache.james.jmap.routes.DownloadRoutes.{BUFFER_SIZE, LOGGER}
 import org.apache.james.jmap.{Endpoint, JMAPRoute, JMAPRoutes}
 import org.apache.james.mailbox.model.ContentType.{MediaType, MimeType, SubType}
@@ -54,12 +54,13 @@ import reactor.core.publisher.Mono
 import reactor.core.scala.publisher.SMono
 import reactor.core.scheduler.Schedulers
 import reactor.netty.http.server.{HttpServerRequest, HttpServerResponse}
-
 import java.io.InputStream
 import java.nio.charset.StandardCharsets
 import java.util.stream
 import java.util.stream.Stream
+
 import javax.inject.{Inject, Named}
+
 import scala.compat.java8.FunctionConverters._
 import scala.jdk.CollectionConverters._
 import scala.util.{Failure, Success, Try}
@@ -186,7 +187,8 @@ class AttachmentBlobResolver @Inject()(val attachmentManager: AttachmentManager)
 }
 
 class MessagePartBlobResolver @Inject()(val messageIdFactory: MessageId.Factory,
-                                        val messageIdManager: MessageIdManager) extends BlobResolver {
+                                        val messageIdManager: MessageIdManager,
+                                        val zoneIdSupplier: ZoneIdProvider) extends BlobResolver {
   private def asMessageAndPartId(blobId: BlobId): Try[(MessageId, PartId)] = {
     blobId.value.value.split('_').toList match {
       case List(messageIdString, partIdString) => for {
@@ -206,7 +208,7 @@ class MessagePartBlobResolver @Inject()(val messageIdFactory: MessageId.Factory,
         Applicable(SMono.fromPublisher(
           messageIdManager.getMessagesReactive(List(messageId).asJava, FetchGroup.FULL_CONTENT, mailboxSession))
           .handle[EmailBodyPart] {
-            case (message, sink) => EmailBodyPart.of(messageId, message)
+            case (message, sink) => EmailBodyPart.of(None, zoneIdSupplier.get(), messageId, message)
               .fold(sink.error, sink.next)
           }
           .handle[EmailBodyPart] {


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