Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved message sending and draft create/update performance #544

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
@@ -1,5 +1,8 @@
# Changelog

### Unreleased
* Improved message sending and draft create/update performance

### 7.2.0 / 2024-02-27
* Added support for `roundTo` field in availability response model
* Added support for `attributes` field in folder model
Expand Down
52 changes: 42 additions & 10 deletions src/resources/drafts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,28 @@ export class Drafts extends Resource {
requestBody,
overrides,
}: CreateDraftParams & Overrides): Promise<NylasResponse<Draft>> {
const form = Messages._buildFormRequest(requestBody);
const path = `/v3/grants/${identifier}/drafts`;

return this.apiClient.request({
method: 'POST',
path: `/v3/grants/${identifier}/drafts`,
form,
// Use form data only if the attachment size is greater than 3mb
const attachmentSize =
requestBody.attachments?.reduce(function(_, attachment) {
return attachment.size || 0;
}, 0) || 0;

if (attachmentSize >= Messages.MAXIMUM_JSON_ATTACHMENT_SIZE) {
const form = Messages._buildFormRequest(requestBody);

return this.apiClient.request({
method: 'POST',
path,
form,
overrides,
});
}

return super._create({
path,
requestBody,
overrides,
});
}
Expand All @@ -134,12 +150,28 @@ export class Drafts extends Resource {
requestBody,
overrides,
}: UpdateDraftParams & Overrides): Promise<NylasResponse<Draft>> {
const form = Messages._buildFormRequest(requestBody);
const path = `/v3/grants/${identifier}/drafts/${draftId}`;

return this.apiClient.request({
method: 'PUT',
path: `/v3/grants/${identifier}/drafts/${draftId}`,
form,
// Use form data only if the attachment size is greater than 3mb
const attachmentSize =
requestBody.attachments?.reduce(function(_, attachment) {
return attachment.size || 0;
}, 0) || 0;

if (attachmentSize >= Messages.MAXIMUM_JSON_ATTACHMENT_SIZE) {
const form = Messages._buildFormRequest(requestBody);

return this.apiClient.request({
method: 'PUT',
path,
form,
overrides,
});
}

return super._update({
path,
requestBody,
overrides,
});
}
Expand Down
28 changes: 21 additions & 7 deletions src/resources/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
import * as FormData from 'form-data';
import { objKeysToSnakeCase } from '../utils.js';
import { SmartCompose } from './smartCompose.js';
import APIClient from '../apiClient.js';
import APIClient, { RequestOptionsParams } from '../apiClient.js';

/**
* The parameters for the {@link Messages.list} method
Expand Down Expand Up @@ -110,6 +110,8 @@ export type StopScheduledMessageParams = FindScheduledMessageParams;
*/
export class Messages extends Resource {
public smartCompose: SmartCompose;
// The maximum size of an attachment that can be sent using json
static MAXIMUM_JSON_ATTACHMENT_SIZE = 3 * 1024 * 1024;

constructor(apiClient: APIClient) {
super(apiClient);
Expand Down Expand Up @@ -192,14 +194,26 @@ export class Messages extends Resource {
requestBody,
overrides,
}: SendMessageParams & Overrides): Promise<NylasResponse<Message>> {
const form = Messages._buildFormRequest(requestBody);

return this.apiClient.request({
const path = `/v3/grants/${identifier}/messages/send`;
const requestOptions: RequestOptionsParams = {
method: 'POST',
path: `/v3/grants/${identifier}/messages/send`,
form,
path,
overrides,
});
};

// Use form data only if the attachment size is greater than 3mb
const attachmentSize =
requestBody.attachments?.reduce(function(_, attachment) {
return attachment.size || 0;
}, 0) || 0;

if (attachmentSize >= Messages.MAXIMUM_JSON_ATTACHMENT_SIZE) {
requestOptions.form = Messages._buildFormRequest(requestBody);
} else {
requestOptions.body = requestBody;
}

return this.apiClient.request(requestOptions);
}

/**
Expand Down
85 changes: 78 additions & 7 deletions tests/resources/drafts.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,18 +86,46 @@ describe('Drafts', () => {
});

const capturedRequest = apiClient.request.mock.calls[0][0];
const formData = ((capturedRequest.form as any) as MockedFormData)._getAppendedData();
expect(formData).toEqual({
message: JSON.stringify(jsonBody),
expect(capturedRequest.method).toEqual('POST');
expect(capturedRequest.path).toEqual('/v3/grants/id123/drafts');
expect(capturedRequest.body).toEqual(jsonBody);
expect(capturedRequest.overrides).toEqual({
apiUri: 'https://test.api.nylas.com',
});
});

it('should attach files less than 3mb', async () => {
const fileStream = createReadableStream('This is the text from file 1');
const jsonBody = {
to: [{ name: 'Test', email: 'test@example.com' }],
subject: 'This is my test email',
attachments: [
{
filename: 'file1.txt',
contentType: 'text/plain',
content: fileStream,
size: 100,
},
],
};
await drafts.create({
identifier: 'id123',
requestBody: jsonBody,
overrides: {
apiUri: 'https://test.api.nylas.com',
},
});

const capturedRequest = apiClient.request.mock.calls[0][0];
expect(capturedRequest.method).toEqual('POST');
expect(capturedRequest.path).toEqual('/v3/grants/id123/drafts');
expect(capturedRequest.body).toEqual(jsonBody);
expect(capturedRequest.overrides).toEqual({
apiUri: 'https://test.api.nylas.com',
});
});

it('should attach files properly', async () => {
it('should attach files 3mb+ properly', async () => {
const messageJson = {
to: [{ name: 'Test', email: 'test@example.com' }],
subject: 'This is my test email',
Expand All @@ -107,6 +135,7 @@ describe('Drafts', () => {
filename: 'file1.txt',
contentType: 'text/plain',
content: fileStream,
size: 3 * 1024 * 1024,
};

await drafts.create({
Expand Down Expand Up @@ -134,8 +163,17 @@ describe('Drafts', () => {

describe('update', () => {
it('should call apiClient.request with the correct params', async () => {
const fileStream = createReadableStream('This is the text from file 1');
const jsonBody = {
subject: 'updated subject',
attachments: [
{
filename: 'file1.txt',
contentType: 'text/plain',
content: fileStream,
size: 100,
},
],
};
await drafts.update({
identifier: 'id123',
Expand All @@ -147,10 +185,43 @@ describe('Drafts', () => {
});

const capturedRequest = apiClient.request.mock.calls[0][0];
const formData = ((capturedRequest.form as any) as MockedFormData)._getAppendedData();
expect(formData).toEqual({
message: JSON.stringify(jsonBody),
expect(capturedRequest.method).toEqual('PUT');
expect(capturedRequest.path).toEqual('/v3/grants/id123/drafts/draft123');
expect(capturedRequest.body).toEqual(jsonBody);
expect(capturedRequest.overrides).toEqual({
apiUri: 'https://test.api.nylas.com',
});
});

it('should attach files 3mb+ properly', async () => {
const messageJson = {
to: [{ name: 'Test', email: 'test@example.com' }],
subject: 'This is my test email',
};
const fileStream = createReadableStream('This is the text from file 1');
const file1: CreateAttachmentRequest = {
filename: 'file1.txt',
contentType: 'text/plain',
content: fileStream,
size: 3 * 1024 * 1024,
};

await drafts.update({
identifier: 'id123',
draftId: 'draft123',
requestBody: {
...messageJson,
attachments: [file1],
},
overrides: {
apiUri: 'https://test.api.nylas.com',
},
});

const capturedRequest = apiClient.request.mock.calls[0][0];
const formData = ((capturedRequest.form as any) as MockedFormData)._getAppendedData();
expect(formData.message).toEqual(JSON.stringify(messageJson));
expect(formData.file0).toEqual(fileStream);
expect(capturedRequest.method).toEqual('PUT');
expect(capturedRequest.path).toEqual('/v3/grants/id123/drafts/draft123');
expect(capturedRequest.overrides).toEqual({
Expand Down
37 changes: 33 additions & 4 deletions tests/resources/messages.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,18 +136,46 @@ describe('Messages', () => {
});

const capturedRequest = apiClient.request.mock.calls[0][0];
const formData = ((capturedRequest.form as any) as MockedFormData)._getAppendedData();
expect(formData).toEqual({
message: JSON.stringify(jsonBody),
expect(capturedRequest.method).toEqual('POST');
expect(capturedRequest.path).toEqual('/v3/grants/id123/messages/send');
expect(capturedRequest.body).toEqual(jsonBody);
expect(capturedRequest.overrides).toEqual({
apiUri: 'https://test.api.nylas.com',
});
});

it('should attach files less than 3mb', async () => {
const fileStream = createReadableStream('This is the text from file 1');
const jsonBody = {
to: [{ name: 'Test', email: 'test@example.com' }],
subject: 'This is my test email',
attachments: [
{
filename: 'file1.txt',
contentType: 'text/plain',
content: fileStream,
size: 100,
},
],
};
await messages.send({
identifier: 'id123',
requestBody: jsonBody,
overrides: {
apiUri: 'https://test.api.nylas.com',
},
});

const capturedRequest = apiClient.request.mock.calls[0][0];
expect(capturedRequest.method).toEqual('POST');
expect(capturedRequest.path).toEqual('/v3/grants/id123/messages/send');
expect(capturedRequest.body).toEqual(jsonBody);
expect(capturedRequest.overrides).toEqual({
apiUri: 'https://test.api.nylas.com',
});
});

it('should attach files properly', async () => {
it('should attach files 3mb+ properly', async () => {
const messageJson = {
to: [{ name: 'Test', email: 'test@example.com' }],
subject: 'This is my test email',
Expand All @@ -157,6 +185,7 @@ describe('Messages', () => {
filename: 'file1.txt',
contentType: 'text/plain',
content: fileStream,
size: 3 * 1024 * 1024,
};

await messages.send({
Expand Down
Loading