Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add metadata field to SendMessageRequest and Message model #399

Merged
merged 8 commits into from
Dec 20, 2024
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ nylas-python Changelog
======================

Unreleased
--------------
----------------
* Add support for Scheduler APIs
* Fixed attachment download response handling
* Add metadata field support for drafts and messages through CreateDraftRequest model

v6.4.0
----------------
Expand Down
67 changes: 67 additions & 0 deletions examples/metadata_field_demo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Metadata Field Example

This example demonstrates how to use metadata fields when creating drafts and sending messages using the Nylas Python SDK.

## Features

- Create drafts with custom metadata fields
- Send messages with custom metadata fields
- Error handling and environment variable configuration
- Clear output and status messages

## Prerequisites

1. A Nylas account with API access
2. Python 3.x installed
3. Local installation of the Nylas Python SDK (this repository)

## Setup

1. Install the SDK in development mode from the repository root:
```bash
cd /path/to/nylas-python
pip install -e .
```

2. Set your environment variables:
```bash
export NYLAS_API_KEY="your_api_key"
export NYLAS_GRANT_ID="your_grant_id"
export TEST_EMAIL="[email protected]" # Optional
```

3. Run the example from the repository root:
```bash
python examples/metadata_field_demo/metadata_example.py
```

## Example Output

```
Demonstrating Metadata Field Usage
=================================

1. Creating draft with metadata...
✓ Created draft with ID: draft-abc123
Request ID: req-xyz789

2. Sending message with metadata...
✓ Sent message with ID: msg-def456
Request ID: req-uvw321

Example completed successfully!
```

## Error Handling

The example includes proper error handling for:
- Missing environment variables
- API authentication errors
- Draft creation failures
- Message sending failures

## Documentation

