Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## [Unreleased]

### Added
* `is_plaintext` support in `SendMessageRequest` and `CreateDraftRequest` to control plain text vs HTML message formatting

### Changed
* `SendMessageRequest.sendAt` field changed from `Int?` to `Long?` to support Unix timestamps beyond 2038. Maintains backward compatibility through method overloading - existing `Int` parameters are automatically converted to `Long`. **Note:** Kotlin users passing integer literals may need to specify type explicitly (e.g., `1620000000L` or `1620000000 as Int`).

Expand Down
61 changes: 61 additions & 0 deletions examples/src/main/java/com/nylas/examples/MessagesExample.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.nylas.examples;

import com.nylas.NylasClient;
import com.nylas.models.EmailName;
import com.nylas.models.FindMessageQueryParams;
import com.nylas.models.ListMessagesQueryParams;
import com.nylas.models.ListResponse;
Expand All @@ -9,6 +10,7 @@
import com.nylas.models.NylasApiError;
import com.nylas.models.NylasSdkTimeoutError;
import com.nylas.models.Response;
import com.nylas.models.SendMessageRequest;
import com.nylas.models.TrackingOptions;
import okhttp3.OkHttpClient;

Expand Down Expand Up @@ -134,6 +136,9 @@ private static void runMessagesExample(NylasClient nylas, Map<String, String> co

// 5. Find a specific message with different field options
demonstrateMessageFinding(nylas, grantId);

// 6. Demonstrate is_plaintext feature for sending messages
demonstratePlaintextFeature(nylas, grantId, config);
}

private static void demonstrateStandardMessageListing(NylasClient nylas, String grantId) throws NylasApiError, NylasSdkTimeoutError {
Expand Down Expand Up @@ -301,4 +306,60 @@ private static void printMessageDetails(Message message, String requestType) {
System.out.println(" Raw MIME length: " + message.getRawMime().length() + " characters");
}
}

private static void demonstratePlaintextFeature(NylasClient nylas, String grantId, Map<String, String> config) throws NylasApiError, NylasSdkTimeoutError {
System.out.println("📝 6. Demonstrating is_plaintext feature for sending messages (NEW FEATURE):");

String recipientEmail = config.get("NYLAS_RECIPIENT_EMAIL");
if (recipientEmail == null) {
System.out.println(" ⚠️ Skipping send examples - NYLAS_RECIPIENT_EMAIL not configured");
System.out.println(" To enable this demo, set NYLAS_RECIPIENT_EMAIL in your .env file");
return;
}

System.out.println(" Sending test messages to: " + recipientEmail);

// 1. Send HTML message (default behavior)
System.out.println("\n 📧 Sending HTML message (is_plaintext = false or not specified):");

SendMessageRequest htmlRequest = new SendMessageRequest.Builder(
Arrays.asList(new EmailName(recipientEmail, "Test Recipient"))
)
.subject("HTML Message Test - Nylas SDK")
.body("<html><body><h1>Hello from Nylas!</h1><p>This is an <b>HTML</b> message with <i>formatting</i>.</p></body></html>")
.isPlaintext(false) // Explicitly set to false (this is also the default)
.build();

try {
Response<Message> htmlResponse = nylas.messages().send(grantId, htmlRequest);
System.out.println(" ✅ HTML message sent successfully");
System.out.println(" Message ID: " + htmlResponse.getData().getId());
} catch (Exception e) {
System.out.println(" ❌ Failed to send HTML message: " + e.getMessage());
}

// 2. Send plain text message using is_plaintext feature
System.out.println("\n 📄 Sending plain text message (is_plaintext = true):");

SendMessageRequest plaintextRequest = new SendMessageRequest.Builder(
Arrays.asList(new EmailName(recipientEmail, "Test Recipient"))
)
.subject("Plain Text Message Test - Nylas SDK")
.body("Hello from Nylas!\n\nThis is a PLAIN TEXT message.\nNo HTML formatting will be applied.\n\nBest regards,\nNylas SDK")
.isPlaintext(true) // NEW FEATURE: Force plain text mode
.build();

try {
Response<Message> plaintextResponse = nylas.messages().send(grantId, plaintextRequest);
System.out.println(" ✅ Plain text message sent successfully");
System.out.println(" Message ID: " + plaintextResponse.getData().getId());
} catch (Exception e) {
System.out.println(" ❌ Failed to send plain text message: " + e.getMessage());
}

System.out.println("\n 💡 Key differences:");
System.out.println(" - HTML message (is_plaintext=false): Message body is sent as HTML with MIME formatting");
System.out.println(" - Plain text message (is_plaintext=true): Message body is sent as plain text, no HTML in MIME data");
System.out.println(" - Default behavior: is_plaintext=false (HTML formatting)");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ private fun runMessagesExample(nylas: NylasClient, config: Map<String, String>)

// 5. Find a specific message with different field options
demonstrateMessageFinding(nylas, grantId)

// 6. Demonstrate is_plaintext feature for sending messages
demonstratePlaintextFeature(nylas, grantId, config)
}

