Skip to content

Commit 148af5c

Browse files
authored
feat: add support for include_tracking_options and raw_mime message fields (#419)
- Add new Fields enum values: include_tracking_options, raw_mime - Add TrackingOptions model with opens, thread_replies, links, label fields - Add tracking_options and raw_mime fields to Message model - Update ListMessagesQueryParams and FindMessageQueryParams documentation - Add 8 comprehensive tests for new functionality with backwards compatibility - Add complete example in examples/message_fields_demo/ with documentation - Update CHANGELOG.md This implementation supports the latest Nylas API message fields while maintaining full backwards compatibility. All existing functionality continues to work unchanged.
1 parent 601a4be commit 148af5c

5 files changed

Lines changed: 574 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
nylas-python Changelog
22
======================
33

4+
Unreleased
5+
----------------
6+
* Added support for new message fields query parameter values: `include_tracking_options` and `raw_mime`
7+
* Added `tracking_options` field to Message model for message tracking settings
8+
* Added `raw_mime` field to Message model for Base64url-encoded message data
9+
* Added TrackingOptions model for message tracking configuration
10+
* Maintained backwards compatibility for existing message functionality
11+
412
v6.9.0
513
----------------
614
* Added support for for tentative_as_busy parameter to the availability request
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Message Fields Demo
2+
3+
This example demonstrates the usage of the new message `fields` query parameter values (`include_tracking_options` and `raw_mime`) introduced in the Nylas API. These fields allow you to access tracking information and raw MIME data for messages.
4+
5+
## Features Demonstrated
6+
7+
1. **include_tracking_options Field**: Shows how to fetch messages with their tracking options (opens, thread_replies, links, and label).
8+
2. **raw_mime Field**: Demonstrates how to retrieve the raw MIME content of messages as Base64url-encoded data.
9+
3. **Backwards Compatibility**: Shows that existing code continues to work as expected without specifying the new fields.
10+
4. **TrackingOptions Model**: Demonstrates working with the new TrackingOptions dataclass for serialization and deserialization.
11+
12+
## API Fields Overview
13+
14+
### include_tracking_options
15+
When using `fields=include_tracking_options`, the API returns messages with their tracking settings:
16+
- `opens`: Boolean indicating if message open tracking is enabled
17+
- `thread_replies`: Boolean indicating if thread replied tracking is enabled
18+
- `links`: Boolean indicating if link clicked tracking is enabled
19+
- `label`: String label describing the message tracking purpose
20+
21+
### raw_mime
22+
When using `fields=raw_mime`, the API returns only essential fields plus the raw MIME content:
23+
- `grant_id`: The grant identifier
24+
- `object`: The object type ("message")
25+
- `id`: The message identifier
26+
- `raw_mime`: Base64url-encoded string containing the complete message data
27+
28+
## Setup
29+
30+
1. Install the SDK in development mode from the repository root:
31+
```bash
32+
cd /path/to/nylas-python
33+
pip install -e .
34+
```
35+
36+
2. Set your environment variables:
37+
```bash
38+
export NYLAS_API_KEY="your_api_key"
39+
export NYLAS_GRANT_ID="your_grant_id"
40+
```
41+
42+
3. Run the example from the repository root:
43+
```bash
44+
python examples/message_fields_demo/message_fields_example.py
45+
```
46+
47+
## Example Output
48+
49+
```
50+
Demonstrating Message Fields Usage
51+
=================================
52+
53+
=== Standard Message Fetching (Backwards Compatible) ===
54+
Fetching messages with standard fields...
55+
✓ Found 2 messages with standard payload
56+
57+
=== Include Tracking Options ===
58+
Fetching messages with tracking options...
59+
✓ Found 2 messages with tracking data
60+
Message tracking: opens=True, links=False, label="Campaign A"
61+
62+
=== Raw MIME Content ===
63+
Fetching messages with raw MIME data...
64+
✓ Found 2 messages with raw MIME content
65+
Raw MIME length: 1245 characters
66+
67+
=== TrackingOptions Model Demo ===
68+
Creating and serializing TrackingOptions...
69+
✓ TrackingOptions serialization works correctly
70+
71+
Example completed successfully!
72+
```
73+
74+
## Use Cases
75+
76+
### Tracking Options
77+
- **Email Campaign Analytics**: Monitor open rates, link clicks, and thread engagement
78+
- **Marketing Automation**: Track customer engagement with promotional emails
79+
- **CRM Integration**: Feed tracking data into customer relationship management systems
80+
81+
### Raw MIME
82+
- **Email Archival**: Store complete email data including headers and formatting
83+
- **Email Migration**: Transfer emails between systems with full fidelity
84+
- **Security Analysis**: Examine email headers and structure for security purposes
85+
- **Custom Email Parsing**: Build custom email processing pipelines
86+
87+
## Error Handling
88+
89+
The example includes proper error handling for:
90+
- Missing environment variables
91+
- API authentication errors
92+
- Empty message collections
93+
- Invalid field parameters
94+
95+
## Documentation
96+
97+
For more information about the Nylas Python SDK and message fields, visit:
98+
- [Nylas Python SDK Documentation](https://developer.nylas.com/docs/sdks/python/)
99+
- [Nylas API Messages Reference](https://developer.nylas.com/docs/api/v3/ecc/#tag--Messages)
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Nylas SDK Example: Using Message Fields (include_tracking_options and raw_mime)
4+
5+
This example demonstrates how to use the new 'fields' query parameter values
6+
'include_tracking_options' and 'raw_mime' to access message tracking data and raw MIME content.
7+
8+
Required Environment Variables:
9+
NYLAS_API_KEY: Your Nylas API key
10+
NYLAS_GRANT_ID: Your Nylas grant ID
11+
12+
Usage:
13+
First, install the SDK in development mode:
14+
cd /path/to/nylas-python
15+
pip install -e .
16+
17+
Then set environment variables and run:
18+
export NYLAS_API_KEY="your_api_key"
19+
export NYLAS_GRANT_ID="your_grant_id"
20+
python examples/message_fields_demo/message_fields_example.py
21+
"""
22+
23+
import os
24+
import sys
25+
import json
26+
import base64
27+
from nylas import Client
28+
from nylas.models.messages import TrackingOptions
29+
30+
31+
def get_env_or_exit(var_name: str) -> str:
32+
"""Get an environment variable or exit if not found."""
33+
value = os.getenv(var_name)
34+
if not value:
35+
print(f"Error: {var_name} environment variable is required")
36+
sys.exit(1)
37+
return value
38+
39+
40+
def print_separator(title: str) -> None:
41+
"""Print a formatted section separator."""
42+
print(f"\n=== {title} ===")
43+
44+
45+
def demonstrate_standard_fields(client: Client, grant_id: str) -> None:
46+
"""Demonstrate backwards compatible message fetching (standard fields)."""
47+
print_separator("Standard Message Fetching (Backwards Compatible)")
48+
49+
try:
50+
print("Fetching messages with standard fields...")
51+
messages = client.messages.list(
52+
identifier=grant_id,
53+
query_params={"limit": 2}
54+
)
55+
56+
if not messages.data:
57+
print("⚠️ No messages found in this account")
58+
return
59+
60+
print(f"✓ Found {len(messages.data)} messages with standard payload")
61+
62+
for i, message in enumerate(messages.data, 1):
63+
print(f"\nMessage {i}:")
64+
print(f" ID: {message.id}")
65+
print(f" Subject: {message.subject or 'No subject'}")
66+
print(f" From: {message.from_[0].email if message.from_ else 'Unknown'}")
67+
print(f" Tracking Options: {message.tracking_options}") # Should be None
68+
print(f" Raw MIME: {message.raw_mime}") # Should be None
69+
70+
except Exception as e:
71+
print(f"❌ Error fetching standard messages: {e}")
72+
73+
74+
def demonstrate_tracking_options(client: Client, grant_id: str) -> None:
75+
"""Demonstrate fetching messages with tracking options."""
76+
print_separator("Include Tracking Options")
77+
78+
try:
79+
print("Fetching messages with tracking options...")
80+
messages = client.messages.list(
81+
identifier=grant_id,
82+
query_params={
83+
"limit": 2,
84+
"fields": "include_tracking_options"
85+
}
86+
)
87+
88+
if not messages.data:
89+
print("⚠️ No messages found in this account")
90+
return
91+
92+
print(f"✓ Found {len(messages.data)} messages with tracking data")
93+
94+
for i, message in enumerate(messages.data, 1):
95+
print(f"\nMessage {i}:")
96+
print(f" ID: {message.id}")
97+
print(f" Subject: {message.subject or 'No subject'}")
98+
99+
if message.tracking_options:
100+
print(f" Tracking Options:")
101+
print(f" Opens: {message.tracking_options.opens}")
102+
print(f" Thread Replies: {message.tracking_options.thread_replies}")
103+
print(f" Links: {message.tracking_options.links}")
104+
print(f" Label: {message.tracking_options.label}")
105+
else:
106+
print(" Tracking Options: None (tracking not enabled for this message)")
107+
108+
except Exception as e:
109+
print(f"❌ Error fetching messages with tracking options: {e}")
110+
111+
112+
def demonstrate_raw_mime(client: Client, grant_id: str) -> None:
113+
"""Demonstrate fetching messages with raw MIME content."""
114+
print_separator("Raw MIME Content")
115+
116+
try:
117+
print("Fetching messages with raw MIME data...")
118+
messages = client.messages.list(
119+
identifier=grant_id,
120+
query_params={
121+
"limit": 2,
122+
"fields": "raw_mime"
123+
}
124+
)
125+
126+
if not messages.data:
127+
print("⚠️ No messages found in this account")
128+
return
129+
130+
print(f"✓ Found {len(messages.data)} messages with raw MIME content")
131+
132+
for i, message in enumerate(messages.data, 1):
133+
print(f"\nMessage {i}:")
134+
print(f" ID: {message.id}")
135+
print(f" Grant ID: {message.grant_id}")
136+
print(f" Object: {message.object}")
137+
138+
if message.raw_mime:
139+
print(f" Raw MIME length: {len(message.raw_mime)} characters")
140+
141+
# Decode a small portion to show it's real MIME data
142+
try:
143+
# Show first 200 characters of decoded MIME
144+
decoded_sample = base64.urlsafe_b64decode(
145+
message.raw_mime + '=' * (4 - len(message.raw_mime) % 4)
146+
).decode('utf-8', errors='ignore')[:200]
147+
print(f" MIME preview: {decoded_sample}...")
148+
except Exception as decode_error:
149+
print(f" MIME preview: Unable to decode preview ({decode_error})")
150+
else:
151+
print(" Raw MIME: None")
152+
153+
# Note: In raw_mime mode, most other fields should be None
154+
print(f" Subject (should be None): {message.subject}")
155+
print(f" Body (should be None): {getattr(message, 'body', 'N/A')}")
156+
157+
except Exception as e:
158+
print(f"❌ Error fetching messages with raw MIME: {e}")
159+
160+
161+
def demonstrate_single_message_fields(client: Client, grant_id: str) -> None:
162+
"""Demonstrate fetching a single message with different field options."""
163+
print_separator("Single Message with Different Fields")
164+
165+
try:
166+
# First get a message ID
167+
print("Finding a message to demonstrate single message field options...")
168+
messages = client.messages.list(
169+
identifier=grant_id,
170+
query_params={"limit": 1}
171+
)
172+
173+
if not messages.data:
174+
print("⚠️ No messages found for single message demo")
175+
return
176+
177+
message_id = messages.data[0].id
178+
print(f"Using message ID: {message_id}")
179+
180+
# Fetch with tracking options
181+
print("\nFetching single message with tracking options...")
182+
message = client.messages.find(
183+
identifier=grant_id,
184+
message_id=message_id,
185+
query_params={"fields": "include_tracking_options"}
186+
)
187+
188+
print(f"✓ Message fetched with tracking: {message.tracking_options is not None}")
189+
190+
# Fetch with raw MIME
191+
print("\nFetching single message with raw MIME...")
192+
message = client.messages.find(
193+
identifier=grant_id,
194+
message_id=message_id,
195+
query_params={"fields": "raw_mime"}
196+
)
197+
198+
print(f"✓ Message fetched with raw MIME: {message.raw_mime is not None}")
199+
if message.raw_mime:
200+
print(f" Raw MIME size: {len(message.raw_mime)} characters")
201+
202+
except Exception as e:
203+
print(f"❌ Error in single message demo: {e}")
204+
205+
206+
def demonstrate_tracking_options_model() -> None:
207+
"""Demonstrate working with the TrackingOptions model directly."""
208+
print_separator("TrackingOptions Model Demo")
209+
210+
try:
211+
print("Creating TrackingOptions object...")
212+
213+
# Create a TrackingOptions instance
214+
tracking = TrackingOptions(
215+
opens=True,
216+
thread_replies=False,
217+
links=True,
218+
label="Marketing Campaign Demo"
219+
)
220+
221+
print("✓ TrackingOptions created:")
222+
print(f" Opens: {tracking.opens}")
223+
print(f" Thread Replies: {tracking.thread_replies}")
224+
print(f" Links: {tracking.links}")
225+
print(f" Label: {tracking.label}")
226+
227+
# Demonstrate serialization
228+
print("\nSerializing to dict...")
229+
tracking_dict = tracking.to_dict()
230+
print(f"✓ Serialized: {json.dumps(tracking_dict, indent=2)}")
231+
232+
# Demonstrate deserialization
233+
print("\nDeserializing from dict...")
234+
restored_tracking = TrackingOptions.from_dict(tracking_dict)
235+
print(f"✓ Deserialized: opens={restored_tracking.opens}, label='{restored_tracking.label}'")
236+
237+
# Demonstrate JSON serialization
238+
print("\nJSON serialization...")
239+
tracking_json = tracking.to_json()
240+
print(f"✓ JSON: {tracking_json}")
241+
242+
restored_from_json = TrackingOptions.from_json(tracking_json)
243+
print(f"✓ From JSON: {restored_from_json.to_dict()}")
244+
245+
except Exception as e:
246+
print(f"❌ Error in TrackingOptions demo: {e}")
247+
248+
249+
def main():
250+
"""Main function demonstrating message fields usage."""
251+
# Get required environment variables
252+
api_key = get_env_or_exit("NYLAS_API_KEY")
253+
grant_id = get_env_or_exit("NYLAS_GRANT_ID")
254+
255+
# Initialize Nylas client
256+
client = Client(api_key=api_key)
257+
258+
print("Demonstrating Message Fields Usage")
259+
print("=================================")
260+
print("This shows the new 'include_tracking_options' and 'raw_mime' field options")
261+
262+
# Demonstrate different field options
263+
demonstrate_standard_fields(client, grant_id)
264+
demonstrate_tracking_options(client, grant_id)
265+
demonstrate_raw_mime(client, grant_id)
266+
demonstrate_single_message_fields(client, grant_id)
267+
demonstrate_tracking_options_model()
268+
269+
print("\n" + "="*50)
270+
print("Example completed successfully!")
271+
print("="*50)
272+
273+
274+
if __name__ == "__main__":
275+
main()

0 commit comments

Comments
 (0)