Skip to content

Commit e5d6e07

Browse files
authored
fix(encoding): ensure ASCII characters are not escaped in JSON payloads (#446)
1 parent e2359da commit e5d6e07

File tree

5 files changed

+192
-40
lines changed

5 files changed

+192
-40
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Unreleased
55
----------
66
* Added `message.deleted` to the Webhook enum, appended tests
77
* Fixed Participant.email not being optional, Microsoft events can now be represented
8+
* Clarified UTF-8 encoding behavior: ASCII characters are preserved as-is (not escaped) while non-ASCII characters are preserved as UTF-8 in JSON payloads
89
* Added support for metadata_pair query params to the messages and drafts list endpoints
910

1011
v6.13.1

examples/send_email_demo/README.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Send Email Example
2+
3+
This example demonstrates how to send an email with special characters (accented letters) in the subject line using the Nylas Python SDK.
4+
5+
## Overview
6+
7+
The example sends an email with the subject **"De l'idée à la post-prod, sans friction"** to demonstrate proper handling of UTF-8 characters in email subjects.
8+
9+
## Prerequisites
10+
11+
- Python 3.8 or higher
12+
- Nylas Python SDK installed
13+
- Nylas API key
14+
- Nylas grant ID
15+
- Email address for testing
16+
17+
## Setup
18+
19+
1. Install the SDK in development mode:
20+
```bash
21+
cd /path/to/nylas-python
22+
pip install -e .
23+
```
24+
25+
2. Set the required environment variables:
26+
```bash
27+
export NYLAS_API_KEY="your_api_key"
28+
export NYLAS_GRANT_ID="your_grant_id"
29+
export RECIPIENT_EMAIL="recipient@example.com"
30+
```
31+
32+
## Running the Example
33+
34+
```bash
35+
python examples/send_email_demo/send_email_example.py
36+
```
37+
38+
## What This Example Demonstrates
39+
40+
- Sending an email with special characters (accented letters) in the subject
41+
- Proper UTF-8 encoding of email subjects
42+
- Using the `messages.send()` method to send emails directly
43+
44+
## Expected Output
45+
46+
```
47+
============================================================
48+
Nylas SDK: Send Email with Special Characters Example
49+
============================================================
50+
51+
This example sends an email with the subject:
52+
"De l'idée à la post-prod, sans friction"
53+
54+
Grant ID: your_grant_id
55+
Recipient: recipient@example.com
56+
57+
Sending email...
58+
To: recipient@example.com
59+
Subject: De l'idée à la post-prod, sans friction
60+
61+
✓ Email sent successfully!
62+
Message ID: message-id-here
63+
Subject: De l'idée à la post-prod, sans friction
64+
65+
✅ Special characters in subject are correctly preserved
66+
67+
============================================================
68+
Example completed successfully! ✅
69+
============================================================
70+
```
71+
72+
## Notes
73+
74+
- The SDK properly handles UTF-8 characters in email subjects and bodies
75+
- Special characters like é, à, and other accented letters are preserved correctly
76+
- The email will be delivered with the subject exactly as specified
77+
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Nylas SDK Example: Send Email with Special Characters
4+
5+
This example demonstrates how to send an email with special characters
6+
(accented letters) in the subject line using the Nylas Python SDK.
7+
8+
The example sends an email with the subject "De l'idée à la post-prod, sans friction"
9+
to demonstrate proper handling of UTF-8 characters in email subjects.
10+
11+
Required Environment Variables:
12+
NYLAS_API_KEY: Your Nylas API key
13+
NYLAS_GRANT_ID: Your Nylas grant ID
14+
RECIPIENT_EMAIL: Email address to send the message to
15+
16+
Usage:
17+
First, install the SDK in development mode:
18+
cd /path/to/nylas-python
19+
pip install -e .
20+
21+
Then set environment variables and run:
22+
export NYLAS_API_KEY="your_api_key"
23+
export NYLAS_GRANT_ID="your_grant_id"
24+
export RECIPIENT_EMAIL="recipient@example.com"
25+
python examples/send_email_demo/send_email_example.py
26+
"""
27+
28+
import os
29+
import sys
30+
from nylas import Client
31+
32+
33+
def get_env_or_exit(var_name: str) -> str:
34+
"""Get an environment variable or exit if not found."""
35+
value = os.getenv(var_name)
36+
if not value:
37+
print(f"Error: {var_name} environment variable is required")
38+
sys.exit(1)
39+
return value
40+
41+
42+
def send_email(client: Client, grant_id: str, recipient: str) -> None:
43+
"""Send an email with special characters in the subject."""
44+
# Subject with special characters (accented letters)
45+
subject = "De l'idée à la post-prod, sans friction"
46+
47+
body = """
48+
<html>
49+
<body>
50+
<h1>Bonjour!</h1>
51+
<p>Ce message démontre l'envoi d'un email avec des caractères spéciaux dans le sujet.</p>
52+
<p>Le sujet de cet email est: <strong>De l'idée à la post-prod, sans friction</strong></p>
53+
<p>Les caractères accentués sont correctement préservés grâce à l'encodage UTF-8.</p>
54+
</body>
55+
</html>
56+
"""
57+
58+
print(f"Sending email...")
59+
print(f" To: {recipient}")
60+
print(f" Subject: {subject}")
61+
62+
try:
63+
response = client.messages.send(
64+
identifier=grant_id,
65+
request_body={
66+
"subject": subject,
67+
"to": [{"email": recipient}],
68+
"body": body,
69+
}
70+
)
71+
72+
print(f"\n✓ Email sent successfully!")
73+
print(f" Message ID: {response.data.id}")
74+
print(f" Subject: {response.data.subject}")
75+
print(f"\n✅ Special characters in subject are correctly preserved")
76+
77+
except Exception as e:
78+
print(f"\n❌ Error sending email: {e}")
79+
sys.exit(1)
80+
81+
82+
def main():
83+
"""Main function."""
84+
# Get required environment variables
85+
api_key = get_env_or_exit("NYLAS_API_KEY")
86+
grant_id = get_env_or_exit("NYLAS_GRANT_ID")
87+
recipient = get_env_or_exit("RECIPIENT_EMAIL")
88+
89+
# Initialize Nylas client
90+
client = Client(api_key=api_key)
91+
92+
print("=" * 60)
93+
print(" Nylas SDK: Send Email with Special Characters Example")
94+
print("=" * 60)
95+
print()
96+
print("This example sends an email with the subject:")
97+
print(' "De l\'idée à la post-prod, sans friction"')
98+
print()
99+
print(f"Grant ID: {grant_id}")
100+
print(f"Recipient: {recipient}")
101+
print()
102+
103+
# Send the email
104+
send_email(client, grant_id, recipient)
105+
106+
print("\n" + "=" * 60)
107+
print("Example completed successfully! ✅")
108+
print("=" * 60)
109+
110+
111+
if __name__ == "__main__":
112+
main()
113+

nylas/utils/file_utils.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,7 @@ def _build_form_request(request_body: dict) -> MultipartEncoder:
6565
"""
6666
attachments = request_body.get("attachments", [])
6767
request_body.pop("attachments", None)
68-
# Use ensure_ascii=False to preserve UTF-8 characters (accented letters, etc.)
69-
# instead of escaping them as unicode sequences
70-
message_payload = json.dumps(request_body, ensure_ascii=False)
68+
message_payload = json.dumps(request_body)
7169

7270
# Create the multipart/form-data encoder
7371
fields = {"message": ("", message_payload, "application/json")}

tests/utils/test_file_utils.py

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -171,43 +171,6 @@ def test_build_form_request_no_attachments(self):
171171
)
172172
assert request.fields["message"][2] == "application/json"
173173

