From ce9a81fab972977c5083773f6746f4fb92252e8e Mon Sep 17 00:00:00 2001 From: Aaron de Mello Date: Thu, 29 May 2025 09:52:18 -0400 Subject: [PATCH 1/5] Added support for new message `fields` query parameter values: `include_tracking_options` and `raw_mime` --- CHANGELOG.md | 7 ++ src/models/messages.ts | 30 +++++++ tests/resources/messages.spec.ts | 150 +++++++++++++++++++++++++++++++ 3 files changed, 187 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bd825d2..b55b9a8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added +- Support for new message `fields` query parameter values: `include_tracking_options` and `raw_mime` +- Support for `trackingOptions` property in Message responses when using `fields=include_tracking_options` +- `MessageTrackingOptions` interface for tracking message opens, thread replies, link clicks, and custom labels + ## [7.10.0] - 2025-05-27 ### Added diff --git a/src/models/messages.ts b/src/models/messages.ts index afa93796..17161838 100644 --- a/src/models/messages.ts +++ b/src/models/messages.ts @@ -76,6 +76,29 @@ export interface BaseMessage { unread?: boolean; } +/** + * Interface representing message tracking options. + */ +export interface MessageTrackingOptions { + /** + * When true, shows that message open tracking is enabled. + */ + opens: boolean; + /** + * When true, shows that thread replied tracking is enabled. + */ + threadReplies: boolean; + /** + * When true, shows that link clicked tracking is enabled. + */ + links: boolean; + /** + * A label describing the message tracking purpose. + * Maximum length: 2048 characters. + */ + label: string; +} + /** * Interface representing a Nylas Message object. */ @@ -89,6 +112,11 @@ export interface Message extends BaseMessage { * Only present if the 'fields' query parameter is set to includeHeaders. */ headers?: MessageHeaders[]; + /** + * The message tracking options. + * Only present if the 'fields' query parameter is set to include_tracking_options. + */ + trackingOptions?: MessageTrackingOptions; /** * A list of key-value pairs storing additional data. */ @@ -150,6 +178,8 @@ export interface MessageHeaders { export enum MessageFields { STANDARD = 'standard', INCLUDE_HEADERS = 'include_headers', + INCLUDE_TRACKING_OPTIONS = 'include_tracking_options', + RAW_MIME = 'raw_mime', } /** diff --git a/tests/resources/messages.spec.ts b/tests/resources/messages.spec.ts index 29e3e218..d11a5307 100644 --- a/tests/resources/messages.spec.ts +++ b/tests/resources/messages.spec.ts @@ -2,6 +2,7 @@ import APIClient from '../../src/apiClient'; import { Messages } from '../../src/resources/messages'; import { createReadableStream, MockedFormData } from '../testUtils'; import { CreateAttachmentRequest } from '../../src/models/attachments'; +import { MessageFields, Message, MessageTrackingOptions } from '../../src/models/messages'; jest.mock('../src/apiClient'); // Mock the FormData constructor @@ -71,6 +72,42 @@ describe('Messages', () => { }, }); }); + + it('should call apiClient.request with fields=include_headers for list', async () => { + await messages.list({ + identifier: 'id123', + queryParams: { + fields: MessageFields.INCLUDE_HEADERS, + }, + }); + + expect(apiClient.request).toHaveBeenCalledWith({ + method: 'GET', + path: '/v3/grants/id123/messages', + overrides: undefined, + queryParams: { + fields: MessageFields.INCLUDE_HEADERS, + }, + }); + }); + + it('should call apiClient.request with fields=standard for list', async () => { + await messages.list({ + identifier: 'id123', + queryParams: { + fields: MessageFields.STANDARD, + }, + }); + + expect(apiClient.request).toHaveBeenCalledWith({ + method: 'GET', + path: '/v3/grants/id123/messages', + overrides: undefined, + queryParams: { + fields: MessageFields.STANDARD, + }, + }); + }); }); describe('find', () => { @@ -119,6 +156,80 @@ describe('Messages', () => { }) ); }); + + it('should call apiClient.request with fields=include_tracking_options', async () => { + await messages.find({ + identifier: 'id123', + messageId: 'message123', + queryParams: { + fields: MessageFields.INCLUDE_TRACKING_OPTIONS, + }, + }); + + expect(apiClient.request).toHaveBeenCalledWith({ + method: 'GET', + path: '/v3/grants/id123/messages/message123', + overrides: undefined, + queryParams: { + fields: MessageFields.INCLUDE_TRACKING_OPTIONS, + }, + }); + }); + + it('should call apiClient.request with fields=raw_mime', async () => { + await messages.find({ + identifier: 'id123', + messageId: 'message123', + queryParams: { + fields: MessageFields.RAW_MIME, + }, + }); + + expect(apiClient.request).toHaveBeenCalledWith({ + method: 'GET', + path: '/v3/grants/id123/messages/message123', + overrides: undefined, + queryParams: { + fields: MessageFields.RAW_MIME, + }, + }); + }); + + it('should call apiClient.request with fields=include_headers for list', async () => { + await messages.list({ + identifier: 'id123', + queryParams: { + fields: MessageFields.INCLUDE_HEADERS, + }, + }); + + expect(apiClient.request).toHaveBeenCalledWith({ + method: 'GET', + path: '/v3/grants/id123/messages', + overrides: undefined, + queryParams: { + fields: MessageFields.INCLUDE_HEADERS, + }, + }); + }); + + it('should call apiClient.request with fields=standard for list', async () => { + await messages.list({ + identifier: 'id123', + queryParams: { + fields: MessageFields.STANDARD, + }, + }); + + expect(apiClient.request).toHaveBeenCalledWith({ + method: 'GET', + path: '/v3/grants/id123/messages', + overrides: undefined, + queryParams: { + fields: MessageFields.STANDARD, + }, + }); + }); }); describe('update', () => { @@ -383,4 +494,43 @@ describe('Messages', () => { }); }); }); + + describe('MessageTrackingOptions interface', () => { + it('should have the correct structure for MessageTrackingOptions', () => { + const trackingOptions: MessageTrackingOptions = { + opens: true, + threadReplies: false, + links: true, + label: 'Test tracking label', + }; + + expect(trackingOptions.opens).toBe(true); + expect(trackingOptions.threadReplies).toBe(false); + expect(trackingOptions.links).toBe(true); + expect(trackingOptions.label).toBe('Test tracking label'); + }); + + it('should allow Message interface to include trackingOptions', () => { + const message: Partial = { + id: 'message123', + grantId: 'grant123', + object: 'message', + date: 1234567890, + folders: ['folder1'], + to: [{ name: 'Test User', email: 'test@example.com' }], + trackingOptions: { + opens: true, + threadReplies: false, + links: true, + label: 'Test tracking', + }, + }; + + expect(message.trackingOptions).toBeDefined(); + expect(message.trackingOptions?.opens).toBe(true); + expect(message.trackingOptions?.threadReplies).toBe(false); + expect(message.trackingOptions?.links).toBe(true); + expect(message.trackingOptions?.label).toBe('Test tracking'); + }); + }); }); From 6e063034252dcc2dc53b1b99b7a12eebee5d8d1f Mon Sep 17 00:00:00 2001 From: Aaron de Mello Date: Thu, 29 May 2025 09:59:16 -0400 Subject: [PATCH 2/5] Added support for `rawMime` property in Message responses when using `fields=raw_mime` --- CHANGELOG.md | 1 + src/models/messages.ts | 6 ++++++ tests/resources/messages.spec.ts | 34 ++++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b55b9a8e..98456771 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Support for new message `fields` query parameter values: `include_tracking_options` and `raw_mime` - Support for `trackingOptions` property in Message responses when using `fields=include_tracking_options` +- Support for `rawMime` property in Message responses when using `fields=raw_mime` - `MessageTrackingOptions` interface for tracking message opens, thread replies, link clicks, and custom labels ## [7.10.0] - 2025-05-27 diff --git a/src/models/messages.ts b/src/models/messages.ts index 17161838..f09fe7d4 100644 --- a/src/models/messages.ts +++ b/src/models/messages.ts @@ -117,6 +117,12 @@ export interface Message extends BaseMessage { * Only present if the 'fields' query parameter is set to include_tracking_options. */ trackingOptions?: MessageTrackingOptions; + /** + * A Base64url-encoded string containing the message data (including the body content). + * Only present if the 'fields' query parameter is set to raw_mime. + * When this field is requested, only grant_id, object, id, and raw_mime fields are returned. + */ + rawMime?: string; /** * A list of key-value pairs storing additional data. */ diff --git a/tests/resources/messages.spec.ts b/tests/resources/messages.spec.ts index d11a5307..1cf87ed1 100644 --- a/tests/resources/messages.spec.ts +++ b/tests/resources/messages.spec.ts @@ -532,5 +532,39 @@ describe('Messages', () => { expect(message.trackingOptions?.links).toBe(true); expect(message.trackingOptions?.label).toBe('Test tracking'); }); + + it('should allow Message interface to include rawMime', () => { + const message: Partial = { + id: 'message123', + grantId: 'grant123', + object: 'message', + rawMime: 'base64-encoded-mime-content-example', + }; + + expect(message.rawMime).toBeDefined(); + expect(message.rawMime).toBe('base64-encoded-mime-content-example'); + }); + + it('should allow Message interface to include both trackingOptions and rawMime', () => { + const message: Partial = { + id: 'message123', + grantId: 'grant123', + object: 'message', + date: 1234567890, + folders: ['folder1'], + to: [{ name: 'Test User', email: 'test@example.com' }], + trackingOptions: { + opens: true, + threadReplies: false, + links: true, + label: 'Test tracking', + }, + rawMime: 'base64-encoded-mime-content-example', + }; + + expect(message.trackingOptions).toBeDefined(); + expect(message.rawMime).toBeDefined(); + expect(message.rawMime).toBe('base64-encoded-mime-content-example'); + }); }); }); From 9a68cb8483aff1f733613b7009fc4f256f815d99 Mon Sep 17 00:00:00 2001 From: Aaron de Mello Date: Thu, 29 May 2025 10:37:00 -0400 Subject: [PATCH 3/5] feat: add message cleaning and scheduled messages examples --- examples/.env.example | 5 +- examples/README.md | 10 + examples/messages/ENVIRONMENT.md | 45 +++ examples/messages/README.md | 59 ++++ examples/messages/messages.ts | 507 +++++++++++++++++++++++++++++++ examples/package.json | 3 +- 6 files changed, 627 insertions(+), 2 deletions(-) create mode 100644 examples/messages/ENVIRONMENT.md create mode 100644 examples/messages/README.md create mode 100644 examples/messages/messages.ts diff --git a/examples/.env.example b/examples/.env.example index bc0794e1..c635ee7f 100644 --- a/examples/.env.example +++ b/examples/.env.example @@ -11,4 +11,7 @@ MEETING_LINK=your_meeting_link_here NYLAS_GRANT_ID=your_grant_id_here # Calendar ID (required for event creation/updates) -NYLAS_CALENDAR_ID=your_calendar_id_here \ No newline at end of file +NYLAS_CALENDAR_ID=your_calendar_id_here + +# For testing message sending (optional) +TEST_EMAIL=your-test-email@example.com \ No newline at end of file diff --git a/examples/README.md b/examples/README.md index 58c6a3ad..1517f82c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -5,6 +5,7 @@ This directory contains examples of how to use the Nylas Node.js SDK to interact ## Examples - [Notetakers](./notetakers/README.md) - Examples of how to use the Nylas Notetakers API to invite a Notetaker bot to meetings, get recordings and transcripts, and more. +- [Messages](./messages/README.md) - Examples of how to use the Nylas Messages API to list, find, send, update messages, and work with new features like tracking options and raw MIME data. ## Running the Examples @@ -31,10 +32,19 @@ To run these examples, you'll need to: ```bash # Using ts-node npx ts-node notetakers/notetaker.ts + npx ts-node calendars/event_with_notetaker.ts + npx ts-node messages/messages.ts + + # Or using npm scripts + npm run notetakers + npm run calendars + npm run messages # Or if you compiled the examples npm run build node dist/notetakers/notetaker.js + node dist/calendars/event_with_notetaker.js + node dist/messages/messages.js ``` ## Documentation diff --git a/examples/messages/ENVIRONMENT.md b/examples/messages/ENVIRONMENT.md new file mode 100644 index 00000000..afd6f8ef --- /dev/null +++ b/examples/messages/ENVIRONMENT.md @@ -0,0 +1,45 @@ +# Environment Setup for Messages Example + +## Required Environment Variables + +Create a `.env` file in the `examples` directory with the following variables: + +```bash +# Required +NYLAS_API_KEY=your_api_key_here +NYLAS_GRANT_ID=your_grant_id_here + +# Optional +NYLAS_API_URI=https://api.us.nylas.com + +# For testing message sending (optional) +TEST_EMAIL=your-test-email@example.com +``` + +## Getting Your API Key and Grant ID + +1. **API Key**: Get your API key from the [Nylas Dashboard](https://dashboard.nylas.com) +2. **Grant ID**: After connecting an email account, you'll get a grant ID that represents that connection + +## Testing Message Sending + +If you want to test the message sending functionality, set the `TEST_EMAIL` environment variable to an email address you control. The example will skip message sending if this variable is not set. + +## Running the Example + +Once your environment variables are set: + +```bash +cd examples +npm install +npm run messages +``` + +## What the Example Demonstrates + +- **Message Fields**: Different ways to query messages with various field options +- **Tracking Options**: How tracking data appears in message responses +- **Raw MIME**: Getting raw MIME data for messages +- **Message Operations**: Listing, finding, updating, and sending messages +- **Scheduled Messages**: Working with scheduled message functionality +- **Message Cleaning**: Using the clean messages API \ No newline at end of file diff --git a/examples/messages/README.md b/examples/messages/README.md new file mode 100644 index 00000000..09856915 --- /dev/null +++ b/examples/messages/README.md @@ -0,0 +1,59 @@ +# Messages Examples + +This directory contains examples of how to use the Nylas Messages API to list, find, send, and work with messages. + +## Features Demonstrated + +This example demonstrates: + +- **Message Operations**: List, find, send, update, and delete messages +- **Message Fields**: Using different `fields` query parameters to get different data: + - `standard` - Standard message payload + - `include_headers` - Message with custom headers + - `include_tracking_options` - Message with tracking settings + - `raw_mime` - Raw MIME data (Base64url-encoded) +- **Message Tracking**: Working with tracking options for opens, thread replies, and link clicks +- **Scheduled Messages**: Creating, listing, and managing scheduled messages +- **Message Cleaning**: Using the clean messages API to process message content +- **Error Handling**: Proper error handling with NylasApiError + +## Prerequisites + +Before running this example, make sure you have: + +1. A Nylas application with an API key +2. A connected email account (grant) +3. The required environment variables set up + +## Environment Variables + +Create a `.env` file in the parent `examples` directory with: + +```bash +# Required +NYLAS_API_KEY=your_api_key_here +NYLAS_GRANT_ID=your_grant_id_here + +# Optional +NYLAS_API_URI=https://api.us.nylas.com +``` + +## Running the Example + +```bash +cd examples +npm install +npx ts-node messages/messages.ts +``` + +## Example Output + +The example will demonstrate: +1. Listing messages with different field options +2. Finding specific messages with tracking data +3. Sending messages with tracking enabled +4. Working with raw MIME data +5. Managing scheduled messages +6. Cleaning message content + +Each operation includes detailed console output showing the API responses and data structures. \ No newline at end of file diff --git a/examples/messages/messages.ts b/examples/messages/messages.ts new file mode 100644 index 00000000..205022f6 --- /dev/null +++ b/examples/messages/messages.ts @@ -0,0 +1,507 @@ +import * as dotenv from 'dotenv'; +import * as path from 'path'; +import * as process from 'process'; +import Nylas from 'nylas'; +import { + Message, + MessageFields, + MessageTrackingOptions, + SendMessageRequest, + UpdateMessageRequest, + CleanMessagesRequest, + ScheduledMessage, + NylasResponse, + NylasListResponse, + NylasApiError, + ListMessagesQueryParams +} from 'nylas'; + +// Load environment variables from .env file +dotenv.config({ path: path.resolve(__dirname, '../.env') }); + +// Check for required environment variables +const apiKey: string = process.env.NYLAS_API_KEY || ''; +const grantId: string = process.env.NYLAS_GRANT_ID || ''; + +if (!apiKey) { + throw new Error('NYLAS_API_KEY environment variable is not set'); +} +if (!grantId) { + throw new Error('NYLAS_GRANT_ID environment variable is not set'); +} + +// Initialize the Nylas client +const nylas = new Nylas({ + apiKey, + apiUri: process.env.NYLAS_API_URI || 'https://api.us.nylas.com' +}); + +/** + * Demonstrates listing messages with different field options + */ +async function demonstrateMessageFields(): Promise { + console.log('\n=== Demonstrating Message Fields ==='); + + try { + // 1. List messages with standard fields + console.log('\n--- Listing messages with standard fields ---'); + const standardMessages = await nylas.messages.list({ + identifier: grantId, + queryParams: { + fields: MessageFields.STANDARD, + limit: 2 + } + }); + + console.log(`Found ${standardMessages.data.length} messages with standard fields`); + for (const message of standardMessages.data) { + console.log(`- ${message.id}: ${message.subject || '(no subject)'}`); + console.log(` From: ${message.from?.map(f => `${f.name} <${f.email}>`).join(', ') || 'Unknown'}`); + console.log(` Tracking Options: ${message.trackingOptions ? 'Present' : 'Not present'}`); + console.log(` Headers: ${message.headers ? 'Present' : 'Not present'}`); + console.log(` Raw MIME: ${message.rawMime ? 'Present' : 'Not present'}`); + } + + // 2. List messages with tracking options + console.log('\n--- Listing messages with tracking options ---'); + const trackingMessages = await nylas.messages.list({ + identifier: grantId, + queryParams: { + fields: MessageFields.INCLUDE_TRACKING_OPTIONS, + limit: 2 + } + }); + + console.log(`Found ${trackingMessages.data.length} messages with tracking options`); + for (const message of trackingMessages.data) { + console.log(`- ${message.id}: ${message.subject || '(no subject)'}`); + if (message.trackingOptions) { + console.log(` Tracking Options:`); + console.log(` Opens: ${message.trackingOptions.opens}`); + console.log(` Thread Replies: ${message.trackingOptions.threadReplies}`); + console.log(` Links: ${message.trackingOptions.links}`); + console.log(` Label: ${message.trackingOptions.label}`); + } else { + console.log(` No tracking options available`); + } + } + + // 3. List messages with headers + console.log('\n--- Listing messages with headers ---'); + const headerMessages = await nylas.messages.list({ + identifier: grantId, + queryParams: { + fields: MessageFields.INCLUDE_HEADERS, + limit: 2 + } + }); + + console.log(`Found ${headerMessages.data.length} messages with headers`); + for (const message of headerMessages.data) { + console.log(`- ${message.id}: ${message.subject || '(no subject)'}`); + if (message.headers && message.headers.length > 0) { + console.log(` Headers (showing first 3):`); + message.headers.slice(0, 3).forEach(header => { + console.log(` ${header.name}: ${header.value.substring(0, 100)}${header.value.length > 100 ? '...' : ''}`); + }); + if (message.headers.length > 3) { + console.log(` ... and ${message.headers.length - 3} more headers`); + } + } else { + console.log(` No headers available`); + } + } + + } catch (error) { + if (error instanceof NylasApiError) { + console.error(`Error demonstrating message fields: ${error.message}`); + console.error(`Error details: ${JSON.stringify(error, null, 2)}`); + } else if (error instanceof Error) { + console.error(`Unexpected error in demonstrateMessageFields: ${error.message}`); + } + throw error; + } +} + +/** + * Demonstrates getting raw MIME data for a message + */ +async function demonstrateRawMime(): Promise { + console.log('\n=== Demonstrating Raw MIME Data ==='); + + try { + // First, get a message ID + const messages = await nylas.messages.list({ + identifier: grantId, + queryParams: { + limit: 1 + } + }); + + if (messages.data.length === 0) { + console.log('No messages available to demonstrate raw MIME'); + return; + } + + const messageId = messages.data[0].id; + console.log(`Getting raw MIME data for message: ${messageId}`); + + // Get the message with raw MIME data + const rawMessage = await nylas.messages.find({ + identifier: grantId, + messageId, + queryParams: { + fields: MessageFields.RAW_MIME + } + }); + + console.log('Raw MIME Response:'); + console.log(`- ID: ${rawMessage.data.id}`); + console.log(`- Grant ID: ${rawMessage.data.grantId}`); + console.log(`- Object: ${rawMessage.data.object}`); + + if (rawMessage.data.rawMime) { + console.log(`- Raw MIME Data: ${rawMessage.data.rawMime.substring(0, 100)}... (truncated)`); + console.log(`- Raw MIME Length: ${rawMessage.data.rawMime.length} characters`); + + // Note: When requesting raw_mime, only grant_id, object, id, and raw_mime fields are returned + console.log('\nNote: When requesting raw_mime, only specific fields are returned:'); + console.log(`- Subject: ${rawMessage.data.subject || 'Not included'}`); + console.log(`- Body: ${rawMessage.data.body ? 'Present' : 'Not included'}`); + console.log(`- From: ${rawMessage.data.from ? 'Present' : 'Not included'}`); + } else { + console.log('- Raw MIME Data: Not available'); + } + + } catch (error) { + if (error instanceof NylasApiError) { + console.error(`Error demonstrating raw MIME: ${error.message}`); + } else if (error instanceof Error) { + console.error(`Unexpected error in demonstrateRawMime: ${error.message}`); + } + throw error; + } +} + +/** + * Demonstrates sending a message with tracking enabled + */ +async function demonstrateMessageSending(): Promise | null> { + console.log('\n=== Demonstrating Message Sending ==='); + + try { + const testEmail = process.env.TEST_EMAIL; + if (!testEmail) { + console.log('TEST_EMAIL environment variable not set. Skipping message sending demo.'); + console.log('Set TEST_EMAIL=your-email@example.com to test message sending.'); + return null; + } + + console.log(`Sending test message to: ${testEmail}`); + + const requestBody: SendMessageRequest = { + to: [{ + name: 'Test Recipient', + email: testEmail + }], + subject: 'Nylas SDK Messages Example - Testing New Features', + body: ` + + +

