diff --git a/CHANGELOG.md b/CHANGELOG.md
index 17f8e737..5ef48e34 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,8 @@
# Changelog
+### Unreleased
+* Documented `tracking_options.domain_name` hash support for custom link and open tracking hostnames. See the [message tracking documentation](https://developer.nylas.com/docs/v3/email/message-tracking/)
+
### [6.8.1]
* Documented `tentative_as_busy` support in the `Calendars#get_free_busy` request body, consistent with other Nylas SDKs
diff --git a/examples/README.md b/examples/README.md
index 717964da..6a9b5f78 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -92,6 +92,103 @@ Before running any example, make sure to:
export NYLAS_TEST_EMAIL="test@example.com" # Email address to send test messages to
```
+#### Custom tracking hostnames
+
+Set `tracking_options.domain_name` to an active custom hostname owned by your organization. Enable
+link tracking, open tracking, or both in the same hash. If `domain_name` is omitted, tracked messages
+use the default Nylas tracking hostname. See the
+[message tracking documentation](https://developer.nylas.com/docs/v3/email/message-tracking/) for
+registration, validation, and failure behavior.
+
+##### Regular Send
+
+```ruby
+nylas.messages.send(
+ identifier: grant_id,
+ request_body: {
+ to: [{ email: "recipient@example.com" }],
+ subject: "Your update",
+ body: 'View update',
+ tracking_options: {
+ links: true,
+ opens: true,
+ domain_name: "links.example.com"
+ }
+ }
+)
+```
+
+##### Transactional Send
+
+The sender domain belongs in the Transactional Send route. The distinct `tracking_options.domain_name`
+value belongs in the request body and is used for recipient-visible links and open pixels.
+
+```ruby
+sender_domain = "sender.example.com"
+transactional_path = "/v3/domains/#{sender_domain}/messages/send"
+transactional_request_body = {
+ from: [{ email: "billing@sender.example.com" }],
+ to: [{ email: "recipient@example.com" }],
+ subject: "Your receipt",
+ body: 'View receipt',
+ tracking_options: {
+ links: true,
+ opens: true,
+ domain_name: "links.example.com"
+ }
+}
+
+# POST transactional_path with transactional_request_body.
+```
+
+##### Drafts
+
+```ruby
+draft, = nylas.drafts.create(
+ identifier: grant_id,
+ request_body: {
+ to: [{ email: "recipient@example.com" }],
+ subject: "Draft update",
+ body: 'View update',
+ tracking_options: {
+ links: true,
+ opens: true,
+ domain_name: "links.example.com"
+ }
+ }
+)
+
+nylas.drafts.update(
+ identifier: grant_id,
+ draft_id: draft[:id],
+ request_body: {
+ subject: "Updated draft subject"
+ # Omit tracking_options to preserve the draft's existing tracking settings.
+ }
+)
+```
+
+##### Scheduled Send
+
+Custom tracking hostnames are validated when the message is scheduled and revalidated before delivery.
+
+```ruby
+nylas.messages.send(
+ identifier: grant_id,
+ request_body: {
+ to: [{ email: "recipient@example.com" }],
+ subject: "Scheduled update",
+ body: 'View update',
+ send_at: Time.now.to_i + 3600,
+ tracking_options: {
+ links: true,
+ opens: true,
+ domain_name: "links.example.com"
+ }
+ }
+)
+```
+
### Notetaker
- `notetaker/notetaker_example.rb`: Shows basic Notetaker functionality, including:
- Inviting a Notetaker to a meeting
@@ -123,4 +220,4 @@ When adding new examples:
If you encounter any issues or have questions about these examples, please:
1. Check the [Nylas documentation](https://developer.nylas.com)
2. Visit our [GitHub repository](https://github.com/nylas/nylas-ruby)
-3. Contact [Nylas support](https://support.nylas.com)
\ No newline at end of file
+3. Contact [Nylas support](https://support.nylas.com)
diff --git a/lib/nylas/resources/drafts.rb b/lib/nylas/resources/drafts.rb
index 6dbf8fb2..df555d2b 100644
--- a/lib/nylas/resources/drafts.rb
+++ b/lib/nylas/resources/drafts.rb
@@ -39,6 +39,9 @@ def find(identifier:, draft_id:)
#
# @param identifier [String] Grant ID or email account in which to create the draft.
# @param request_body [Hash] The values to create the message with.
+ # Use `:tracking_options` with `:links` or `:opens` enabled to track the draft. To use a custom
+ # tracking hostname, set `:domain_name` inside `:tracking_options` to an active hostname owned
+ # by your organization.
# If you're attaching files, you must pass an array of [File] objects, or
# you can pass in base64 encoded strings if the total attachment size is less than 3mb.
# You can also use {FileUtils::attach_file_request_builder} to build each object attach.
@@ -61,6 +64,9 @@ def create(identifier:, request_body:)
# @param identifier [String] Grant ID or email account in which to update the draft.
# @param draft_id [String] The id of the draft to update.
# @param request_body [Hash] The values to create the message with.
+ # To replace the draft's custom tracking hostname, set `:tracking_options` with `:links` or
+ # `:opens` enabled and a `:domain_name`. Omit `:tracking_options` to preserve the draft's
+ # existing tracking settings.
# If you're attaching files, you must pass an array of [File] objects, or
# you can pass in base64 encoded strings if the total attachment size is less than 3mb.
# You can also use {FileUtils::attach_file_request_builder} to build each object attach.
diff --git a/lib/nylas/resources/messages.rb b/lib/nylas/resources/messages.rb
index 2a83c4ad..b6f63842 100644
--- a/lib/nylas/resources/messages.rb
+++ b/lib/nylas/resources/messages.rb
@@ -114,6 +114,11 @@ def clean_messages(identifier:, request_body:)
#
# @param identifier [String] Grant ID or email account from which to delete an object.
# @param request_body [Hash] The values to create the message with.
+ # Use `:tracking_options` with `:links` or `:opens` enabled to track a message. To use a custom
+ # tracking hostname, set `:domain_name` inside `:tracking_options` to an active hostname owned
+ # by your organization. Omit `:domain_name` to use the default Nylas tracking hostname.
+ # Set `:send_at` to a Unix timestamp to schedule the message; custom tracking hostnames are
+ # validated when the send is scheduled and revalidated before delivery.
# If you're attaching files, you must pass an array of [File] objects, or
# you can pass in base64 encoded strings if the total attachment size is less than 3mb.
# You can also use {FileUtils::attach_file_request_builder} to build each object attach.
diff --git a/spec/nylas/handler/http_client_spec.rb b/spec/nylas/handler/http_client_spec.rb
index c0e905aa..f4f77314 100644
--- a/spec/nylas/handler/http_client_spec.rb
+++ b/spec/nylas/handler/http_client_spec.rb
@@ -175,6 +175,26 @@ class TestHttpClient
)
end
+ it "serializes a custom tracking hostname using the documented JSON shape" do
+ payload = {
+ tracking_options: {
+ links: true,
+ opens: true,
+ domain_name: "links.example.com"
+ }
+ }
+ request = http_client.send(:build_request, method: :post, path: "https://test.api.nylas.com/foo",
+ payload: payload, api_key: "fake-key")
+
+ expect(JSON.parse(request[:payload])).to eq(
+ "tracking_options" => {
+ "links" => true,
+ "opens" => true,
+ "domain_name" => "links.example.com"
+ }
+ )
+ end
+
it "returns the correct request with a multipart flag (string key)" do
payload = { "multipart" => true }
request = http_client.send(:build_request, method: :post, path: "https://test.api.nylas.com/foo",
diff --git a/spec/nylas/resources/drafts_spec.rb b/spec/nylas/resources/drafts_spec.rb
index bc01b57f..402f2bd0 100644
--- a/spec/nylas/resources/drafts_spec.rb
+++ b/spec/nylas/resources/drafts_spec.rb
@@ -97,6 +97,29 @@
expect(draft_response).to eq(response)
end
+ it "forwards a custom tracking hostname in the request body" do
+ identifier = "abc-123-grant-id"
+ request_body = {
+ subject: "Tracked draft",
+ to: [{ email: "recipient@example.com" }],
+ body: 'View update',
+ tracking_options: {
+ links: true,
+ opens: true,
+ domain_name: "links.example.com"
+ }
+ }
+ path = "#{api_uri}/v3/grants/#{identifier}/drafts"
+
+ allow(drafts).to receive(:post)
+ .with(path: path, request_body: request_body)
+ .and_return(response)
+
+ draft_response = drafts.create(identifier: identifier, request_body: request_body)
+
+ expect(draft_response).to eq(response)
+ end
+
it "calls the post method with the correct parameters for small attachments" do
identifier = "abc-123-grant-id"
mock_file = instance_double("file")
@@ -181,6 +204,28 @@
expect(draft_response).to eq(response)
end
+ it "forwards a replacement custom tracking hostname in the request body" do
+ identifier = "abc-123-grant-id"
+ draft_id = "5d3qmne77v32r8l4phyuksl2x"
+ request_body = {
+ tracking_options: {
+ links: true,
+ opens: true,
+ domain_name: "replacement-links.example.com"
+ }
+ }
+ path = "#{api_uri}/v3/grants/#{identifier}/drafts/#{draft_id}"
+
+ allow(drafts).to receive(:put)
+ .with(path: path, request_body: request_body)
+ .and_return(response)
+
+ draft_response = drafts.update(identifier: identifier, draft_id: draft_id,
+ request_body: request_body)
+
+ expect(draft_response).to eq(response)
+ end
+
it "calls the put method with the correct parameters and attachments" do
identifier = "abc-123-grant-id"
draft_id = "5d3qmne77v32r8l4phyuksl2x"
diff --git a/spec/nylas/resources/messages_spec.rb b/spec/nylas/resources/messages_spec.rb
index 32af54dc..1284781e 100644
--- a/spec/nylas/resources/messages_spec.rb
+++ b/spec/nylas/resources/messages_spec.rb
@@ -277,6 +277,53 @@
expect(message_response).to eq(response)
end
+ it "forwards a custom tracking hostname in the request body" do
+ identifier = "abc-123-grant-id"
+ request_body = {
+ subject: "Tracked message",
+ to: [{ email: "recipient@example.com" }],
+ body: 'View update',
+ tracking_options: {
+ links: true,
+ opens: true,
+ domain_name: "links.example.com"
+ }
+ }
+ path = "#{api_uri}/v3/grants/#{identifier}/messages/send"
+
+ allow(messages).to receive(:post)
+ .with(path: path, request_body: request_body)
+ .and_return(response)
+
+ message_response = messages.send(identifier: identifier, request_body: request_body)
+
+ expect(message_response).to eq(response)
+ end
+
+ it "forwards a custom tracking hostname for a scheduled send" do
+ identifier = "abc-123-grant-id"
+ request_body = {
+ subject: "Scheduled tracked message",
+ to: [{ email: "recipient@example.com" }],
+ body: 'View update',
+ send_at: 1_893_456_000,
+ tracking_options: {
+ links: true,
+ opens: true,
+ domain_name: "links.example.com"
+ }
+ }
+ path = "#{api_uri}/v3/grants/#{identifier}/messages/send"
+
+ allow(messages).to receive(:post)
+ .with(path: path, request_body: request_body)
+ .and_return(response)
+
+ message_response = messages.send(identifier: identifier, request_body: request_body)
+
+ expect(message_response).to eq(response)
+ end
+
it "calls the post method with the correct parameters and attachments" do
identifier = "abc-123-grant-id"
mock_file = instance_double("file")