diff --git a/CHANGELOG.md b/CHANGELOG.md
index ac005d6f..5d1405d3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+### Added
+- Add optional `trackingOptions.domainName` support for custom link and open tracking hostnames
+
### Changed
- Clarify that event `default` visibility is Google-only
diff --git a/examples/messages/ENVIRONMENT.md b/examples/messages/ENVIRONMENT.md
index afd6f8ef..0d5834c1 100644
--- a/examples/messages/ENVIRONMENT.md
+++ b/examples/messages/ENVIRONMENT.md
@@ -14,6 +14,9 @@ NYLAS_API_URI=https://api.us.nylas.com
# For testing message sending (optional)
TEST_EMAIL=your-test-email@example.com
+
+# Active custom hostname owned by your organization (optional)
+NYLAS_TRACKING_DOMAIN=links.example.com
```
## Getting Your API Key and Grant ID
@@ -25,6 +28,8 @@ TEST_EMAIL=your-test-email@example.com
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.
+Set `NYLAS_TRACKING_DOMAIN` to an active custom hostname registered to your organization to use it for link and open tracking. If it is omitted, tracked messages use the default Nylas tracking hostname.
+
## Running the Example
Once your environment variables are set:
@@ -42,4 +47,4 @@ npm run messages
- **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
+- **Message Cleaning**: Using the clean messages API
diff --git a/examples/messages/README.md b/examples/messages/README.md
index 3dde23cf..8c9403d9 100644
--- a/examples/messages/README.md
+++ b/examples/messages/README.md
@@ -21,6 +21,99 @@ The file is structured with:
### Basic Messages (`messages.ts`)
Shows basic message operations including reading, sending, and drafting messages.
+### Custom tracking hostnames
+
+Set `trackingOptions.domainName` to an active custom hostname owned by your
+organization. At least one of `links` or `opens` must be enabled. Omit
+`domainName` to keep using the default Nylas tracking hostname.
+
+#### Regular Send
+
+```ts
+await nylas.messages.send({
+ identifier: grantId,
+ requestBody: {
+ to: [{ email: 'recipient@example.com' }],
+ subject: 'Your update',
+ body: 'View update',
+ trackingOptions: {
+ links: true,
+ opens: true,
+ domainName: 'links.example.com',
+ },
+ },
+});
+```
+
+#### Transactional Send
+
+The top-level `domainName` is the verified sender domain used by the route. The
+nested `trackingOptions.domainName` is the tracking hostname used in recipient-
+visible links and open pixels.
+
+```ts
+await nylas.transactionalSend.send({
+ domainName: 'sender.example.com',
+ requestBody: {
+ from: [{ email: 'billing@sender.example.com' }],
+ to: [{ email: 'recipient@example.com' }],
+ subject: 'Your receipt',
+ body: 'View receipt',
+ trackingOptions: {
+ links: true,
+ opens: true,
+ domainName: 'links.example.com',
+ },
+ },
+});
+```
+
+#### Drafts
+
+```ts
+const draft = await nylas.drafts.create({
+ identifier: grantId,
+ requestBody: {
+ to: [{ email: 'recipient@example.com' }],
+ subject: 'Draft update',
+ body: 'View update',
+ trackingOptions: {
+ links: true,
+ opens: true,
+ domainName: 'links.example.com',
+ },
+ },
+});
+
+await nylas.drafts.update({
+ identifier: grantId,
+ draftId: draft.data.id,
+ requestBody: {
+ subject: 'Updated draft subject',
+ // Omit trackingOptions to preserve the draft's existing tracking settings.
+ },
+});
+```
+
+#### Scheduled Send
+
+```ts
+await nylas.messages.send({
+ identifier: grantId,
+ requestBody: {
+ to: [{ email: 'recipient@example.com' }],
+ subject: 'Scheduled update',
+ body: 'View update',
+ sendAt: Math.floor(Date.now() / 1000) + 3600,
+ trackingOptions: {
+ links: true,
+ opens: true,
+ domainName: 'links.example.com',
+ },
+ },
+});
+```
+
### Accessing Rate Limit Headers
All SDK responses now expose a non-enumerable `rawHeaders` with dashed lowercase keys so you can read rate limit information:
@@ -181,4 +274,4 @@ npm run send-attachments status # Check file availability
**TypeScript import errors:**
- Ensure you've run `npm install` in the examples directory
-- The utils are properly exported from the attachment-file-manager module
\ No newline at end of file
+- The utils are properly exported from the attachment-file-manager module
diff --git a/examples/messages/messages.ts b/examples/messages/messages.ts
index f1f6c946..a64373ee 100644
--- a/examples/messages/messages.ts
+++ b/examples/messages/messages.ts
@@ -17,6 +17,8 @@ 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 || '';
+const trackingDomain: string | undefined =
+ process.env.NYLAS_TRACKING_DOMAIN || undefined;
if (!apiKey) {
throw new Error('NYLAS_API_KEY environment variable is not set');
@@ -244,10 +246,17 @@ async function demonstrateMessageSending(): Promise | nul