Nylas SDK Messages Example

+

This message demonstrates the new tracking features in the Nylas Node.js SDK.

+

Features being tested:

+
    +
  • Message tracking options
  • +
  • Raw MIME data support
  • +
  • Enhanced field querying
  • +
+

Visit Nylas Developer Docs for more information.

+

Best regards,
The Nylas SDK Team

+ + + ` + // Note: Tracking options are configured at the API/provider level, not in the request + }; + + console.log('Sending message with tracking...'); + const sentMessage = await nylas.messages.send({ + identifier: grantId, + requestBody + }); + + console.log(`Message sent successfully!`); + console.log(`- Message ID: ${sentMessage.data.id}`); + console.log(`- Subject: ${sentMessage.data.subject}`); + console.log(`- To: ${sentMessage.data.to?.map(t => `${t.name} <${t.email}>`).join(', ')}`); + + return sentMessage; + + } catch (error) { + if (error instanceof NylasApiError) { + console.error(`Error sending message: ${error.message}`); + console.error(`Error details: ${JSON.stringify(error, null, 2)}`); + } else if (error instanceof Error) { + console.error(`Unexpected error in demonstrateMessageSending: ${error.message}`); + } + return null; + } +} + +/** + * Demonstrates updating a message + */ +async function demonstrateMessageUpdate(messageId: string): Promise { + console.log('\n=== Demonstrating Message Update ==='); + + try { + console.log(`Updating message: ${messageId}`); + + const updateRequest: UpdateMessageRequest = { + starred: true, + unread: false, + metadata: { + example_type: 'sdk_demo', + feature_test: 'tracking_and_mime', + updated_at: new Date().toISOString(), + status: 'processed' + } + }; + + const updatedMessage = await nylas.messages.update({ + identifier: grantId, + messageId, + requestBody: updateRequest + }); + + console.log('Message updated successfully!'); + console.log(`- Message ID: ${updatedMessage.data.id}`); + console.log(`- Starred: ${updatedMessage.data.starred}`); + console.log(`- Unread: ${updatedMessage.data.unread}`); + console.log(`- Metadata: ${JSON.stringify(updatedMessage.data.metadata, null, 2)}`); + + } catch (error) { + if (error instanceof NylasApiError) { + console.error(`Error updating message: ${error.message}`); + } else if (error instanceof Error) { + console.error(`Unexpected error in demonstrateMessageUpdate: ${error.message}`); + } + throw error; + } +} + +/** + * Demonstrates scheduled messages + */ +async function demonstrateScheduledMessages(): Promise { + console.log('\n=== Demonstrating Scheduled Messages ==='); + + try { + // List scheduled messages + console.log('Listing scheduled messages...'); + const scheduledMessages = await nylas.messages.listScheduledMessages({ + identifier: grantId + }); + + // Handle case where schedules might be undefined or empty + const schedules = scheduledMessages.data.schedules || []; + console.log(`Found ${schedules.length} scheduled messages`); + + if (schedules.length === 0) { + console.log('No scheduled messages found. This is normal if you haven\'t scheduled any messages.'); + return; + } + + for (const schedule of schedules) { + console.log(`- Schedule ID: ${schedule.scheduleId}`); + console.log(` Status: ${schedule.status.code} - ${schedule.status.description}`); + if (schedule.closeTime) { + console.log(` Close Time: ${new Date(schedule.closeTime * 1000).toISOString()}`); + } + } + + // If there are scheduled messages, demonstrate finding one + const firstSchedule = schedules[0]; + console.log(`\nGetting details for scheduled message: ${firstSchedule.scheduleId}`); + + const scheduleDetails = await nylas.messages.findScheduledMessage({ + identifier: grantId, + scheduleId: firstSchedule.scheduleId.toString() + }); + + console.log('Scheduled message details:'); + console.log(`- Schedule ID: ${scheduleDetails.data.scheduleId}`); + console.log(`- Status: ${scheduleDetails.data.status.code} - ${scheduleDetails.data.status.description}`); + + } catch (error) { + if (error instanceof NylasApiError) { + console.error(`Error with scheduled messages: ${error.message}`); + } else if (error instanceof Error) { + console.error(`Unexpected error in demonstrateScheduledMessages: ${error.message}`); + } + throw error; + } +} + +/** + * Demonstrates message cleaning + */ +async function demonstrateMessageCleaning(): Promise { + console.log('\n=== Demonstrating Message Cleaning ==='); + + try { + // Get a few message IDs for cleaning + const messages = await nylas.messages.list({ + identifier: grantId, + queryParams: { + limit: 2 + } + }); + + if (messages.data.length === 0) { + console.log('No messages available for cleaning demonstration'); + return; + } + + const messageIds = messages.data.map(m => m.id); + console.log(`Cleaning ${messageIds.length} messages...`); + + const cleanRequest: CleanMessagesRequest = { + messageId: messageIds, + ignoreLinks: true, + ignoreImages: true, + ignoreTables: false, + imagesAsMarkdown: false, + removeConclusionPhrases: true + }; + + const cleanedMessages = await nylas.messages.cleanMessages({ + identifier: grantId, + requestBody: cleanRequest + }); + + console.log(`Successfully cleaned ${cleanedMessages.data.length} messages`); + for (const cleanedMessage of cleanedMessages.data) { + console.log(`- Message ID: ${cleanedMessage.id}`); + console.log(` Original body length: ${cleanedMessage.body?.length || 0} characters`); + console.log(` Cleaned conversation length: ${cleanedMessage.conversation?.length || 0} characters`); + if (cleanedMessage.conversation) { + const preview = cleanedMessage.conversation.substring(0, 100); + console.log(` Cleaned preview: ${preview}${cleanedMessage.conversation.length > 100 ? '...' : ''}`); + } + } + + } catch (error) { + if (error instanceof NylasApiError) { + console.error(`Error cleaning messages: ${error.message}`); + } else if (error instanceof Error) { + console.error(`Unexpected error in demonstrateMessageCleaning: ${error.message}`); + } + throw error; + } +} + +/** + * Demonstrates querying messages with various filters + */ +async function demonstrateMessageQuerying(): Promise { + console.log('\n=== Demonstrating Message Querying ==='); + + try { + // Query recent unread messages + console.log('\n--- Finding recent unread messages ---'); + const unreadMessages = await nylas.messages.list({ + identifier: grantId, + queryParams: { + unread: true, + limit: 5, + fields: MessageFields.INCLUDE_TRACKING_OPTIONS + } + }); + + console.log(`Found ${unreadMessages.data.length} unread messages`); + for (const message of unreadMessages.data) { + console.log(`- ${message.id}: ${message.subject || '(no subject)'}`); + console.log(` Date: ${new Date(message.date * 1000).toISOString()}`); + console.log(` From: ${message.from?.map(f => `${f.name} <${f.email}>`).join(', ') || 'Unknown'}`); + } + + // Query messages with attachments + console.log('\n--- Finding messages with attachments ---'); + const attachmentMessages = await nylas.messages.list({ + identifier: grantId, + queryParams: { + hasAttachment: true, + limit: 3 + } + }); + + console.log(`Found ${attachmentMessages.data.length} messages with attachments`); + for (const message of attachmentMessages.data) { + console.log(`- ${message.id}: ${message.subject || '(no subject)'}`); + console.log(` Attachments: ${message.attachments?.length || 0}`); + if (message.attachments && message.attachments.length > 0) { + message.attachments.forEach(att => { + console.log(` - ${att.filename} (${att.contentType}, ${att.size} bytes)`); + }); + } + } + + } catch (error) { + if (error instanceof NylasApiError) { + console.error(`Error querying messages: ${error.message}`); + } else if (error instanceof Error) { + console.error(`Unexpected error in demonstrateMessageQuerying: ${error.message}`); + } + throw error; + } +} + +/** + * Main function to run all message examples + */ +async function main(): Promise { + try { + console.log('=== Nylas Messages API Examples ==='); + console.log(`API Key: ${apiKey.substring(0, 8)}...`); + console.log(`Grant ID: ${grantId}`); + console.log(`API URI: ${process.env.NYLAS_API_URI || 'https://api.us.nylas.com'}`); + + // Run all demonstrations + await demonstrateMessageFields(); + await demonstrateRawMime(); + await demonstrateMessageQuerying(); + + // Send a message if TEST_EMAIL is provided + const sentMessage = await demonstrateMessageSending(); + + // If we sent a message, demonstrate updating it + if (sentMessage) { + await demonstrateMessageUpdate(sentMessage.data.id); + } + + await demonstrateScheduledMessages(); + await demonstrateMessageCleaning(); + + console.log('\n=== All message examples completed successfully! ==='); + + } catch (error) { + console.error('\n=== Error running message examples ==='); + if (error instanceof NylasApiError) { + console.error(`Nylas API Error: ${error.message}`); + console.error(`Status Code: ${error.statusCode}`); + console.error(`Request ID: ${error.requestId}`); + } else if (error instanceof Error) { + console.error(`Error: ${error.message}`); + console.error(`Stack: ${error.stack}`); + } else { + console.error('Unknown error:', error); + } + process.exit(1); + } +} + +// Run the main function +if (require.main === module) { + main(); +} \ No newline at end of file diff --git a/examples/package.json b/examples/package.json index 9798444e..5d8a147b 100644 --- a/examples/package.json +++ b/examples/package.json @@ -7,7 +7,8 @@ "start": "ts-node", "build": "tsc", "notetakers": "ts-node notetakers/notetaker.ts", - "calendars": "ts-node calendars/event_with_notetaker.ts" + "calendars": "ts-node calendars/event_with_notetaker.ts", + "messages": "ts-node messages/messages.ts" }, "dependencies": { "dotenv": "^16.0.0", From 8cd527230141d74380426e2728e285a316a7f970 Mon Sep 17 00:00:00 2001 From: Aaron de Mello Date: Thu, 29 May 2025 10:44:31 -0400 Subject: [PATCH 4/5] Ran prettier --- package-lock.json | 6 ++++-- tests/resources/messages.spec.ts | 6 +++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2e9ec229..a6ba3638 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9276,7 +9276,8 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true + "dev": true, + "requires": {} }, "ajv": { "version": "6.12.6", @@ -11687,7 +11688,8 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true + "dev": true, + "requires": {} }, "jest-regex-util": { "version": "29.4.3", diff --git a/tests/resources/messages.spec.ts b/tests/resources/messages.spec.ts index 1cf87ed1..96280fe8 100644 --- a/tests/resources/messages.spec.ts +++ b/tests/resources/messages.spec.ts @@ -2,7 +2,11 @@ import APIClient from '../../src/apiClient'; import { Messages } from '../../src/resources/messages'; import { createReadableStream, MockedFormData } from '../testUtils'; import { CreateAttachmentRequest } from '../../src/models/attachments'; -import { MessageFields, Message, MessageTrackingOptions } from '../../src/models/messages'; +import { + MessageFields, + Message, + MessageTrackingOptions, +} from '../../src/models/messages'; jest.mock('../src/apiClient'); // Mock the FormData constructor From 5c4d997845fce5af13b0ab0a0be0629785edfa1f Mon Sep 17 00:00:00 2001 From: Aaron de Mello Date: Thu, 29 May 2025 10:50:09 -0400 Subject: [PATCH 5/5] Clean up --- examples/messages/messages.ts | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/examples/messages/messages.ts b/examples/messages/messages.ts index 205022f6..04397911 100644 --- a/examples/messages/messages.ts +++ b/examples/messages/messages.ts @@ -1,20 +1,15 @@ import * as dotenv from 'dotenv'; -import * as path from 'path'; -import * as process from 'process'; -import Nylas from 'nylas'; -import { +import Nylas, { + CleanMessagesRequest, Message, MessageFields, - MessageTrackingOptions, - SendMessageRequest, - UpdateMessageRequest, - CleanMessagesRequest, - ScheduledMessage, - NylasResponse, - NylasListResponse, NylasApiError, - ListMessagesQueryParams + NylasResponse, + SendMessageRequest, + UpdateMessageRequest } from 'nylas'; +import * as path from 'path'; +import * as process from 'process'; // Load environment variables from .env file dotenv.config({ path: path.resolve(__dirname, '../.env') });