diff --git a/resend/domains/_domain.py b/resend/domains/_domain.py index 06de159..91a1df8 100644 --- a/resend/domains/_domain.py +++ b/resend/domains/_domain.py @@ -1,6 +1,6 @@ from typing import List, Union -from typing_extensions import TypedDict +from typing_extensions import NotRequired, TypedDict from resend.domains._record import Record @@ -34,3 +34,15 @@ class Domain(TypedDict): """ Wether the domain is deleted or not """ + open_tracking: NotRequired[bool] + """ + Track the open rate of each email. + """ + click_tracking: NotRequired[bool] + """ + Track clicks within the body of each HTML email. + """ + tracking_subdomain: NotRequired[str] + """ + The custom subdomain used for click and open tracking links (e.g., "links"). + """ diff --git a/resend/domains/_domains.py b/resend/domains/_domains.py index 2ba0ba8..ab65e70 100644 --- a/resend/domains/_domains.py +++ b/resend/domains/_domains.py @@ -71,6 +71,9 @@ class CreateDomainResponse(BaseResponse): status (str): Status of the domain region (str): The region where emails will be sent from records (Union[List[Record], None]): The list of domain records + open_tracking (bool): Whether open tracking is enabled + click_tracking (bool): Whether click tracking is enabled + tracking_subdomain (str): The custom subdomain for tracking links """ id: str @@ -97,6 +100,18 @@ class CreateDomainResponse(BaseResponse): """ The list of domain records """ + open_tracking: NotRequired[bool] + """ + Track email opens + """ + click_tracking: NotRequired[bool] + """ + Track clicks within the body of HTML emails + """ + tracking_subdomain: NotRequired[str] + """ + The custom subdomain used for click and open tracking links (e.g., "links") + """ class UpdateParams(TypedDict): id: str @@ -122,6 +137,10 @@ class UpdateParams(TypedDict): communication must use TLS no matter what. If the receiving server does not support TLS, the email will not be sent. """ + tracking_subdomain: NotRequired[str] + """ + The custom subdomain used for click and open tracking links (e.g., "links"). + """ class CreateParams(TypedDict): name: str @@ -139,6 +158,10 @@ class CreateParams(TypedDict): You can change this by setting the optional `custom_return_path` parameter when creating a domain via the API or under Advanced options in the dashboard. """ + tracking_subdomain: NotRequired[str] + """ + The custom subdomain used for click and open tracking links (e.g., "links"). + """ @classmethod def create(cls, params: CreateParams) -> CreateDomainResponse: diff --git a/resend/domains/_record.py b/resend/domains/_record.py index e2393a7..0654004 100644 --- a/resend/domains/_record.py +++ b/resend/domains/_record.py @@ -1,10 +1,10 @@ -from typing_extensions import TypedDict +from typing_extensions import NotRequired, TypedDict class Record(TypedDict): record: str """ - The domain record type, ie: SPF. + The domain record type, ie: SPF, DKIM, Inbound, Tracking. """ name: str """ @@ -26,7 +26,7 @@ class Record(TypedDict): """ The domain record value. """ - priority: int + priority: NotRequired[int] """ The domain record priority. """ diff --git a/tests/domains_async_test.py b/tests/domains_async_test.py index 2ba6497..52ee2f4 100644 --- a/tests/domains_async_test.py +++ b/tests/domains_async_test.py @@ -146,6 +146,89 @@ async def test_should_list_domains_async_raise_exception_when_no_content( with pytest.raises(NoContentError): _ = await resend.Domains.list_async() + async def test_domains_create_async_with_tracking_subdomain(self) -> None: + self.set_mock_json( + { + "id": "4dd369bc-aa82-4ff3-97de-514ae3000ee0", + "name": "example.com", + "created_at": "2023-03-28T17:12:02.059593+00:00", + "status": "not_started", + "open_tracking": True, + "click_tracking": True, + "tracking_subdomain": "links", + "records": [ + { + "record": "DKIM", + "name": "nhapbbryle57yxg3fbjytyodgbt2kyyg._domainkey", + "value": "nhapbbryle57yxg3fbjytyodgbt2kyyg.dkim.amazonses.com.", + "type": "CNAME", + "status": "not_started", + "ttl": "Auto", + }, + { + "record": "Tracking", + "name": "links.example.com", + "value": "links1.resend-dns.com", + "type": "CNAME", + "ttl": "Auto", + "status": "not_started", + }, + ], + "region": "us-east-1", + } + ) + + create_params: resend.Domains.CreateParams = { + "name": "example.com", + "region": "us-east-1", + "tracking_subdomain": "links", + } + domain = await resend.Domains.create_async(params=create_params) + assert domain["id"] == "4dd369bc-aa82-4ff3-97de-514ae3000ee0" + assert domain["open_tracking"] is True + assert domain["click_tracking"] is True + assert domain["tracking_subdomain"] == "links" + tracking_record = next( + (r for r in (domain["records"] or []) if r["record"] == "Tracking"), None + ) + assert tracking_record is not None + assert tracking_record["name"] == "links.example.com" + assert tracking_record["value"] == "links1.resend-dns.com" + assert tracking_record["type"] == "CNAME" + + async def test_domains_get_async_with_tracking_fields(self) -> None: + self.set_mock_json( + { + "object": "domain", + "id": "d91cd9bd-1176-453e-8fc1-35364d380206", + "name": "example.com", + "status": "not_started", + "created_at": "2023-04-26T20:21:26.347412+00:00", + "region": "us-east-1", + "open_tracking": True, + "click_tracking": True, + "tracking_subdomain": "links", + "records": [ + { + "record": "Tracking", + "name": "links.example.com", + "value": "links1.resend-dns.com", + "type": "CNAME", + "ttl": "Auto", + "status": "verified", + } + ], + } + ) + + domain = await resend.Domains.get_async( + domain_id="d91cd9bd-1176-453e-8fc1-35364d380206", + ) + assert domain["id"] == "d91cd9bd-1176-453e-8fc1-35364d380206" + assert domain["open_tracking"] is True + assert domain["click_tracking"] is True + assert domain["tracking_subdomain"] == "links" + async def test_domains_update_async(self) -> None: self.set_mock_json( { @@ -171,6 +254,21 @@ async def test_domains_update_async(self) -> None: assert domain["created_at"] == "2023-04-26T20:21:26.347412+00:00" assert domain["region"] == "us-east-1" + async def test_domains_update_async_with_tracking_subdomain(self) -> None: + self.set_mock_json( + { + "object": "domain", + "id": "d91cd9bd-1176-453e-8fc1-35364d380206", + } + ) + + update_params: resend.Domains.UpdateParams = { + "id": "d91cd9bd-1176-453e-8fc1-35364d380206", + "tracking_subdomain": "links", + } + domain = await resend.Domains.update_async(params=update_params) + assert domain["id"] == "d91cd9bd-1176-453e-8fc1-35364d380206" + async def test_should_update_domains_async_raise_exception_when_no_content( self, ) -> None: diff --git a/tests/domains_test.py b/tests/domains_test.py index c604699..22df57c 100644 --- a/tests/domains_test.py +++ b/tests/domains_test.py @@ -180,6 +180,91 @@ def test_should_verify_domains_raise_exception_when_no_content(self) -> None: domain_id="d91cd9bd-1176-453e-8fc1-35364d380206", ) + def test_domains_create_with_tracking_subdomain(self) -> None: + self.set_mock_json( + { + "id": "4dd369bc-aa82-4ff3-97de-514ae3000ee0", + "name": "example.com", + "created_at": "2023-03-28T17:12:02.059593+00:00", + "status": "not_started", + "open_tracking": True, + "click_tracking": True, + "tracking_subdomain": "links", + "records": [ + { + "record": "DKIM", + "name": "nhapbbryle57yxg3fbjytyodgbt2kyyg._domainkey", + "value": "nhapbbryle57yxg3fbjytyodgbt2kyyg.dkim.amazonses.com.", + "type": "CNAME", + "status": "not_started", + "ttl": "Auto", + }, + { + "record": "Tracking", + "name": "links.example.com", + "value": "links1.resend-dns.com", + "type": "CNAME", + "ttl": "Auto", + "status": "not_started", + }, + ], + "region": "us-east-1", + } + ) + + create_params: resend.Domains.CreateParams = { + "name": "example.com", + "region": "us-east-1", + "tracking_subdomain": "links", + } + domain: resend.Domains.CreateDomainResponse = resend.Domains.create( + params=create_params + ) + assert domain["id"] == "4dd369bc-aa82-4ff3-97de-514ae3000ee0" + assert domain["open_tracking"] is True + assert domain["click_tracking"] is True + assert domain["tracking_subdomain"] == "links" + tracking_record = next( + (r for r in (domain["records"] or []) if r["record"] == "Tracking"), None + ) + assert tracking_record is not None + assert tracking_record["name"] == "links.example.com" + assert tracking_record["value"] == "links1.resend-dns.com" + assert tracking_record["type"] == "CNAME" + + def test_domains_get_with_tracking_fields(self) -> None: + self.set_mock_json( + { + "object": "domain", + "id": "d91cd9bd-1176-453e-8fc1-35364d380206", + "name": "example.com", + "status": "not_started", + "created_at": "2023-04-26T20:21:26.347412+00:00", + "region": "us-east-1", + "open_tracking": True, + "click_tracking": True, + "tracking_subdomain": "links", + "records": [ + { + "record": "Tracking", + "name": "links.example.com", + "value": "links1.resend-dns.com", + "type": "CNAME", + "ttl": "Auto", + "status": "verified", + } + ], + } + ) + + domain = resend.Domains.get( + domain_id="d91cd9bd-1176-453e-8fc1-35364d380206", + ) + assert domain["id"] == "d91cd9bd-1176-453e-8fc1-35364d380206" + assert domain["open_tracking"] is True + assert domain["click_tracking"] is True + assert domain["tracking_subdomain"] == "links" + def test_domains_update(self) -> None: self.set_mock_json( { @@ -197,6 +282,21 @@ def test_domains_update(self) -> None: domain = resend.Domains.update(params) assert domain["id"] == "479e3145-dd38-476b-932c-529ceb705947" + def test_domains_update_with_tracking_subdomain(self) -> None: + self.set_mock_json( + { + "object": "domain", + "id": "479e3145-dd38-476b-932c-529ceb705947", + } + ) + + params: resend.Domains.UpdateParams = { + "id": "479e3145-dd38-476b-932c-529ceb705947", + "tracking_subdomain": "links", + } + domain = resend.Domains.update(params) + assert domain["id"] == "479e3145-dd38-476b-932c-529ceb705947" + def test_should_update_domains_raise_exception_when_no_content(self) -> None: self.set_mock_json(None) params: resend.Domains.UpdateParams = {