This is an automated email from the ASF dual-hosted git repository.

zbendhiba pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new f91d1da56502 CAMEL-23258: Add google-mail:draft DataType transformer 
(#22389)
f91d1da56502 is described below

commit f91d1da56502dc1d157db8b5c0091b4954151a93
Author: Zineb BENDHIBA <[email protected]>
AuthorDate: Thu Apr 2 17:08:51 2026 +0200

    CAMEL-23258: Add google-mail:draft DataType transformer (#22389)
    
    * CAMEL-23258: Add google-mail:draft DataType transformer
    This code was created with the help of Claude code
    Fixes #CAMEL-23258
---
 .../apache/camel/catalog/transformers.properties   |   1 +
 .../catalog/transformers/google-mail-draft.json    |  14 +
 components/camel-google/camel-google-mail/pom.xml  |   5 +
 .../org/apache/camel/transformer.properties        |   2 +-
 .../org/apache/camel/transformer/google-mail-draft |   2 +
 .../camel/transformer/google-mail-draft.json       |  14 +
 .../src/main/docs/google-mail-component.adoc       | 159 ++++++++
 .../GoogleMailDraftDataTypeTransformer.java        | 111 ++++++
 .../GoogleMailDraftDataTypeTransformerTest.java    | 440 +++++++++++++++++++++
 9 files changed, 747 insertions(+), 1 deletion(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers.properties
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers.properties
index c261164480cc..601ee6a5db41 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers.properties
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers.properties
@@ -21,6 +21,7 @@ azure-storage-blob-application-cloudevents
 azure-storage-datalake-application-cloudevents
 azure-storage-queue-application-cloudevents
 google-calendar-stream-application-cloudevents
+google-mail-draft
 google-mail-stream-application-cloudevents
 google-mail-update-message-labels
 google-pubsub-application-cloudevents
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers/google-mail-draft.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers/google-mail-draft.json
new file mode 100644
index 000000000000..84793b6c4cf4
--- /dev/null
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/transformers/google-mail-draft.json
@@ -0,0 +1,14 @@
+{
+  "transformer": {
+    "kind": "transformer",
+    "name": "google-mail:draft",
+    "title": "Google Mail (Draft)",
+    "description": "Creates a Gmail Draft from String body and threading 
metadata from headers",
+    "deprecated": false,
+    "javaType": 
"org.apache.camel.component.google.mail.transform.GoogleMailDraftDataTypeTransformer",
+    "groupId": "org.apache.camel",
+    "artifactId": "camel-google-mail",
+    "version": "4.19.0-SNAPSHOT"
+  }
+}
+
diff --git a/components/camel-google/camel-google-mail/pom.xml 
b/components/camel-google/camel-google-mail/pom.xml
index 08a202cab843..794167679c6d 100644
--- a/components/camel-google/camel-google-mail/pom.xml
+++ b/components/camel-google/camel-google-mail/pom.xml
@@ -155,6 +155,11 @@
             <artifactId>commons-codec</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <scope>test</scope>
+        </dependency>
 
     </dependencies>
 
diff --git 
a/components/camel-google/camel-google-mail/src/generated/resources/META-INF/services/org/apache/camel/transformer.properties
 
b/components/camel-google/camel-google-mail/src/generated/resources/META-INF/services/org/apache/camel/transformer.properties
index 20bd343808d0..1e495cc4bf67 100644
--- 
a/components/camel-google/camel-google-mail/src/generated/resources/META-INF/services/org/apache/camel/transformer.properties
+++ 
b/components/camel-google/camel-google-mail/src/generated/resources/META-INF/services/org/apache/camel/transformer.properties
@@ -1,5 +1,5 @@
 # Generated by camel build tools - do NOT edit this file!
-transformers=google-mail-stream:application-cloudevents 
google-mail:update-message-labels
+transformers=google-mail-stream:application-cloudevents google-mail:draft 
google-mail:update-message-labels
 groupId=org.apache.camel
 artifactId=camel-google-mail
 version=4.19.0-SNAPSHOT
diff --git 
a/components/camel-google/camel-google-mail/src/generated/resources/META-INF/services/org/apache/camel/transformer/google-mail-draft
 
b/components/camel-google/camel-google-mail/src/generated/resources/META-INF/services/org/apache/camel/transformer/google-mail-draft
new file mode 100644
index 000000000000..1aae0ca7df0b
--- /dev/null
+++ 
b/components/camel-google/camel-google-mail/src/generated/resources/META-INF/services/org/apache/camel/transformer/google-mail-draft
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.component.google.mail.transform.GoogleMailDraftDataTypeTransformer
diff --git 
a/components/camel-google/camel-google-mail/src/generated/resources/META-INF/services/org/apache/camel/transformer/google-mail-draft.json
 
b/components/camel-google/camel-google-mail/src/generated/resources/META-INF/services/org/apache/camel/transformer/google-mail-draft.json
new file mode 100644
index 000000000000..84793b6c4cf4
--- /dev/null
+++ 
b/components/camel-google/camel-google-mail/src/generated/resources/META-INF/services/org/apache/camel/transformer/google-mail-draft.json
@@ -0,0 +1,14 @@
+{
+  "transformer": {
+    "kind": "transformer",
+    "name": "google-mail:draft",
+    "title": "Google Mail (Draft)",
+    "description": "Creates a Gmail Draft from String body and threading 
metadata from headers",
+    "deprecated": false,
+    "javaType": 
"org.apache.camel.component.google.mail.transform.GoogleMailDraftDataTypeTransformer",
+    "groupId": "org.apache.camel",
+    "artifactId": "camel-google-mail",
+    "version": "4.19.0-SNAPSHOT"
+  }
+}
+
diff --git 
a/components/camel-google/camel-google-mail/src/main/docs/google-mail-component.adoc
 
b/components/camel-google/camel-google-mail/src/main/docs/google-mail-component.adoc
index 69381e4843e7..ed782de1e1b6 100644
--- 
a/components/camel-google/camel-google-mail/src/main/docs/google-mail-component.adoc
+++ 
b/components/camel-google/camel-google-mail/src/main/docs/google-mail-component.adoc
@@ -140,6 +140,165 @@ XML::
 </route>
 ----
 ====
+== Data Type Transformer: draft
+
+The `google-mail:draft` transformer converts a String body into a Gmail 
`Draft` using mail headers from the exchange.
+
+It works with `google-mail-stream`, which sets headers like `threadId`, 
`messageId`, `from`, `to`, `cc`, `bcc`, and `subject` automatically when 
consuming messages.
+You can also set these headers manually to create drafts from scratch.
+
+=== Headers
+
+The transformer reads the following headers:
+
+[cols="1,1,3"]
+|===
+| Header | Type | Description
+
+| `CamelGoogleMailStreamThreadId`
+| `String`
+| Thread ID for the conversation (optional, used for replies)
+
+| `CamelGoogleMailStreamMessageId`
+| `String`
+| Message ID to reply to (optional, sets In-Reply-To and References headers)
+
+| `CamelGoogleMailStreamFrom`
+| `String`
+| From address (optional)
+
+| `CamelGoogleMailStreamTo`
+| `String`
+| To address (optional)
+
+| `CamelGoogleMailStreamCc`
+| `String`
+| CC address (optional)
+
+| `CamelGoogleMailStreamBcc`
+| `String`
+| BCC address (optional)
+
+| `CamelGoogleMailStreamSubject`
+| `String`
+| Email subject (optional)
+|===
+
+All headers are optional.
+
+=== Message Body
+
+The message body is a `String` with the email content (plain text or HTML).
+
+Content type defaults to `text/plain; charset=UTF-8`. For HTML, set 
`Exchange.CONTENT_TYPE` to `text/html; charset=UTF-8`.
+
+The transformer:
+
+* Builds an RFC 2822 MIME message and encodes it as Base64 URL-safe (required 
by Gmail API)
+* Sets `In-Reply-To` and `References` headers when `messageId` is present
+* Associates the draft with the correct thread when `threadId` is present
+
+=== Examples
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+// Create a draft reply from a received message
+from("google-mail-stream:0")
+    .setBody(constant("Thank you for your message. I will respond soon."))
+    .transformDataType(new DataType("google-mail:draft"))
+    .to("google-mail:drafts/create?inBody=content");
+
+// Create a new draft (no threading)
+from("direct:newDraft")
+    .setBody(constant("This is a new draft message"))
+    .setHeader("CamelGoogleMailStreamFrom", constant("[email protected]"))
+    .setHeader("CamelGoogleMailStreamTo", constant("[email protected]"))
+    .setHeader("CamelGoogleMailStreamSubject", constant("New Draft"))
+    .transformDataType(new DataType("google-mail:draft"))
+    .to("google-mail:drafts/create?inBody=content");
+----
+
+YAML::
++
+[source,yaml]
+----
+# Create a draft reply from a received message
+- route:
+    from:
+      uri: "google-mail-stream:0"
+    steps:
+      - setBody:
+          constant: "Thank you for your message. I will respond soon."
+      - transformDataType:
+          toType: "google-mail:draft"
+      - to:
+          uri: "google-mail:drafts/create"
+          parameters:
+            inBody: content
+
+# Create a new draft (no threading)
+- route:
+    from:
+      uri: "direct:newDraft"
+    steps:
+      - setBody:
+          constant: "This is a new draft message"
+      - setHeader:
+          name: CamelGoogleMailStreamFrom
+          constant: "[email protected]"
+      - setHeader:
+          name: CamelGoogleMailStreamTo
+          constant: "[email protected]"
+      - setHeader:
+          name: CamelGoogleMailStreamSubject
+          constant: "New Draft"
+      - transformDataType:
+          toType: "google-mail:draft"
+      - to:
+          uri: "google-mail:drafts/create"
+          parameters:
+            inBody: content
+----
+
+XML::
++
+[source,xml]
+----
+<!-- Create a draft reply from a received message -->
+<route>
+    <from uri="google-mail-stream:0"/>
+    <setBody>
+        <constant>Thank you for your message. I will respond soon.</constant>
+    </setBody>
+    <transformDataType toType="google-mail:draft"/>
+    <to uri="google-mail:drafts/create?inBody=content"/>
+</route>
+
+<!-- Create a new draft (no threading) -->
+<route>
+    <from uri="direct:newDraft"/>
+    <setBody>
+        <constant>This is a new draft message</constant>
+    </setBody>
+    <setHeader name="CamelGoogleMailStreamFrom">
+        <constant>[email protected]</constant>
+    </setHeader>
+    <setHeader name="CamelGoogleMailStreamTo">
+        <constant>[email protected]</constant>
+    </setHeader>
+    <setHeader name="CamelGoogleMailStreamSubject">
+        <constant>New Draft</constant>
+    </setHeader>
+    <transformDataType toType="google-mail:draft"/>
+    <to uri="google-mail:drafts/create?inBody=content"/>
+</route>
+----
+====
+
 
 == More Information
 
diff --git 
a/components/camel-google/camel-google-mail/src/main/java/org/apache/camel/component/google/mail/transform/GoogleMailDraftDataTypeTransformer.java
 
b/components/camel-google/camel-google-mail/src/main/java/org/apache/camel/component/google/mail/transform/GoogleMailDraftDataTypeTransformer.java
new file mode 100644
index 000000000000..9a57de4807b2
--- /dev/null
+++ 
b/components/camel-google/camel-google-mail/src/main/java/org/apache/camel/component/google/mail/transform/GoogleMailDraftDataTypeTransformer.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.google.mail.transform;
+
+import java.io.ByteArrayOutputStream;
+import java.util.Properties;
+
+import jakarta.mail.Session;
+import jakarta.mail.internet.InternetAddress;
+import jakarta.mail.internet.MimeMessage;
+
+import com.google.api.client.util.Base64;
+import com.google.api.services.gmail.model.Draft;
+import com.google.api.services.gmail.model.Message;
+import org.apache.camel.Exchange;
+import org.apache.camel.component.google.mail.stream.GoogleMailStreamConstants;
+import org.apache.camel.spi.DataType;
+import org.apache.camel.spi.DataTypeTransformer;
+import org.apache.camel.spi.Transformer;
+import org.apache.camel.util.ObjectHelper;
+
+import static jakarta.mail.Message.RecipientType.BCC;
+import static jakarta.mail.Message.RecipientType.CC;
+import static jakarta.mail.Message.RecipientType.TO;
+
+/**
+ * Data type transformer that builds a {@link Draft} from a String body and 
mail headers populated by
+ * google-mail-stream.
+ */
+@DataTypeTransformer(name = "google-mail:draft",
+                     description = "Creates a Gmail Draft from String body and 
threading metadata from headers")
+public class GoogleMailDraftDataTypeTransformer extends Transformer {
+
+    @Override
+    public void transform(org.apache.camel.Message message, DataType fromType, 
DataType toType) throws Exception {
+        String body = message.getBody(String.class);
+        if (ObjectHelper.isEmpty(body)) {
+            throw new IllegalArgumentException("Draft body must not be null or 
empty");
+        }
+
+        String threadId = 
message.getHeader(GoogleMailStreamConstants.MAIL_THREAD_ID, String.class);
+        String messageId = 
message.getHeader(GoogleMailStreamConstants.MAIL_MESSAGE_ID, String.class);
+        String from = message.getHeader(GoogleMailStreamConstants.MAIL_FROM, 
String.class);
+        String subject = 
message.getHeader(GoogleMailStreamConstants.MAIL_SUBJECT, String.class);
+        String to = message.getHeader(GoogleMailStreamConstants.MAIL_TO, 
String.class);
+        String cc = message.getHeader(GoogleMailStreamConstants.MAIL_CC, 
String.class);
+        String bcc = message.getHeader(GoogleMailStreamConstants.MAIL_BCC, 
String.class);
+
+        Session session = Session.getDefaultInstance(new Properties());
+        MimeMessage mimeMessage = new MimeMessage(session);
+
+        if (ObjectHelper.isNotEmpty(from)) {
+            mimeMessage.setFrom(new InternetAddress(from, true));
+        }
+        if (ObjectHelper.isNotEmpty(to)) {
+            mimeMessage.addRecipients(TO, InternetAddress.parse(to, true));
+        }
+        if (ObjectHelper.isNotEmpty(cc)) {
+            mimeMessage.addRecipients(CC, InternetAddress.parse(cc, true));
+        }
+        if (ObjectHelper.isNotEmpty(bcc)) {
+            mimeMessage.addRecipients(BCC, InternetAddress.parse(bcc, true));
+        }
+        if (ObjectHelper.isNotEmpty(subject)) {
+            mimeMessage.setSubject(subject, "UTF-8");
+        }
+
+        if (ObjectHelper.isNotEmpty(messageId)) {
+            mimeMessage.setHeader("In-Reply-To", messageId);
+            mimeMessage.setHeader("References", messageId);
+        }
+
+        String contentType = message.getHeader(Exchange.CONTENT_TYPE, 
String.class);
+        if (contentType == null) {
+            contentType = "text/plain; charset=UTF-8";
+        }
+        mimeMessage.setContent(body, contentType);
+
+        String encodedEmail;
+        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+            mimeMessage.writeTo(baos);
+            encodedEmail = 
Base64.encodeBase64URLSafeString(baos.toByteArray());
+        }
+
+        Message gmailMessage = new Message();
+        gmailMessage.setRaw(encodedEmail);
+
+        if (ObjectHelper.isNotEmpty(threadId)) {
+            gmailMessage.setThreadId(threadId);
+        }
+
+        Draft draft = new Draft();
+        draft.setMessage(gmailMessage);
+
+        message.setBody(draft);
+    }
+}
diff --git 
a/components/camel-google/camel-google-mail/src/test/java/org/apache/camel/component/google/mail/transform/GoogleMailDraftDataTypeTransformerTest.java
 
b/components/camel-google/camel-google-mail/src/test/java/org/apache/camel/component/google/mail/transform/GoogleMailDraftDataTypeTransformerTest.java
new file mode 100644
index 000000000000..60b3a94b0d0b
--- /dev/null
+++ 
b/components/camel-google/camel-google-mail/src/test/java/org/apache/camel/component/google/mail/transform/GoogleMailDraftDataTypeTransformerTest.java
@@ -0,0 +1,440 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.google.mail.transform;
+
+import java.nio.charset.StandardCharsets;
+
+import jakarta.mail.internet.MimeUtility;
+
+import com.google.api.client.util.Base64;
+import com.google.api.services.gmail.model.Draft;
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.spi.DataType;
+import org.apache.camel.support.DefaultExchange;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static 
org.apache.camel.component.google.mail.stream.GoogleMailStreamConstants.MAIL_BCC;
+import static 
org.apache.camel.component.google.mail.stream.GoogleMailStreamConstants.MAIL_CC;
+import static 
org.apache.camel.component.google.mail.stream.GoogleMailStreamConstants.MAIL_FROM;
+import static 
org.apache.camel.component.google.mail.stream.GoogleMailStreamConstants.MAIL_MESSAGE_ID;
+import static 
org.apache.camel.component.google.mail.stream.GoogleMailStreamConstants.MAIL_SUBJECT;
+import static 
org.apache.camel.component.google.mail.stream.GoogleMailStreamConstants.MAIL_THREAD_ID;
+import static 
org.apache.camel.component.google.mail.stream.GoogleMailStreamConstants.MAIL_TO;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class GoogleMailDraftDataTypeTransformerTest extends CamelTestSupport {
+
+    private GoogleMailDraftDataTypeTransformer transformer;
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        return new DefaultCamelContext();
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:input")
+                        .transformDataType("google-mail:draft")
+                        .to("mock:result");
+            }
+        };
+    }
+
+    @Override
+    protected void doPostSetup() {
+        transformer = new GoogleMailDraftDataTypeTransformer();
+        transformer.setCamelContext(context);
+    }
+
+    @Test
+    void testBasicDraftCreation() throws Exception {
+        Exchange exchange = new DefaultExchange(context);
+        exchange.getMessage().setBody("This is a test draft message");
+
+        transformer.transform(exchange.getMessage(), DataType.ANY, 
DataType.ANY);
+
+        Draft draft = exchange.getMessage().getBody(Draft.class);
+        assertThat(draft).isNotNull();
+        assertThat(draft.getMessage()).isNotNull();
+        assertThat(draft.getMessage().getRaw()).isNotNull();
+
+        String decodedMessage = decodeMessage(draft.getMessage().getRaw());
+        assertThat(decodedMessage)
+                .contains("This is a test draft message")
+                .contains("Content-Type: text/plain; charset=UTF-8");
+    }
+
+    @Test
+    void testDraftWithThreadingMetadata() throws Exception {
+        Exchange exchange = new DefaultExchange(context);
+        exchange.getMessage().setBody("Reply message");
+        exchange.getMessage().setHeader(MAIL_THREAD_ID, "thread-123");
+        exchange.getMessage().setHeader(MAIL_MESSAGE_ID, 
"<[email protected]>");
+        exchange.getMessage().setHeader(MAIL_FROM, "[email protected]");
+        exchange.getMessage().setHeader(MAIL_SUBJECT, "Re: Test Subject");
+
+        transformer.transform(exchange.getMessage(), DataType.ANY, 
DataType.ANY);
+
+        Draft draft = exchange.getMessage().getBody(Draft.class);
+        assertThat(draft).isNotNull();
+        assertThat(draft.getMessage()).isNotNull();
+        assertThat(draft.getMessage().getThreadId()).isEqualTo("thread-123");
+
+        String decodedMessage = decodeMessage(draft.getMessage().getRaw());
+        assertThat(decodedMessage)
+                .contains("From: [email protected]")
+                .contains("Subject: Re: Test Subject")
+                .contains("In-Reply-To: <[email protected]>")
+                .contains("References: <[email protected]>")
+                .contains("Reply message");
+    }
+
+    @Test
+    void testDraftWithAllHeaders() throws Exception {
+        Exchange exchange = new DefaultExchange(context);
+        exchange.getMessage().setBody("Complete message");
+        exchange.getMessage().setHeader(MAIL_FROM, "[email protected]");
+        exchange.getMessage().setHeader(MAIL_TO, "[email protected]");
+        exchange.getMessage().setHeader(MAIL_CC, "[email protected]");
+        exchange.getMessage().setHeader(MAIL_BCC, "[email protected]");
+        exchange.getMessage().setHeader(MAIL_SUBJECT, "Complete Test");
+
+        transformer.transform(exchange.getMessage(), DataType.ANY, 
DataType.ANY);
+
+        Draft draft = exchange.getMessage().getBody(Draft.class);
+        assertThat(draft).isNotNull();
+
+        String decodedMessage = decodeMessage(draft.getMessage().getRaw());
+        assertThat(decodedMessage)
+                .contains("From: [email protected]")
+                .contains("To: [email protected]")
+                .contains("Cc: [email protected]")
+                .contains("Bcc: [email protected]")
+                .contains("Subject: Complete Test")
+                .contains("Complete message");
+    }
+
+    @Test
+    void testDraftWithEmptyBody() {
+        Exchange exchange = new DefaultExchange(context);
+        exchange.getMessage().setBody("");
+        exchange.getMessage().setHeader(MAIL_SUBJECT, "Empty Body Test");
+
+        assertThatThrownBy(() -> transformer.transform(exchange.getMessage(), 
DataType.ANY, DataType.ANY))
+                .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    void testDraftWithNullBody() {
+        Exchange exchange = new DefaultExchange(context);
+        exchange.getMessage().setBody(null);
+        exchange.getMessage().setHeader(MAIL_SUBJECT, "Null Body Test");
+
+        assertThatThrownBy(() -> transformer.transform(exchange.getMessage(), 
DataType.ANY, DataType.ANY))
+                .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    void testDraftWithoutThreadId() throws Exception {
+        Exchange exchange = new DefaultExchange(context);
+        exchange.getMessage().setBody("New conversation");
+        exchange.getMessage().setHeader(MAIL_SUBJECT, "New Thread");
+
+        transformer.transform(exchange.getMessage(), DataType.ANY, 
DataType.ANY);
+
+        Draft draft = exchange.getMessage().getBody(Draft.class);
+        assertThat(draft).isNotNull();
+        assertThat(draft.getMessage().getThreadId()).isNull();
+
+        String decodedMessage = decodeMessage(draft.getMessage().getRaw());
+        assertThat(decodedMessage)
+                .contains("Subject: New Thread")
+                .contains("New conversation");
+    }
+
+    @Test
+    void testDraftWithHtmlBodyRequiresExplicitContentType() throws Exception {
+        Exchange exchange = new DefaultExchange(context);
+        String htmlBody = "<html><body><h1>Hello</h1><p>This is 
HTML</p></body></html>";
+        exchange.getMessage().setBody(htmlBody);
+        exchange.getMessage().setHeader(MAIL_SUBJECT, "HTML Test");
+
+        transformer.transform(exchange.getMessage(), DataType.ANY, 
DataType.ANY);
+
+        Draft draft = exchange.getMessage().getBody(Draft.class);
+        assertThat(draft).isNotNull();
+
+        String decodedMessage = decodeMessage(draft.getMessage().getRaw());
+        assertThat(decodedMessage)
+                .as("Without explicit Content-Type header, should default to 
text/plain even for HTML-like body")
+                .contains("text/plain");
+    }
+
+    @Test
+    void testDraftWithHtmlBodyAndExplicitHtmlContentType() throws Exception {
+        Exchange exchange = new DefaultExchange(context);
+        String htmlBody = "<html><body><h1>Hello</h1><p>This is 
HTML</p></body></html>";
+        exchange.getMessage().setBody(htmlBody);
+        exchange.getMessage().setHeader(Exchange.CONTENT_TYPE, "text/html; 
charset=UTF-8");
+        exchange.getMessage().setHeader(MAIL_SUBJECT, "HTML Test");
+
+        transformer.transform(exchange.getMessage(), DataType.ANY, 
DataType.ANY);
+
+        Draft draft = exchange.getMessage().getBody(Draft.class);
+        assertThat(draft).isNotNull();
+
+        String decodedMessage = decodeMessage(draft.getMessage().getRaw());
+        assertThat(decodedMessage)
+                .contains("text/html")
+                .contains(htmlBody);
+    }
+
+    @Test
+    void testDraftWithExplicitContentTypeHeader() throws Exception {
+        Exchange exchange = new DefaultExchange(context);
+        exchange.getMessage().setBody("Some plain text");
+        exchange.getMessage().setHeader(Exchange.CONTENT_TYPE, "text/html");
+        exchange.getMessage().setHeader(MAIL_SUBJECT, "Explicit CT");
+
+        transformer.transform(exchange.getMessage(), DataType.ANY, 
DataType.ANY);
+
+        Draft draft = exchange.getMessage().getBody(Draft.class);
+        assertThat(draft).isNotNull();
+
+        String decodedMessage = decodeMessage(draft.getMessage().getRaw());
+        assertThat(decodedMessage)
+                .as("MimeMessage should set text/html and determine charset 
automatically")
+                .contains("Content-Type: text/html");
+    }
+
+    @Test
+    void testDraftWithPlainTextDefaultContentType() throws Exception {
+        Exchange exchange = new DefaultExchange(context);
+        exchange.getMessage().setBody("Just plain text, no HTML");
+        exchange.getMessage().setHeader(MAIL_SUBJECT, "Plain Test");
+
+        transformer.transform(exchange.getMessage(), DataType.ANY, 
DataType.ANY);
+
+        Draft draft = exchange.getMessage().getBody(Draft.class);
+        assertThat(draft).isNotNull();
+
+        String decodedMessage = decodeMessage(draft.getMessage().getRaw());
+        assertThat(decodedMessage).contains("Content-Type: text/plain; 
charset=UTF-8");
+    }
+
+    @Test
+    void testDraftWithNonAsciiSubject() throws Exception {
+        Exchange exchange = new DefaultExchange(context);
+        exchange.getMessage().setBody("Message with accented subject");
+        exchange.getMessage().setHeader(MAIL_SUBJECT, "Resumé for café 
meeting");
+
+        transformer.transform(exchange.getMessage(), DataType.ANY, 
DataType.ANY);
+
+        Draft draft = exchange.getMessage().getBody(Draft.class);
+        assertThat(draft).isNotNull();
+
+        String decodedMessage = decodeMessage(draft.getMessage().getRaw());
+        assertThat(decodedMessage)
+                .contains("Subject: =?UTF-8?")
+                .contains("?=")
+                .contains("Message with accented subject");
+        String decodedSubject = MimeUtility.decodeText(
+                decodedMessage.lines().filter(l -> 
l.startsWith("Subject:")).findFirst().orElse(""));
+        assertThat(decodedSubject)
+                .as("Non-ASCII characters should be preserved after decoding")
+                .contains("Resumé for café meeting");
+    }
+
+    @Test
+    void testDraftWithNonAsciiFrom() throws Exception {
+        Exchange exchange = new DefaultExchange(context);
+        exchange.getMessage().setBody("Test message");
+        exchange.getMessage().setHeader(MAIL_FROM, "Renée Müller 
<[email protected]>");
+
+        transformer.transform(exchange.getMessage(), DataType.ANY, 
DataType.ANY);
+
+        Draft draft = exchange.getMessage().getBody(Draft.class);
+        assertThat(draft).isNotNull();
+
+        String decodedMessage = decodeMessage(draft.getMessage().getRaw());
+        assertThat(decodedMessage)
+                .as("MimeMessage should handle non-ASCII in From display name 
and preserve the address")
+                .contains("From:")
+                .contains("[email protected]");
+    }
+
+    @Test
+    void testDraftWithAsciiHeadersNotEncoded() throws Exception {
+        Exchange exchange = new DefaultExchange(context);
+        exchange.getMessage().setBody("Test message");
+        exchange.getMessage().setHeader(MAIL_SUBJECT, "Plain ASCII Subject");
+
+        transformer.transform(exchange.getMessage(), DataType.ANY, 
DataType.ANY);
+
+        Draft draft = exchange.getMessage().getBody(Draft.class);
+        String decodedMessage = decodeMessage(draft.getMessage().getRaw());
+        assertThat(decodedMessage).contains("Subject: Plain ASCII Subject");
+    }
+
+    @Test
+    void testDraftWithExplicitContentTypeIncludingCharset() throws Exception {
+        Exchange exchange = new DefaultExchange(context);
+        exchange.getMessage().setBody("Some text");
+        exchange.getMessage().setHeader(Exchange.CONTENT_TYPE, "text/html; 
charset=ISO-8859-1");
+
+        transformer.transform(exchange.getMessage(), DataType.ANY, 
DataType.ANY);
+
+        Draft draft = exchange.getMessage().getBody(Draft.class);
+        String decodedMessage = decodeMessage(draft.getMessage().getRaw());
+        assertThat(decodedMessage)
+                .contains("Content-Type: text/html; charset=ISO-8859-1")
+                .doesNotContain("charset=UTF-8");
+    }
+
+    // E2E tests via Camel route
+
+    @Test
+    void testDraftCreationViaRoute() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:result");
+        mock.reset();
+        mock.expectedMessageCount(1);
+
+        template.send("direct:input", exchange -> {
+            exchange.getMessage().setBody("Route test message");
+            exchange.getMessage().setHeader(MAIL_FROM, "[email protected]");
+            exchange.getMessage().setHeader(MAIL_SUBJECT, "Route Test");
+        });
+
+        mock.assertIsSatisfied();
+
+        Exchange received = mock.getExchanges().get(0);
+        Draft draft = received.getMessage().getBody(Draft.class);
+        assertThat(draft).isNotNull();
+        assertThat(draft.getMessage()).isNotNull();
+
+        String decodedMessage = decodeMessage(draft.getMessage().getRaw());
+        assertThat(decodedMessage)
+                .contains("From: [email protected]")
+                .contains("Subject: Route Test")
+                .contains("Route test message");
+    }
+
+    @Test
+    void testReplyDraftViaRoute() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:result");
+        mock.reset();
+        mock.expectedMessageCount(1);
+
+        template.send("direct:input", exchange -> {
+            exchange.getMessage().setBody("This is a reply");
+            exchange.getMessage().setHeader(MAIL_THREAD_ID, 
"thread-reply-123");
+            exchange.getMessage().setHeader(MAIL_MESSAGE_ID, 
"<[email protected]>");
+            exchange.getMessage().setHeader(MAIL_FROM, "[email protected]");
+            exchange.getMessage().setHeader(MAIL_SUBJECT, "Re: Original 
Subject");
+        });
+
+        mock.assertIsSatisfied();
+
+        Exchange received = mock.getExchanges().get(0);
+        Draft draft = received.getMessage().getBody(Draft.class);
+        assertThat(draft).isNotNull();
+        
assertThat(draft.getMessage().getThreadId()).isEqualTo("thread-reply-123");
+
+        String decodedMessage = decodeMessage(draft.getMessage().getRaw());
+        assertThat(decodedMessage)
+                .contains("In-Reply-To: <[email protected]>")
+                .contains("References: <[email protected]>")
+                .contains("This is a reply");
+    }
+
+    @Test
+    void testMultilineBodyViaRoute() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:result");
+        mock.reset();
+        mock.expectedMessageCount(1);
+
+        String multilineBody = "Line 1\nLine 2\nLine 3\n\nLine 5";
+        template.send("direct:input", exchange -> {
+            exchange.getMessage().setBody(multilineBody);
+            exchange.getMessage().setHeader(MAIL_SUBJECT, "Multiline Test");
+        });
+
+        mock.assertIsSatisfied();
+
+        Exchange received = mock.getExchanges().get(0);
+        Draft draft = received.getMessage().getBody(Draft.class);
+        assertThat(draft).isNotNull();
+
+        String decodedMessage = decodeMessage(draft.getMessage().getRaw());
+        assertThat(decodedMessage)
+                .contains("Line 1")
+                .contains("Line 2")
+                .contains("Line 3")
+                .contains("Line 5");
+    }
+
+    @Test
+    void testDraftWithMalformedEmailAddress() {
+        Exchange exchange = new DefaultExchange(context);
+        exchange.getMessage().setBody("Test message");
+        exchange.getMessage().setHeader(MAIL_TO, "not a valid@ address");
+        exchange.getMessage().setHeader(MAIL_SUBJECT, "Malformed Test");
+
+        assertThatThrownBy(() -> transformer.transform(exchange.getMessage(), 
DataType.ANY, DataType.ANY))
+                .isInstanceOf(Exception.class);
+    }
+
+    @Test
+    void testDraftWithMultipleRecipients() throws Exception {
+        Exchange exchange = new DefaultExchange(context);
+        exchange.getMessage().setBody("Message to multiple recipients");
+        exchange.getMessage().setHeader(MAIL_TO, "[email protected], 
[email protected]");
+        exchange.getMessage().setHeader(MAIL_CC, "[email protected], 
[email protected]");
+        exchange.getMessage().setHeader(MAIL_BCC, "[email protected]");
+        exchange.getMessage().setHeader(MAIL_SUBJECT, "Multiple Recipients 
Test");
+
+        transformer.transform(exchange.getMessage(), DataType.ANY, 
DataType.ANY);
+
+        Draft draft = exchange.getMessage().getBody(Draft.class);
+        assertThat(draft).isNotNull();
+
+        String decodedMessage = decodeMessage(draft.getMessage().getRaw());
+        assertThat(decodedMessage)
+                .contains("To: [email protected]")
+                .contains("[email protected]")
+                .contains("Cc: [email protected]")
+                .contains("[email protected]")
+                .contains("Bcc: [email protected]")
+                .contains("Subject: Multiple Recipients Test")
+                .contains("Message to multiple recipients");
+    }
+
+    // Helper method to decode Base64 message
+    private String decodeMessage(String encodedMessage) {
+        byte[] decodedBytes = Base64.decodeBase64(encodedMessage);
+        return new String(decodedBytes, StandardCharsets.UTF_8);
+    }
+}


Reply via email to