For more information about the Nylas Python SDK and its features, visit:
- [Nylas Python SDK Documentation](https://developer.nylas.com/docs/sdks/python/)
- [Nylas API Reference](https://developer.nylas.com/docs/api/)
137 changes: 137 additions & 0 deletions examples/metadata_field_demo/metadata_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
#!/usr/bin/env python3
"""
Nylas SDK Example: Using Metadata Fields with Drafts and Messages

This example demonstrates how to use metadata fields when creating drafts
and sending messages using the Nylas Python SDK.

Required Environment Variables:
NYLAS_API_KEY: Your Nylas API key
NYLAS_GRANT_ID: Your Nylas grant ID
TEST_EMAIL: Email address for sending test messages (optional)

Usage:
First, install the SDK in development mode:
cd /path/to/nylas-python
pip install -e .

Then set environment variables and run:
export NYLAS_API_KEY="your_api_key"
export NYLAS_GRANT_ID="your_grant_id"
export TEST_EMAIL="[email protected]"
python examples/metadata_field_demo/metadata_example.py
"""

import os
import sys
from typing import Dict, Any, Optional

# Import from local nylas package
from nylas import Client
from nylas.models.errors import NylasApiError


def get_env_or_exit(var_name: str, required: bool = True) -> Optional[str]:
"""Get an environment variable or exit if required and not found."""
value = os.getenv(var_name)
if required and not value:
print(f"Error: {var_name} environment variable is required")
sys.exit(1)
return value


def create_draft_with_metadata(
client: Client, grant_id: str, metadata: Dict[str, Any], recipient: str
) -> str:
"""Create a draft message with metadata fields."""
try:
draft_request = {
"subject": "Test Draft with Metadata",
"to": [{"email": recipient}],
"body": "This is a test draft with metadata fields.",
"metadata": metadata
}

draft, request_id = client.drafts.create(
identifier=grant_id,
request_body=draft_request
)
print(f"✓ Created draft with ID: {draft.id}")
print(f" Request ID: {request_id}")
return draft.id
except NylasApiError as e:
print(f"✗ Failed to create draft: {e}")
sys.exit(1)


def send_message_with_metadata(
client: Client, grant_id: str, metadata: Dict[str, Any], recipient: str
) -> str:
"""Send a message directly with metadata fields."""
try:
message_request = {
"subject": "Test Message with Metadata",
"to": [{"email": recipient}],
"body": "This is a test message with metadata fields.",
"metadata": metadata
}

message, request_id = client.messages.send(
identifier=grant_id,
request_body=message_request
)
print(f"✓ Sent message with ID: {message.id}")
print(f" Request ID: {request_id}")

return message.id
except NylasApiError as e:
print(f"✗ Failed to send message: {e}")
sys.exit(1)


def main():
"""Main function demonstrating metadata field usage."""
# Get required environment variables
api_key = get_env_or_exit("NYLAS_API_KEY")
grant_id = get_env_or_exit("NYLAS_GRANT_ID")
recipient = get_env_or_exit("TEST_EMAIL", required=False) or "[email protected]"

# Initialize Nylas client
client = Client(
api_key=api_key,
)

# Example metadata
metadata = {
"campaign_id": "example-123",
"user_id": "user-456",
"custom_field": "test-value"
}

print("\nDemonstrating Metadata Field Usage")
print("=================================")

# Create a draft with metadata
print("\n1. Creating draft with metadata...")
draft_id = create_draft_with_metadata(client, grant_id, metadata, recipient)

# Send a message with metadata
print("\n2. Sending message with metadata...")
message_id = send_message_with_metadata(client, grant_id, metadata, recipient)

print("\nExample completed successfully!")

# Get the draft and message to demonstrate metadata retrieval
draft = client.drafts.find(identifier=grant_id, draft_id=draft_id)
message = client.messages.find(identifier=grant_id, message_id=message_id)

print("\nRetrieved Draft Metadata:")
print("-------------------------")
print(draft.data)

print("\nRetrieved Message Metadata:")
print("---------------------------")
print(message.data)

if __name__ == "__main__":
main()
4 changes: 3 additions & 1 deletion nylas/models/drafts.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from dataclasses import dataclass
from typing import List, get_type_hints
from typing import List, Dict, Any, get_type_hints

from dataclasses_json import dataclass_json
from typing_extensions import TypedDict, NotRequired
Expand Down Expand Up @@ -87,6 +87,7 @@ class CreateDraftRequest(TypedDict):
reply_to_message_id: The ID of the message that you are replying to.
tracking_options: Options for tracking opens, links, and thread replies.
custom_headers: Custom headers to add to the message.
metadata: A dictionary of key-value pairs storing additional data.
"""

body: NotRequired[str]
Expand All @@ -101,6 +102,7 @@ class CreateDraftRequest(TypedDict):
reply_to_message_id: NotRequired[str]
tracking_options: NotRequired[TrackingOptions]
custom_headers: NotRequired[List[CustomHeader]]
metadata: NotRequired[Dict[str, Any]]


UpdateDraftRequest = CreateDraftRequest
Expand Down
1 change: 1 addition & 0 deletions nylas/models/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ class Message:
date: Optional[int] = None
schedule_id: Optional[str] = None
send_at: Optional[int] = None
metadata: Optional[Dict[str, Any]] = None


# Need to use Functional typed dicts because "from" and "in" are Python
Expand Down
41 changes: 41 additions & 0 deletions tests/resources/test_drafts.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from nylas.models.drafts import Draft
from nylas.resources.drafts import Drafts
from nylas.resources.messages import Messages


class TestDraft:
Expand Down Expand Up @@ -143,6 +144,27 @@ def test_create_draft(self, http_client_response):
overrides=None,
)

def test_create_draft_with_metadata(self, http_client_response):
drafts = Drafts(http_client_response)
request_body = {
"subject": "Hello from Nylas!",
"to": [{"name": "Jon Snow", "email": "[email protected]"}],
"cc": [{"name": "Arya Stark", "email": "[email protected]"}],
"body": "This is the body of my draft message.",
"metadata": {"custom_field": "value", "another_field": 123}
}

drafts.create(identifier="abc-123", request_body=request_body)

http_client_response._execute.assert_called_once_with(
"POST",
"/v3/grants/abc-123/drafts",
None,
None,
request_body,
overrides=None,
)

def test_create_draft_small_attachment(self, http_client_response):
drafts = Drafts(http_client_response)
request_body = {
Expand Down Expand Up @@ -349,6 +371,25 @@ def test_send_draft(self, http_client_response):
method="POST", path="/v3/grants/abc-123/drafts/draft-123", overrides=None
)

def test_send_message_with_metadata(self, http_client_response):
messages = Messages(http_client_response)
request_body = {
"subject": "Hello from Nylas!",
"to": [{"name": "Jon Snow", "email": "[email protected]"}],
"body": "This is the body of my message.",
"metadata": {"custom_field": "value", "another_field": 123}
}

messages.send(identifier="abc-123", request_body=request_body)

http_client_response._execute.assert_called_once_with(
method="POST",
path="/v3/grants/abc-123/messages/send",
request_body=request_body,
data=None,
overrides=None,
)

def test_send_draft_encoded_id(self, http_client_response):
drafts = Drafts(http_client_response)

Expand Down
Loading
Loading