From 9d70ed452af972976add5b9a0637d5ea9c6b0fbd Mon Sep 17 00:00:00 2001
From: Mostafa Rashed <17770919+mrashed-dev@users.noreply.github.com>
Date: Fri, 3 May 2024 12:22:36 -0400
Subject: [PATCH 1/2] Add clean conversation support (#361)
This PR adds support for the /messages/clean endpoint.
---
CHANGELOG.md | 4 +++
nylas/models/messages.py | 53 ++++++++++++++++++++++++++++++++
nylas/resources/messages.py | 23 ++++++++++++++
tests/conftest.py | 29 +++++++++++++++++
tests/resources/test_messages.py | 34 ++++++++++++++++++++
5 files changed, 143 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 14b8ae25..30638355 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,10 @@
nylas-python Changelog
======================
+Unreleased
+----------------
+* Add clean messages support
+
v6.1.1
----------------
* Improved message sending and draft create/update performance
diff --git a/nylas/models/messages.py b/nylas/models/messages.py
index 3e1c82a9..58f917ce 100644
--- a/nylas/models/messages.py
+++ b/nylas/models/messages.py
@@ -198,3 +198,56 @@ class StopScheduledMessageResponse:
"""
message: str
+
+
+class CleanMessagesRequest(TypedDict):
+ """
+ Request to clean a list of messages.
+
+ Attributes:
+ message_id: IDs of the email messages to clean.
+ ignore_links: If true, removes link-related tags () from the email message while keeping the text.
+ ignore_images: If true, removes images from the email message.
+ images_as_markdown: If true, converts images in the email message to Markdown.
+ ignore_tables: If true, removes table-related tags (
, , | , | ) from the email message while
+ keeping rows.
+ remove_conclusion_phrases: If true, removes phrases such as "Best" and "Regards" in the email message signature.
+ """
+
+ message_id: List[str]
+ ignore_links: NotRequired[bool]
+ ignore_images: NotRequired[bool]
+ images_as_markdown: NotRequired[bool]
+ ignore_tables: NotRequired[bool]
+ remove_conclusion_phrases: NotRequired[bool]
+
+
+@dataclass_json
+@dataclass
+class CleanMessagesResponse(Message):
+ """
+ Message object with the cleaned HTML message body.
+
+ Attributes:
+ id (str): Globally unique object identifier.
+ grant_id (str): The grant that this message belongs to.
+ from_ (List[EmailName]): The sender of the message.
+ date (int): The date the message was received.
+ object: The type of object.
+ thread_id (Optional[str]): The thread that this message belongs to.
+ subject (Optional[str]): The subject of the message.
+ to (Optional[List[EmailName]]): The recipients of the message.
+ cc (Optional[List[EmailName]]): The CC recipients of the message.
+ bcc (Optional[List[EmailName]]): The BCC recipients of the message.
+ reply_to (Optional[List[EmailName]]): The reply-to recipients of the message.
+ unread (Optional[bool]): Whether the message is unread.
+ starred (Optional[bool]): Whether the message is starred.
+ snippet (Optional[str]): A snippet of the message body.
+ body (Optional[str]): The body of the message.
+ attachments (Optional[List[Attachment]]): The attachments on the message.
+ folders (Optional[List[str]]): The folders that the message is in.
+ created_at (Optional[int]): Unix timestamp of when the message was created.
+ conversation (str): The cleaned HTML message body.
+ """
+
+ conversation: str = ""
diff --git a/nylas/resources/messages.py b/nylas/resources/messages.py
index b37dabed..ed19c106 100644
--- a/nylas/resources/messages.py
+++ b/nylas/resources/messages.py
@@ -14,6 +14,8 @@
UpdateMessageRequest,
ScheduledMessage,
StopScheduledMessageResponse,
+ CleanMessagesRequest,
+ CleanMessagesResponse,
)
from nylas.models.response import Response, ListResponse, DeleteResponse
from nylas.resources.smart_compose import SmartCompose
@@ -223,3 +225,24 @@ def stop_scheduled_message(
)
return Response.from_dict(json_response, StopScheduledMessageResponse)
+
+ def clean_messages(
+ self, identifier: str, request_body: CleanMessagesRequest
+ ) -> ListResponse[CleanMessagesResponse]:
+ """
+ Remove extra information from a list of messages.
+
+ Args:
+ identifier: The identifier of the grant to clean the message for.
+ request_body: The values to clean the message with.
+
+ Returns:
+ The list of cleaned messages.
+ """
+ json_resposne = self._http_client._execute(
+ method="PUT",
+ path=f"/v3/grants/{identifier}/messages/clean",
+ request_body=request_body,
+ )
+
+ return ListResponse.from_dict(json_resposne, CleanMessagesResponse)
diff --git a/tests/conftest.py b/tests/conftest.py
index 41542ea7..6fba23db 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -189,3 +189,32 @@ def http_client_list_scheduled_messages():
],
}
return mock_http_client
+
+
+@pytest.fixture
+def http_client_clean_messages():
+ mock_http_client = Mock()
+ mock_http_client._execute.return_value = {
+ "request_id": "dd3ec9a2-8f15-403d-b269-32b1f1beb9f5",
+ "data": [
+ {
+ "body": "Hello, I just sent a message using Nylas!",
+ "from": [
+ {"name": "Daenerys Targaryen", "email": "daenerys.t@example.com"}
+ ],
+ "grant_id": "41009df5-bf11-4c97-aa18-b285b5f2e386",
+ "id": "message-1",
+ "object": "message",
+ "conversation": "cleaned example",
+ },
+ {
+ "body": "Hello, this is a test message!",
+ "from": [{"name": "Michael Scott", "email": "m.scott@email.com"}],
+ "grant_id": "41009df5-bf11-4c97-aa18-b285b5f2e386",
+ "id": "message-2",
+ "object": "message",
+ "conversation": "another example",
+ },
+ ],
+ }
+ return mock_http_client
diff --git a/tests/resources/test_messages.py b/tests/resources/test_messages.py
index d345a444..9a1be5bb 100644
--- a/tests/resources/test_messages.py
+++ b/tests/resources/test_messages.py
@@ -272,3 +272,37 @@ def test_stop_scheduled_message(self, http_client_response):
method="DELETE",
path="/v3/grants/abc-123/messages/schedules/schedule-123",
)
+
+ def test_clean_messages(self, http_client_clean_messages):
+ messages = Messages(http_client_clean_messages)
+ request_body = {
+ "message_id": ["message-1", "message-2"],
+ "ignore_images": True,
+ "ignore_links": True,
+ "ignore_tables": True,
+ "images_as_markdown": True,
+ "remove_conclusion_phrases": True,
+ }
+
+ response = messages.clean_messages(
+ identifier="abc-123",
+ request_body=request_body,
+ )
+
+ http_client_clean_messages._execute.assert_called_once_with(
+ method="PUT",
+ path="/v3/grants/abc-123/messages/clean",
+ request_body=request_body,
+ )
+
+ # Assert the conversation field, and the typical message fields serialize properly
+ assert len(response.data) == 2
+ assert response.data[0].body == "Hello, I just sent a message using Nylas!"
+ assert response.data[0].from_ == [
+ {"name": "Daenerys Targaryen", "email": "daenerys.t@example.com"}
+ ]
+ assert response.data[0].object == "message"
+ assert response.data[0].id == "message-1"
+ assert response.data[0].grant_id == "41009df5-bf11-4c97-aa18-b285b5f2e386"
+ assert response.data[0].conversation == "cleaned example"
+ assert response.data[1].conversation == "another example"
From 873363fd9c58613ad06f798fcd1b68525708bd29 Mon Sep 17 00:00:00 2001
From: Blag
Date: Fri, 3 May 2024 12:26:23 -0400
Subject: [PATCH 2/2] Python SDK - Add missing Webhook triggers (#357)
Added message_bouce_detected, message_created, message_updated, message_opened, message_link_clicked, thread.replied
---
nylas/models/webhooks.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/nylas/models/webhooks.py b/nylas/models/webhooks.py
index dc49a284..c4239f3a 100644
--- a/nylas/models/webhooks.py
+++ b/nylas/models/webhooks.py
@@ -24,6 +24,12 @@ class WebhookTriggers(str, Enum):
GRANT_EXPIRED = "grant.expired"
MESSAGE_SEND_SUCCESS = "message.send_success"
MESSAGE_SEND_FAILED = "message.send_failed"
+ MESSAGE_BOUNCE_DETECTED = "message.bounce_detected"
+ MESSAGE_CREATED = "message.created"
+ MESSAGE_UPDATED = "message.updated"
+ MESSAGE_OPENED = "message.opened"
+ MESSAGE_LINK_CLICKED = "message.link_clicked"
+ THREAD_REPLIED = "thread.replied"
@dataclass_json