174-
def test_build_form_request_with_special_characters(self):
175-
"""Test that special characters (accented letters) are properly encoded in form requests."""
176-
import json
177-
178-
# This is the exact subject from the bug report
179-
request_body = {
180-
"to": [{"email": "test@gmail.com"}],
181-
"subject": "De l'idée à la post-prod, sans friction",
182-
"body": "Test body with special chars: café, naïve, résumé",
183-
"attachments": [
184-
{
185-
"filename": "attachment.txt",
186-
"content_type": "text/plain",
187-
"content": b"test data",
188-
"size": 1234,
189-
}
190-
],
191-
}
192-
193-
request = _build_form_request(request_body)
194-
195-
# Verify the message field exists
196-
assert "message" in request.fields
197-
message_content = request.fields["message"][1]
198-
199-
# Parse the JSON to verify it contains the correct characters
200-
parsed_message = json.loads(message_content)
201-
assert parsed_message["subject"] == "De l'idée à la post-prod, sans friction"
202-
assert "café" in parsed_message["body"]
203-
assert "naïve" in parsed_message["body"]
204-
assert "résumé" in parsed_message["body"]
205-
206-
# Verify that the special characters are preserved in the JSON string itself
207-
# They should NOT be escaped as unicode escape sequences
208-
assert "idée" in message_content
209-
assert "café" in message_content
210-
211174
def test_build_form_request_encoding_comparison(self):
212175
"""Test to demonstrate the difference between ensure_ascii=True and ensure_ascii=False."""
213176
import json

0 commit comments

Comments
 (0)