private fun demonstrateStandardMessageListing(nylas: NylasClient, grantId: String) {
Expand Down Expand Up @@ -277,4 +280,60 @@ private fun printMessageDetails(message: Message, requestType: String) {
message.rawMime?.let { rawMime ->
println(" Raw MIME length: ${rawMime.length} characters")
}
}

private fun demonstratePlaintextFeature(nylas: NylasClient, grantId: String, config: Map<String, String>) {
println("📝 6. Demonstrating is_plaintext feature for sending messages (NEW FEATURE):")

val recipientEmail = config["NYLAS_RECIPIENT_EMAIL"]
if (recipientEmail == null) {
println(" ⚠️ Skipping send examples - NYLAS_RECIPIENT_EMAIL not configured")
println(" To enable this demo, set NYLAS_RECIPIENT_EMAIL in your .env file")
return
}

println(" Sending test messages to: $recipientEmail")

// 1. Send HTML message (default behavior)
println("\n 📧 Sending HTML message (is_plaintext = false or not specified):")

val htmlRequest = SendMessageRequest.Builder(
listOf(EmailName(recipientEmail, "Test Recipient"))
)
.subject("HTML Message Test - Nylas SDK")
.body("<html><body><h1>Hello from Nylas!</h1><p>This is an <b>HTML</b> message with <i>formatting</i>.</p></body></html>")
.isPlaintext(false) // Explicitly set to false (this is also the default)
.build()

try {
val htmlResponse = nylas.messages().send(grantId, htmlRequest)
println(" ✅ HTML message sent successfully")
println(" Message ID: ${htmlResponse.data.id}")
} catch (e: Exception) {
println(" ❌ Failed to send HTML message: ${e.message}")
}

// 2. Send plain text message using is_plaintext feature
println("\n 📄 Sending plain text message (is_plaintext = true):")

val plaintextRequest = SendMessageRequest.Builder(
listOf(EmailName(recipientEmail, "Test Recipient"))
)
.subject("Plain Text Message Test - Nylas SDK")
.body("Hello from Nylas!\n\nThis is a PLAIN TEXT message.\nNo HTML formatting will be applied.\n\nBest regards,\nNylas SDK")
.isPlaintext(true) // NEW FEATURE: Force plain text mode
.build()

try {
val plaintextResponse = nylas.messages().send(grantId, plaintextRequest)
println(" ✅ Plain text message sent successfully")
println(" Message ID: ${plaintextResponse.data.id}")
} catch (e: Exception) {
println(" ❌ Failed to send plain text message: ${e.message}")
}

println("\n 💡 Key differences:")
println(" - HTML message (is_plaintext=false): Message body is sent as HTML with MIME formatting")
println(" - Plain text message (is_plaintext=true): Message body is sent as plain text, no HTML in MIME data")
println(" - Default behavior: is_plaintext=false (HTML formatting)")
}
21 changes: 19 additions & 2 deletions src/main/kotlin/com/nylas/models/CreateDraftRequest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ data class CreateDraftRequest(
*/
@Json(name = "custom_headers")
val customHeaders: List<CustomHeader>? = null,
/**
* When true, the message body is sent as plain text and the MIME data doesn't include the HTML version of the message.
* When false, the message body is sent as HTML.
*/
@Json(name = "is_plaintext")
val isPlaintext: Boolean? = null,
) : IMessageAttachmentRequest {
/**
* Builder for [CreateDraftRequest].
Expand All @@ -90,6 +96,7 @@ data class CreateDraftRequest(
private var replyToMessageId: String? = null
private var trackingOptions: TrackingOptions? = null
private var customHeaders: List<CustomHeader>? = null
private var isPlaintext: Boolean? = null

/**
* Sets the from address.
Expand Down Expand Up @@ -184,8 +191,17 @@ data class CreateDraftRequest(
fun customHeaders(customHeaders: List<CustomHeader>?) = apply { this.customHeaders = customHeaders }

/**
* Builds a [SendMessageRequest] instance.
* @return The [SendMessageRequest] instance.
* Sets whether the message body is sent as plain text.
* When true, the message body is sent as plain text and the MIME data doesn't include the HTML version of the message.
* When false, the message body is sent as HTML.
* @param isPlaintext Whether the message body is sent as plain text.
* @return The builder.
*/
fun isPlaintext(isPlaintext: Boolean?) = apply { this.isPlaintext = isPlaintext }

/**
* Builds a [CreateDraftRequest] instance.
* @return The [CreateDraftRequest] instance.
*/
fun build() =
CreateDraftRequest(
Expand All @@ -202,6 +218,7 @@ data class CreateDraftRequest(
replyToMessageId,
trackingOptions,
customHeaders,
isPlaintext,
)
}
}
17 changes: 17 additions & 0 deletions src/main/kotlin/com/nylas/models/SendMessageRequest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ data class SendMessageRequest(
*/
@Json(name = "custom_headers")
val customHeaders: List<CustomHeader>? = null,
/**
* When true, the message body is sent as plain text and the MIME data doesn't include the HTML version of the message.
* When false, the message body is sent as HTML.
*/
@Json(name = "is_plaintext")
val isPlaintext: Boolean? = null,
) : IMessageAttachmentRequest {
/**
* Builder for [SendMessageRequest].
Expand All @@ -99,6 +105,7 @@ data class SendMessageRequest(
private var trackingOptions: TrackingOptions? = null
private var useDraft: Boolean? = null
private var customHeaders: List<CustomHeader>? = null
private var isPlaintext: Boolean? = null

/**
* Sets the bcc recipients.
Expand Down Expand Up @@ -200,6 +207,15 @@ data class SendMessageRequest(
*/
fun customHeaders(customHeaders: List<CustomHeader>?) = apply { this.customHeaders = customHeaders }

/**
* Sets whether the message body is sent as plain text.
* When true, the message body is sent as plain text and the MIME data doesn't include the HTML version of the message.
* When false, the message body is sent as HTML.
* @param isPlaintext Whether the message body is sent as plain text.
* @return The builder.
*/
fun isPlaintext(isPlaintext: Boolean?) = apply { this.isPlaintext = isPlaintext }

/**
* Builds a [SendMessageRequest] instance.
* @return The [SendMessageRequest] instance.
Expand All @@ -220,6 +236,7 @@ data class SendMessageRequest(
trackingOptions,
useDraft,
customHeaders,
isPlaintext,
)
}
}
63 changes: 63 additions & 0 deletions src/test/kotlin/com/nylas/resources/DraftsTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ import java.io.ByteArrayInputStream
import java.lang.reflect.Type
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertIs
import kotlin.test.assertNull
import kotlin.test.assertTrue

class DraftsTests {
private val mockHttpClient: OkHttpClient = Mockito.mock(OkHttpClient::class.java)
Expand Down Expand Up @@ -254,6 +256,67 @@ class DraftsTests {
assertNull(queryParamCaptor.firstValue)
}

@Test
fun `creating a draft with is_plaintext true serializes correctly`() {
val adapter = JsonHelper.moshi().adapter(CreateDraftRequest::class.java)
val createDraftRequest =
CreateDraftRequest(
body = "Hello, I just created a draft using Nylas!",
subject = "Hello from Nylas!",
isPlaintext = true,
)

val serializedRequest = adapter.toJson(createDraftRequest)
assertTrue(serializedRequest.contains("\"is_plaintext\":true"))

drafts.create(grantId, createDraftRequest)

val pathCaptor = argumentCaptor<String>()
val typeCaptor = argumentCaptor<Type>()
val requestBodyCaptor = argumentCaptor<String>()
val queryParamCaptor = argumentCaptor<IQueryParams>()
val overrideParamCaptor = argumentCaptor<RequestOverrides>()
verify(mockNylasClient).executePost<Response<Draft>>(
pathCaptor.capture(),
typeCaptor.capture(),
requestBodyCaptor.capture(),
queryParamCaptor.capture(),
overrideParamCaptor.capture(),
)

assertEquals("v3/grants/$grantId/drafts", pathCaptor.firstValue)
assertEquals(Types.newParameterizedType(Response::class.java, Draft::class.java), typeCaptor.firstValue)
assertEquals(adapter.toJson(createDraftRequest), requestBodyCaptor.firstValue)
assertNull(queryParamCaptor.firstValue)
}

@Test
fun `creating a draft with is_plaintext false or not specified defaults correctly`() {
val adapter = JsonHelper.moshi().adapter(CreateDraftRequest::class.java)

// Test with explicit false
val createDraftRequestFalse =
CreateDraftRequest(
body = "Hello, I just created a draft using Nylas!",
subject = "Hello from Nylas!",
isPlaintext = false,
)

val serializedRequestFalse = adapter.toJson(createDraftRequestFalse)
assertTrue(serializedRequestFalse.contains("\"is_plaintext\":false"))

// Test with not specified (should default to false)
val createDraftRequestDefault =
CreateDraftRequest(
body = "Hello, I just created a draft using Nylas!",
subject = "Hello from Nylas!",
)

val serializedRequestDefault = adapter.toJson(createDraftRequestDefault)
// When null/not specified, the field should not be included in JSON or be false
assertFalse(serializedRequestDefault.contains("\"is_plaintext\":true"))
}

@Test
fun `creating a draft with small attachment calls requests with the correct params`() {
val adapter = JsonHelper.moshi().adapter(CreateDraftRequest::class.java)
Expand Down
Loading
Loading