Skip to content

Commit df928dc

Browse files
authored
feat(messages,drafts): support isPlaintext for messages.send and drafts.create (#661)
1 parent 7fe13ff commit df928dc

File tree

12 files changed

+223
-34
lines changed

12 files changed

+223
-34
lines changed

CHANGELOG.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
- Support `isPlaintext` boolean for messages send and drafts create requests
12+
- Expose raw response headers on all responses via non-enumerable `rawHeaders` while keeping existing `headers` camelCased
13+
14+
## [7.12.0] - 2025-08-01
15+
1016
### Changed
1117
- Upgraded node-fetch from v2 to v3 for better ESM support and compatibility with edge environments
1218

@@ -15,8 +21,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1521
- Updated Jest configuration to properly handle ESM modules from node-fetch v3
1622
- Removed incompatible AbortSignal import from node-fetch externals (now uses native Node.js AbortSignal)
1723

18-
### Added
19-
- Expose raw response headers on all responses via non-enumerable `rawHeaders` while keeping existing `headers` camelCased
2024

2125
## [7.11.0] - 2025-06-23
2226

examples/messages/cli-interface.ts

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ interface CliOptions {
99
attachmentSize: 'small' | 'large';
1010
format: FileFormat;
1111
testEmail?: string;
12+
isPlaintext: boolean;
1213
}
1314

1415
async function getCliOptions(fileManager: TestFileManager): Promise<CliOptions> {
@@ -51,42 +52,48 @@ async function getCliOptions(fileManager: TestFileManager): Promise<CliOptions>
5152
message: 'Recipient email address:',
5253
default: process.env.TEST_EMAIL || '',
5354
validate: (input: string) => input.includes('@') || 'Please enter a valid email address'
55+
},
56+
{
57+
type: 'confirm',
58+
name: 'isPlaintext',
59+
message: 'Send as plaintext (no HTML rendering)?',
60+
default: false
5461
}
5562
]);
5663

5764
return answers as CliOptions;
5865
}
5966

6067
async function runExample(examples: SendAttachmentsExamples, fileManager: TestFileManager, options: CliOptions): Promise<void> {
61-
const { format, testEmail, attachmentSize } = options;
68+
const { format, testEmail, attachmentSize, isPlaintext } = options;
6269

6370
if (!testEmail) {
6471
console.log(chalk.yellow('⚠️ No email provided. Skipping send.'));
6572
return;
6673
}
6774

6875
try {
69-
console.log(chalk.blue(`\n📤 Running ${format} attachment example (${attachmentSize} files)...\n`));
76+
console.log(chalk.blue(`\n📤 Running ${format} attachment example (${attachmentSize} files)${isPlaintext ? ' in plaintext mode' : ''}...\n`));
7077

7178
let result: NylasResponse<Message>;
7279
const isLarge = attachmentSize === 'large';
7380

7481
// Route to the appropriate example based on format
7582
switch (format) {
7683
case 'file':
77-
result = await examples.sendFilePathAttachments(fileManager, testEmail, isLarge);
84+
result = await examples.sendFilePathAttachments(fileManager, testEmail, isLarge, isPlaintext);
7885
break;
7986
case 'stream':
80-
result = await examples.sendStreamAttachments(fileManager, testEmail, isLarge);
87+
result = await examples.sendStreamAttachments(fileManager, testEmail, isLarge, isPlaintext);
8188
break;
8289
case 'buffer':
83-
result = await examples.sendBufferAttachments(fileManager, testEmail, isLarge);
90+
result = await examples.sendBufferAttachments(fileManager, testEmail, isLarge, isPlaintext);
8491
break;
8592
case 'string':
86-
result = await examples.sendStringAttachments(fileManager, testEmail, isLarge);
93+
result = await examples.sendStringAttachments(fileManager, testEmail, isLarge, isPlaintext);
8794
break;
8895
default:
89-
result = await examples.sendAttachmentsByFormat(fileManager, format, testEmail, attachmentSize);
96+
result = await examples.sendAttachmentsByFormat(fileManager, format, testEmail, attachmentSize, isPlaintext);
9097
}
9198

9299
console.log(chalk.green.bold('\n✅ Message sent successfully!'));
@@ -103,11 +110,12 @@ async function runExample(examples: SendAttachmentsExamples, fileManager: TestFi
103110
}
104111
}
105112

106-
async function runBatchMode(examples: SendAttachmentsExamples, fileManager: TestFileManager, size: 'small' | 'large', format: FileFormat, email?: string): Promise<void> {
113+
async function runBatchMode(examples: SendAttachmentsExamples, fileManager: TestFileManager, size: 'small' | 'large', format: FileFormat, email?: string, isPlaintext: boolean = false): Promise<void> {
107114
const options: CliOptions = {
108115
attachmentSize: size,
109116
format,
110-
testEmail: email
117+
testEmail: email,
118+
isPlaintext
111119
};
112120

113121
console.log(chalk.blue.bold('\n🚀 Nylas Send Attachments (Batch Mode)\n'));
@@ -137,17 +145,19 @@ export async function startCli(examples: SendAttachmentsExamples, fileManager: T
137145
.description('Send small attachments')
138146
.option('-f, --format <format>', 'format (file|stream|buffer|string)', 'file')
139147
.option('-e, --email <email>', 'recipient email')
148+
.option('--plaintext', 'send as plaintext', false)
140149
.action(async (options) => {
141-
await runBatchMode(examples, fileManager, 'small', options.format as FileFormat, options.email || testEmail);
150+
await runBatchMode(examples, fileManager, 'small', options.format as FileFormat, options.email || testEmail, Boolean(options.plaintext));
142151
});
143152

144153
program
145154
.command('large')
146155
.description('Send large attachment')
147156
.option('-f, --format <format>', 'format (file|stream|buffer|string)', 'file')
148157
.option('-e, --email <email>', 'recipient email')
158+
.option('--plaintext', 'send as plaintext', false)
149159
.action(async (options) => {
150-
await runBatchMode(examples, fileManager, 'large', options.format as FileFormat, options.email || testEmail);
160+
await runBatchMode(examples, fileManager, 'large', options.format as FileFormat, options.email || testEmail, Boolean(options.plaintext));
151161
});
152162

153163
program

examples/messages/examples/buffer-attachments.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const grantId: string = process.env.NYLAS_GRANT_ID || '';
2121
* Loads the entire file into memory as a Buffer.
2222
* Good for small files or when you need to process content.
2323
*/
24-
export async function sendBufferAttachments(fileManager: TestFileManager, recipientEmail: string, large: boolean = false): Promise<NylasResponse<Message>> {
24+
export async function sendBufferAttachments(fileManager: TestFileManager, recipientEmail: string, large: boolean = false, isPlaintext: boolean = false): Promise<NylasResponse<Message>> {
2525
console.log('💾 Sending attachments using buffers...');
2626

2727
let sizeDescription;
@@ -48,12 +48,15 @@ export async function sendBufferAttachments(fileManager: TestFileManager, recipi
4848
const requestBody: SendMessageRequest = {
4949
to: [{ name: 'Test Recipient', email: recipientEmail }],
5050
subject: 'Nylas SDK - Buffer Attachments',
51-
body: `
51+
body: isPlaintext
52+
? 'Buffer Attachments Example\nThis demonstrates sending attachments using Node.js Buffer objects.'
53+
: `
5254
<h2>Buffer Attachments Example</h2>
5355
<p>This demonstrates sending attachments using Node.js Buffer objects.</p>
5456
<p>Good for small files when you need the content in memory.</p>
5557
`,
56-
attachments: bufferAttachments
58+
attachments: bufferAttachments,
59+
isPlaintext
5760
};
5861

5962
// For large files, use a longer timeout (5 minutes)

examples/messages/examples/file-path-attachments.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const grantId: string = process.env.NYLAS_GRANT_ID || '';
2121
* This is the recommended approach for most use cases.
2222
* Uses streams internally for memory efficiency.
2323
*/
24-
export async function sendFilePathAttachments(fileManager: TestFileManager, recipientEmail: string, large: boolean = false): Promise<NylasResponse<Message>> {
24+
export async function sendFilePathAttachments(fileManager: TestFileManager, recipientEmail: string, large: boolean = false, isPlaintext: boolean = false): Promise<NylasResponse<Message>> {
2525
console.log('📁 Sending attachments using file paths...');
2626

2727
let attachments;
@@ -42,13 +42,16 @@ export async function sendFilePathAttachments(fileManager: TestFileManager, reci
4242
const requestBody: SendMessageRequest = {
4343
to: [{ name: 'Test Recipient', email: recipientEmail }],
4444
subject: `Nylas SDK - File Path Attachments (${sizeDescription})`,
45-
body: `
45+
body: isPlaintext
46+
? `File Path Attachments Example\nThis demonstrates sending attachments using file paths.\nAttachment size: ${sizeDescription} (${attachments.length} file${attachments.length > 1 ? 's' : ''})`
47+
: `
4648
<h2>File Path Attachments Example</h2>
4749
<p>This demonstrates the most common way to send attachments using file paths.</p>
4850
<p>The SDK uses streams internally for memory efficiency.</p>
4951
<p>Attachment size: ${sizeDescription} (${attachments.length} file${attachments.length > 1 ? 's' : ''})</p>
5052
`,
51-
attachments
53+
attachments,
54+
isPlaintext
5255
};
5356

5457
// For large files, use a longer timeout (5 minutes)

examples/messages/examples/flexible-attachments.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const grantId: string = process.env.NYLAS_GRANT_ID || '';
1818
/**
1919
* Flexible attachment sending based on format choice
2020
*/
21-
export async function sendAttachmentsByFormat(fileManager: TestFileManager, format: FileFormat, recipientEmail: string, attachmentSize: 'small' | 'large' = 'small'): Promise<NylasResponse<Message>> {
21+
export async function sendAttachmentsByFormat(fileManager: TestFileManager, format: FileFormat, recipientEmail: string, attachmentSize: 'small' | 'large' = 'small', isPlaintext: boolean = false): Promise<NylasResponse<Message>> {
2222

2323
let attachments: CreateAttachmentRequest[] = [];
2424
let subject: string;
@@ -40,15 +40,18 @@ export async function sendAttachmentsByFormat(fileManager: TestFileManager, form
4040
const requestBody: SendMessageRequest = {
4141
to: [{ name: 'Test Recipient', email: recipientEmail }],
4242
subject,
43-
body: `
43+
body: isPlaintext
44+
? `Attachment Format Test: ${format}\nThis message demonstrates sending attachments using the ${format} format.\nFiles attached: ${attachments.length}`
45+
: `
4446
<h2>Attachment Format Test: ${format}</h2>
4547
<p>This message demonstrates sending attachments using the ${format} format.</p>
4648
<p>Files attached: ${attachments.length}</p>
4749
<ul>
4850
${attachments.map(att => `<li>${att.filename} (${att.size} bytes)</li>`).join('')}
4951
</ul>
5052
`,
51-
attachments
53+
attachments,
54+
isPlaintext
5255
};
5356

5457
// For large files, use a longer timeout (5 minutes)

examples/messages/examples/index.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import { sendStringAttachments } from './string-attachments';
55
import { sendAttachmentsByFormat } from './flexible-attachments';
66

77
export type SendAttachmentsExamples = {
8-
sendFilePathAttachments: typeof sendFilePathAttachments,
9-
sendStreamAttachments: typeof sendStreamAttachments,
10-
sendBufferAttachments: typeof sendBufferAttachments,
11-
sendStringAttachments: typeof sendStringAttachments,
12-
sendAttachmentsByFormat: typeof sendAttachmentsByFormat
8+
sendFilePathAttachments: (fileManager: Parameters<typeof sendFilePathAttachments>[0], recipientEmail: string, large?: boolean, isPlaintext?: boolean) => ReturnType<typeof sendFilePathAttachments>,
9+
sendStreamAttachments: (fileManager: Parameters<typeof sendStreamAttachments>[0], recipientEmail: string, large?: boolean, isPlaintext?: boolean) => ReturnType<typeof sendStreamAttachments>,
10+
sendBufferAttachments: (fileManager: Parameters<typeof sendBufferAttachments>[0], recipientEmail: string, large?: boolean, isPlaintext?: boolean) => ReturnType<typeof sendBufferAttachments>,
11+
sendStringAttachments: (fileManager: Parameters<typeof sendStringAttachments>[0], recipientEmail: string, large?: boolean, isPlaintext?: boolean) => ReturnType<typeof sendStringAttachments>,
12+
sendAttachmentsByFormat: (fileManager: Parameters<typeof sendAttachmentsByFormat>[0], format: Parameters<typeof sendAttachmentsByFormat>[1], recipientEmail: string, attachmentSize?: Parameters<typeof sendAttachmentsByFormat>[3], isPlaintext?: boolean) => ReturnType<typeof sendAttachmentsByFormat>
1313
};

examples/messages/examples/stream-attachments.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const grantId: string = process.env.NYLAS_GRANT_ID || '';
2121
* Useful when you're working with streams from other sources
2222
* or need more control over the stream processing.
2323
*/
24-
export async function sendStreamAttachments(fileManager: TestFileManager, recipientEmail: string, large: boolean = false): Promise<NylasResponse<Message>> {
24+
export async function sendStreamAttachments(fileManager: TestFileManager, recipientEmail: string, large: boolean = false, isPlaintext: boolean = false): Promise<NylasResponse<Message>> {
2525
console.log('🌊 Sending attachments using streams...');
2626

2727
let attachments: CreateAttachmentRequest[] = [];
@@ -52,13 +52,16 @@ export async function sendStreamAttachments(fileManager: TestFileManager, recipi
5252
const requestBody: SendMessageRequest = {
5353
to: [{ name: 'Test Recipient', email: recipientEmail }],
5454
subject: `Nylas SDK - Stream Attachments (${sizeDescription})`,
55-
body: `
55+
body: isPlaintext
56+
? `Stream Attachments Example\nThis demonstrates sending attachments using readable streams.\nAttachment size: ${sizeDescription} (${attachments.length} file${attachments.length > 1 ? 's' : ''})`
57+
: `
5658
<h2>Stream Attachments Example</h2>
5759
<p>This demonstrates sending attachments using readable streams.</p>
5860
<p>Useful when you have streams from other sources.</p>
5961
<p>Attachment size: ${sizeDescription} (${attachments.length} file${attachments.length > 1 ? 's' : ''})</p>
6062
`,
61-
attachments
63+
attachments,
64+
isPlaintext
6265
};
6366

6467
// For large files, use a longer timeout (5 minutes)

examples/messages/examples/string-attachments.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const grantId: string = process.env.NYLAS_GRANT_ID || '';
2121
* Perfect for sending existing files as base64 encoded strings.
2222
* This example pulls the same files used by other examples but encodes them as base64 strings.
2323
*/
24-
export async function sendStringAttachments(fileManager: TestFileManager, recipientEmail: string, large: boolean = false): Promise<NylasResponse<Message>> {
24+
export async function sendStringAttachments(fileManager: TestFileManager, recipientEmail: string, large: boolean = false, isPlaintext: boolean = false): Promise<NylasResponse<Message>> {
2525
console.log('📝 Sending base64 encoded file attachments as strings...');
2626

2727
let stringAttachments: CreateAttachmentRequest[] = [];
@@ -75,7 +75,9 @@ export async function sendStringAttachments(fileManager: TestFileManager, recipi
7575
const requestBody: SendMessageRequest = {
7676
to: [{ name: 'Test Recipient', email: recipientEmail }],
7777
subject: `Nylas SDK - Base64 String Attachments (${sizeDescription})`,
78-
body: `
78+
body: isPlaintext
79+
? `Base64 String Attachments Example\nThis demonstrates sending existing files as base64 encoded strings.\nAttachment size: ${sizeDescription} (${stringAttachments.length} file${stringAttachments.length > 1 ? 's' : ''})`
80+
: `
7981
<h2>Base64 String Attachments Example</h2>
8082
<p>This demonstrates sending existing files as base64 encoded strings.</p>
8183
<p>Files are converted from the same test files used in other examples.</p>
@@ -84,7 +86,8 @@ export async function sendStringAttachments(fileManager: TestFileManager, recipi
8486
${stringAttachments.map(att => `<li>${att.filename} (${att.size} bytes base64 encoded)</li>`).join('')}
8587
</ul>
8688
`,
87-
attachments: stringAttachments
89+
attachments: stringAttachments,
90+
isPlaintext
8891
};
8992

9093
// For large files, use a longer timeout (5 minutes)

examples/messages/messages.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,47 @@ async function demonstrateMessageSending(): Promise<NylasResponse<Message> | nul
243243
}
244244
}
245245

246+
/**
247+
* Demonstrates sending a plaintext-only message (no attachments)
248+
*/
249+
async function demonstratePlaintextMessageSending(): Promise<NylasResponse<Message> | null> {
250+
console.log('\n=== Demonstrating Plaintext Message Sending ===');
251+
try {
252+
const testEmail = process.env.TEST_EMAIL;
253+
if (!testEmail) {
254+
console.log('TEST_EMAIL environment variable not set. Skipping plaintext message sending demo.');
255+
return null;
256+
}
257+
258+
const requestBody: SendMessageRequest = {
259+
to: [{ name: 'Plaintext Recipient', email: testEmail }],
260+
subject: 'Nylas SDK Messages Example - Plaintext',
261+
body: 'This message is sent as plain text only.',
262+
isPlaintext: true,
263+
};
264+
265+
const sentMessage = await nylas.messages.send({
266+
identifier: grantId,
267+
requestBody,
268+
});
269+
270+
console.log('Plaintext message sent successfully!');
271+
console.log(`- Message ID: ${sentMessage.data.id}`);
272+
console.log(`- Subject: ${sentMessage.data.subject}`);
273+
console.log(`- To: ${sentMessage.data.to?.map(t => `${t.name} <${t.email}>`).join(', ')}`);
274+
275+
return sentMessage;
276+
} catch (error) {
277+
if (error instanceof NylasApiError) {
278+
console.error(`Error sending plaintext message: ${error.message}`);
279+
console.error(`Error details: ${JSON.stringify(error, null, 2)}`);
280+
} else if (error instanceof Error) {
281+
console.error(`Unexpected error in demonstratePlaintextMessageSending: ${error.message}`);
282+
}
283+
return null;
284+
}
285+
}
286+
246287
/**
247288
* Demonstrates updating a message
248289
*/
@@ -464,6 +505,7 @@ async function main(): Promise<void> {
464505

465506
// Run all demonstrations
466507
await demonstrateMessageFields();
508+
await demonstratePlaintextMessageSending();
467509
await demonstrateRawMime();
468510
await demonstrateMessageQuerying();
469511

src/models/drafts.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ export interface CreateDraftRequest {
7171
* An array of custom headers to add to the message.
7272
*/
7373
customHeaders?: CustomHeader[];
74+
/**
75+
* When true, the message body is sent as plain text and the MIME data doesn't include the HTML version of the message.
76+
* When false, the message body is sent as HTML. Defaults to false.
77+
*/
78+
isPlaintext?: boolean;
7479
}
7580

7681
/**
@@ -103,7 +108,10 @@ export interface Draft
103108
/**
104109
* Interface representing a request to update a draft.
105110
*/
106-
export type UpdateDraftRequest = Partial<CreateDraftRequest> & {
111+
export type UpdateDraftRequest = Omit<
112+
Partial<CreateDraftRequest>,
113+
'isPlaintext'
114+
> & {
107115
/**
108116
* Return drafts that are unread.
109117
*/

0 commit comments

Comments
 (0)