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..279b62b2 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..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({ @@ -482,6 +487,124 @@ 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[ + 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' }); + 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[ + apiClient.request.mock.calls.length - 1 + ][0]; + const formData = ( + capturedRequest.form as any as MockedFormData + )._getAppendedData(); + expect(formData.mime).toEqual(mimeContent); + }); + + 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[ + 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('complex-mime-test'); + }); + }); + describe('scheduledMessages', () => { it('listing should call apiClient.request with the correct params', async () => { await messages.listScheduledMessages({