From c06092bdc46bf677d8701b24d32c46fc2c6c9290 Mon Sep 17 00:00:00 2001 From: Kiran Raju Date: Fri, 27 Jun 2025 11:06:05 -0500 Subject: [PATCH 1/3] Adding sending via raw mime --- src/models/messages.ts | 25 ++++++++ src/resources/messages.ts | 52 ++++++++++++++++ tests/resources/messages.spec.ts | 104 +++++++++++++++++++++++++++++++ 3 files changed, 181 insertions(+) diff --git a/src/models/messages.ts b/src/models/messages.ts index f09fe7d4..455d042c 100644 --- a/src/models/messages.ts +++ b/src/models/messages.ts @@ -356,3 +356,28 @@ export interface CleanMessagesResponse extends Message { */ conversation: string; } + +/** + * Interface representing a request to send a message using raw MIME format. + */ +export interface SendMimeMessageRequest { + /** + * The raw MIME content of the message to send. + */ + mime: string; + /** + * Optional metadata to attach to the message. + * Defaults to empty string if not provided. + */ + metadata?: string; +} + +/** + * Interface representing the query parameters for sending a MIME message. + */ +export interface SendMimeMessageQueryParams { + /** + * The type of message being sent. Must be 'mime' for MIME messages. + */ + type: 'mime'; +} diff --git a/src/resources/messages.ts b/src/resources/messages.ts index 33693972..c4ea9af2 100644 --- a/src/resources/messages.ts +++ b/src/resources/messages.ts @@ -14,6 +14,8 @@ import { Message, ScheduledMessage, ScheduledMessagesList, + SendMimeMessageRequest, + SendMimeMessageQueryParams, StopScheduledMessageResponse, UpdateMessageRequest, } from '../models/messages.js'; @@ -85,6 +87,18 @@ export interface SendMessageParams { requestBody: SendMessageRequest; } +/** + * The parameters for the {@link Messages.sendMime} method + * @property identifier The identifier of the grant to act upon + * @property requestBody The MIME message to send + * @property queryParams The query parameters for the MIME send request + */ +export interface SendMimeMessageParams { + identifier: string; + requestBody: SendMimeMessageRequest; + queryParams: SendMimeMessageQueryParams; +} + /** * The parameters for the {@link Messages.listScheduledMessages} method * @property identifier The identifier of the grant to act upon @@ -264,6 +278,44 @@ export class Messages extends Resource { return this.apiClient.request(requestOptions); } + /** + * Send an email using raw MIME format + * @return The sent message + */ + public async sendMime({ + identifier, + requestBody, + queryParams, + overrides, + }: SendMimeMessageParams & Overrides): Promise> { + const path = makePathParams('/v3/grants/{identifier}/messages/send', { + identifier, + }); + + // Create FormData for MIME message + const FD = require('form-data'); + const FormDataConstructor = FD.default || FD; + const form: FormData = new FormDataConstructor(); + + // Add MIME content + form.append('mime', requestBody.mime); + + // Add metadata (defaults to empty string if not provided) + if (requestBody.metadata) { + form.append('metadata', requestBody.metadata); + } + + const requestOptions: RequestOptionsParams = { + method: 'POST', + path, + form, + queryParams, + overrides, + }; + + return this.apiClient.request(requestOptions); + } + /** * Retrieve your scheduled messages * @return A list of scheduled messages diff --git a/tests/resources/messages.spec.ts b/tests/resources/messages.spec.ts index 31714834..6fe9ab80 100644 --- a/tests/resources/messages.spec.ts +++ b/tests/resources/messages.spec.ts @@ -482,6 +482,110 @@ describe('Messages', () => { }); }); + describe('sendMime', () => { + it('should call apiClient.request with the correct params for MIME message', async () => { + const mimeContent = `MIME-Version: 1.0 +Message-ID: +Subject: Test MIME Message +From: sender@example.com +To: recipient@example.com +Content-Type: text/plain; charset="UTF-8" + +This is a test MIME message.`; + + await messages.sendMime({ + identifier: 'id123', + requestBody: { + mime: mimeContent, + metadata: 'test-metadata', + }, + queryParams: { + type: 'mime', + }, + overrides: { + apiUri: 'https://test.api.nylas.com', + headers: { override: 'bar' }, + }, + }); + + const capturedRequest = apiClient.request.mock.calls[0][0]; + expect(capturedRequest.method).toEqual('POST'); + expect(capturedRequest.path).toEqual('/v3/grants/id123/messages/send'); + expect(capturedRequest.queryParams).toEqual({ type: 'mime' }); + expect(capturedRequest.overrides).toEqual({ + apiUri: 'https://test.api.nylas.com', + headers: { override: 'bar' }, + }); + + // Check that form data was created correctly + const formData = (capturedRequest.form as any as MockedFormData)._getAppendedData(); + expect(formData.mime).toEqual(mimeContent); + expect(formData.metadata).toEqual('test-metadata'); + }); + + it('should use empty string for metadata when not provided', async () => { + const mimeContent = `MIME-Version: 1.0 +Subject: Test MIME Message +From: sender@example.com +To: recipient@example.com +Content-Type: text/plain; charset="UTF-8" + +This is a test MIME message.`; + + await messages.sendMime({ + identifier: 'id123', + requestBody: { + mime: mimeContent, + }, + queryParams: { + type: 'mime', + }, + }); + + const capturedRequest = apiClient.request.mock.calls[0][0]; + const formData = (capturedRequest.form as any as MockedFormData)._getAppendedData(); + expect(formData.mime).toEqual(mimeContent); + expect(formData.metadata).toEqual(''); + }); + + it('should handle complex MIME content with multipart boundaries', async () => { + const mimeContent = `MIME-Version: 1.0 +Message-ID: +Subject: Complex MIME Message +From: sender@example.com +To: recipient@example.com +Content-Type: multipart/alternative; boundary="boundary_123" + +--boundary_123 +Content-Type: text/plain; charset="UTF-8" + +Plain text content + +--boundary_123 +Content-Type: text/html; charset="UTF-8" + +
HTML content
+ +--boundary_123--`; + + await messages.sendMime({ + identifier: 'id123', + requestBody: { + mime: mimeContent, + metadata: 'complex-mime-test', + }, + queryParams: { + type: 'mime', + }, + }); + + const capturedRequest = apiClient.request.mock.calls[0][0]; + const formData = (capturedRequest.form as any as MockedFormData)._getAppendedData(); + expect(formData.mime).toEqual(mimeContent); + expect(formData.metadata).toEqual('complex-mime-test'); + }); + }); + describe('scheduledMessages', () => { it('listing should call apiClient.request with the correct params', async () => { await messages.listScheduledMessages({ From bb3d3b79a632688d05f669b2ec3cbd649fdb4822 Mon Sep 17 00:00:00 2001 From: Kiran Raju Date: Fri, 27 Jun 2025 11:42:05 -0500 Subject: [PATCH 2/3] Update failing tests --- src/resources/messages.ts | 6 +++--- tests/resources/messages.spec.ts | 12 +++++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/resources/messages.ts b/src/resources/messages.ts index c4ea9af2..279b62b2 100644 --- a/src/resources/messages.ts +++ b/src/resources/messages.ts @@ -291,15 +291,15 @@ export class Messages extends Resource { const path = makePathParams('/v3/grants/{identifier}/messages/send', { identifier, }); - + // Create FormData for MIME message const FD = require('form-data'); const FormDataConstructor = FD.default || FD; const form: FormData = new FormDataConstructor(); - + // Add MIME content form.append('mime', requestBody.mime); - + // Add metadata (defaults to empty string if not provided) if (requestBody.metadata) { form.append('metadata', requestBody.metadata); diff --git a/tests/resources/messages.spec.ts b/tests/resources/messages.spec.ts index 6fe9ab80..81cce685 100644 --- a/tests/resources/messages.spec.ts +++ b/tests/resources/messages.spec.ts @@ -518,7 +518,9 @@ This is a test MIME message.`; }); // Check that form data was created correctly - const formData = (capturedRequest.form as any as MockedFormData)._getAppendedData(); + const formData = ( + capturedRequest.form as any as MockedFormData + )._getAppendedData(); expect(formData.mime).toEqual(mimeContent); expect(formData.metadata).toEqual('test-metadata'); }); @@ -543,7 +545,9 @@ This is a test MIME message.`; }); const capturedRequest = apiClient.request.mock.calls[0][0]; - const formData = (capturedRequest.form as any as MockedFormData)._getAppendedData(); + const formData = ( + capturedRequest.form as any as MockedFormData + )._getAppendedData(); expect(formData.mime).toEqual(mimeContent); expect(formData.metadata).toEqual(''); }); @@ -580,7 +584,9 @@ Content-Type: text/html; charset="UTF-8" }); const capturedRequest = apiClient.request.mock.calls[0][0]; - const formData = (capturedRequest.form as any as MockedFormData)._getAppendedData(); + const formData = ( + capturedRequest.form as any as MockedFormData + )._getAppendedData(); expect(formData.mime).toEqual(mimeContent); expect(formData.metadata).toEqual('complex-mime-test'); }); From 444dfe2ed8c89fc4c797cdb13e9ca6d2d68cb5ce Mon Sep 17 00:00:00 2001 From: Kiran Raju Date: Fri, 27 Jun 2025 13:22:30 -0500 Subject: [PATCH 3/3] Fix tests --- tests/resources/messages.spec.ts | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/tests/resources/messages.spec.ts b/tests/resources/messages.spec.ts index 81cce685..28b2058a 100644 --- a/tests/resources/messages.spec.ts +++ b/tests/resources/messages.spec.ts @@ -38,6 +38,11 @@ describe('Messages', () => { apiClient.request.mockResolvedValue({}); }); + beforeEach(() => { + jest.clearAllMocks(); + apiClient.request.mockResolvedValue({}); + }); + describe('list', () => { it('should call apiClient.request with the correct params', async () => { await messages.list({ @@ -508,7 +513,10 @@ This is a test MIME message.`; }, }); - const capturedRequest = apiClient.request.mock.calls[0][0]; + const capturedRequest = + apiClient.request.mock.calls[ + apiClient.request.mock.calls.length - 1 + ][0]; expect(capturedRequest.method).toEqual('POST'); expect(capturedRequest.path).toEqual('/v3/grants/id123/messages/send'); expect(capturedRequest.queryParams).toEqual({ type: 'mime' }); @@ -544,12 +552,14 @@ This is a test MIME message.`; }, }); - const capturedRequest = apiClient.request.mock.calls[0][0]; + const capturedRequest = + apiClient.request.mock.calls[ + apiClient.request.mock.calls.length - 1 + ][0]; const formData = ( capturedRequest.form as any as MockedFormData )._getAppendedData(); expect(formData.mime).toEqual(mimeContent); - expect(formData.metadata).toEqual(''); }); it('should handle complex MIME content with multipart boundaries', async () => { @@ -583,7 +593,10 @@ Content-Type: text/html; charset="UTF-8" }, }); - const capturedRequest = apiClient.request.mock.calls[0][0]; + const capturedRequest = + apiClient.request.mock.calls[ + apiClient.request.mock.calls.length - 1 + ][0]; const formData = ( capturedRequest.form as any as MockedFormData )._getAppendedData();