From ab99680a927bb116ac9cbdc5fa7471dd65717e7c Mon Sep 17 00:00:00 2001 From: Gabriel Miranda Date: Fri, 11 Sep 2026 14:47:48 -0300 Subject: [PATCH 1/2] feat(broadcasts): add duplicate endpoint Ports resend.broadcasts.duplicate(id) from the Node SDK for POST /broadcasts/{id}/duplicate (resend/resend-openapi#115). Co-Authored-By: Claude Fable 5.1 --- examples/broadcasts.py | 8 +++++ resend/broadcasts/_broadcasts.py | 58 ++++++++++++++++++++++++++++++++ tests/broadcasts_async_test.py | 27 +++++++++++++++ tests/broadcasts_test.py | 18 ++++++++++ 4 files changed, 111 insertions(+) diff --git a/examples/broadcasts.py b/examples/broadcasts.py index c5a59a7..acd2135 100644 --- a/examples/broadcasts.py +++ b/examples/broadcasts.py @@ -35,6 +35,14 @@ print("Updated broadcast!") print(updated_broadcast) +duplicated: resend.Broadcasts.DuplicateResponse = resend.Broadcasts.duplicate( + id=broadcast["id"] +) +print("Duplicated broadcast with ID: {}".format(duplicated["id"])) +print(duplicated) +resend.Broadcasts.remove(id=duplicated["id"]) +print("Removed the duplicate!") + send_params: resend.Broadcasts.SendParams = { "broadcast_id": broadcast["id"], } diff --git a/resend/broadcasts/_broadcasts.py b/resend/broadcasts/_broadcasts.py index 718437d..461737e 100644 --- a/resend/broadcasts/_broadcasts.py +++ b/resend/broadcasts/_broadcasts.py @@ -367,6 +367,24 @@ class CancelResponse(BaseResponse): id of the canceled broadcast """ + class DuplicateResponse(BaseResponse): + """ + DuplicateResponse is the class that wraps the response of the duplicate method. + + Attributes: + object (str): object type: "broadcast" + id (str): id of the duplicated broadcast + """ + + object: str + """ + object type: "broadcast" + """ + id: str + """ + id of the duplicated broadcast + """ + class RemoveResponse(BaseResponse): """ RemoveResponse is the class that wraps the response of the remove method. @@ -556,6 +574,26 @@ def cancel(cls, id: str) -> CancelResponse: ).perform_with_content() return resp + @classmethod + def duplicate(cls, id: str) -> DuplicateResponse: + """ + Duplicate a broadcast. + see more: https://resend.com/docs/api-reference/broadcasts/duplicate-broadcast + + Creates a new draft with the same content as the source, named after it with " (copy)" appended. + + Args: + id (str): The broadcast ID + + Returns: + DuplicateResponse: The duplicate response object + """ + path = f"/broadcasts/{id}/duplicate" + resp = request.Request[Broadcasts.DuplicateResponse]( + path=path, params={}, verb="post" + ).perform_with_content() + return resp + @classmethod def remove(cls, id: str) -> RemoveResponse: """ @@ -731,6 +769,26 @@ async def cancel_async(cls, id: str) -> CancelResponse: ).perform_with_content() return resp + @classmethod + async def duplicate_async(cls, id: str) -> DuplicateResponse: + """ + Duplicate a broadcast (async). + see more: https://resend.com/docs/api-reference/broadcasts/duplicate-broadcast + + Creates a new draft with the same content as the source, named after it with " (copy)" appended. + + Args: + id (str): The broadcast ID + + Returns: + DuplicateResponse: The duplicate response object + """ + path = f"/broadcasts/{id}/duplicate" + resp = await AsyncRequest[Broadcasts.DuplicateResponse]( + path=path, params={}, verb="post" + ).perform_with_content() + return resp + @classmethod async def remove_async(cls, id: str) -> RemoveResponse: """ diff --git a/tests/broadcasts_async_test.py b/tests/broadcasts_async_test.py index cdb5133..5436d92 100644 --- a/tests/broadcasts_async_test.py +++ b/tests/broadcasts_async_test.py @@ -148,6 +148,33 @@ async def test_should_cancel_broadcasts_async_raise_exception_when_no_content( "78261eea-8f8b-4381-83c6-79fa7120f1cf" ) + async def test_broadcasts_duplicate_async(self) -> None: + self.set_mock_json( + { + "object": "broadcast", + "id": "3d4a472d-bc6d-4dd2-aa9d-d3d50ce87222", + } + ) + + duplicated = await resend.Broadcasts.duplicate_async( + "78261eea-8f8b-4381-83c6-79fa7120f1cf" + ) + assert duplicated["id"] == "3d4a472d-bc6d-4dd2-aa9d-d3d50ce87222" + assert duplicated["object"] == "broadcast" + assert ( + self.mock.call_args.kwargs["url"] + == "https://api.resend.com/broadcasts/78261eea-8f8b-4381-83c6-79fa7120f1cf/duplicate" + ) + + async def test_should_duplicate_broadcasts_async_raise_exception_when_no_content( + self, + ) -> None: + self.set_mock_json(None) + with pytest.raises(NoContentError): + _ = await resend.Broadcasts.duplicate_async( + "78261eea-8f8b-4381-83c6-79fa7120f1cf" + ) + async def test_broadcasts_remove_async(self) -> None: self.set_mock_json( { diff --git a/tests/broadcasts_test.py b/tests/broadcasts_test.py index 8c9ca83..98ef889 100644 --- a/tests/broadcasts_test.py +++ b/tests/broadcasts_test.py @@ -113,6 +113,24 @@ def test_broadcasts_cancel(self) -> None: assert canceled["id"] == "78261eea-8f8b-4381-83c6-79fa7120f1cf" assert canceled["object"] == "broadcast" + def test_broadcasts_duplicate(self) -> None: + self.set_mock_json( + { + "object": "broadcast", + "id": "3d4a472d-bc6d-4dd2-aa9d-d3d50ce87222", + } + ) + + duplicated = resend.Broadcasts.duplicate( + "78261eea-8f8b-4381-83c6-79fa7120f1cf" + ) + assert duplicated["id"] == "3d4a472d-bc6d-4dd2-aa9d-d3d50ce87222" + assert duplicated["object"] == "broadcast" + assert ( + self.mock.call_args.kwargs["url"] + == "https://api.resend.com/broadcasts/78261eea-8f8b-4381-83c6-79fa7120f1cf/duplicate" + ) + def test_broadcasts_remove(self) -> None: self.set_mock_json( { From 1412d51a9b806ad12e8b609fb11d26886a6929d1 Mon Sep 17 00:00:00 2001 From: Gabriel Miranda Date: Fri, 11 Sep 2026 14:47:48 -0300 Subject: [PATCH 2/2] chore: bump version to 2.45.0 Co-Authored-By: Claude Fable 5.1 --- resend/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resend/version.py b/resend/version.py index 6595822..d15cc3c 100644 --- a/resend/version.py +++ b/resend/version.py @@ -1,4 +1,4 @@ -__version__ = "2.44.0" +__version__ = "2.45.0" def get_version() -> str: