From 3d02156216e86bc86a4ff8426f4080eab4c521c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Melo?= Date: Tue, 29 Sep 2026 16:55:40 +0000 Subject: [PATCH 1/3] feat(webhooks): add support for topic and contact.topics.updated events --- resend/__init__.py | 10 ++++ resend/webhooks/_webhook.py | 5 ++ resend/webhooks/_webhook_event.py | 87 +++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+) diff --git a/resend/__init__.py b/resend/__init__.py index 301d91a..4d524b9 100644 --- a/resend/__init__.py +++ b/resend/__init__.py @@ -79,6 +79,8 @@ WebhookHeaders, WebhookStatus) from .webhooks._webhook_event import (BaseEmailEventData, ContactCreatedEvent, ContactDeletedEvent, ContactEventData, + ContactTopicsEventData, + ContactTopicsUpdatedEvent, ContactUpdatedEvent, DomainCreatedEvent, DomainDeletedEvent, DomainEventData, DomainUpdatedEvent, EmailBouncedEvent, @@ -92,6 +94,8 @@ SuppressionAddedEvent, SuppressionEventData, SuppressionRemovedEvent, + TopicCreatedEvent, TopicDeletedEvent, + TopicEventData, TopicUpdatedEvent, WebhookEventPayload) from .webhooks._webhooks import Webhooks @@ -210,12 +214,18 @@ "ContactCreatedEvent", "ContactUpdatedEvent", "ContactDeletedEvent", + "ContactTopicsEventData", + "ContactTopicsUpdatedEvent", "DomainCreatedEvent", "DomainUpdatedEvent", "DomainDeletedEvent", "SuppressionEventData", "SuppressionAddedEvent", "SuppressionRemovedEvent", + "TopicEventData", + "TopicCreatedEvent", + "TopicUpdatedEvent", + "TopicDeletedEvent", "Topic", "OAuthGrant", "OAuthGrantClient", diff --git a/resend/webhooks/_webhook.py b/resend/webhooks/_webhook.py index 0f82eac..4f916f9 100644 --- a/resend/webhooks/_webhook.py +++ b/resend/webhooks/_webhook.py @@ -21,6 +21,7 @@ "contact.created", "contact.updated", "contact.deleted", + "contact.topics.updated", # Domain events "domain.created", "domain.updated", @@ -28,6 +29,10 @@ # Suppression events "suppression.added", "suppression.removed", + # Topic events + "topic.created", + "topic.updated", + "topic.deleted", ] diff --git a/resend/webhooks/_webhook_event.py b/resend/webhooks/_webhook_event.py index 65c4902..b06b0ee 100644 --- a/resend/webhooks/_webhook_event.py +++ b/resend/webhooks/_webhook_event.py @@ -2,6 +2,7 @@ from typing_extensions import Literal, NotRequired, TypedDict +from resend.contacts._contact_topic import TopicSubscriptionUpdate from resend.suppressions._suppression import SuppressionListItem # Functional syntax required because ``from`` is a reserved keyword. @@ -289,6 +290,21 @@ class ContactEventData(TypedDict): """ +class ContactTopicsEventData(TypedDict): + """ + ``data`` payload for ``contact.topics.updated`` events. + """ + + email: str + """ + The contact email address. + """ + topics: List[TopicSubscriptionUpdate] + """ + The topics changed in this update, each with its new subscription. + """ + + class DomainRecord(TypedDict): """ DNS record included on domain webhook events. @@ -364,6 +380,41 @@ class SuppressionEventData(SuppressionListItem): """ +class TopicEventData(TypedDict): + """ + ``data`` payload for topic webhook events. + """ + + id: str + """ + The topic ID. + """ + name: str + """ + The topic name. + """ + description: Optional[str] + """ + The topic description. None when the topic has no description. + """ + default_subscription: Literal["opt_in", "opt_out"] + """ + The default subscription preference for new contacts. + """ + deleted: bool + """ + Whether the topic is deleted. True on ``topic.deleted`` events. + """ + created_at: str + """ + When the topic was created. + """ + updated_at: str + """ + When the topic was last updated. + """ + + class EmailSentEvent(TypedDict): """Webhook payload for ``email.sent``.""" @@ -476,6 +527,14 @@ class ContactDeletedEvent(TypedDict): data: ContactEventData +class ContactTopicsUpdatedEvent(TypedDict): + """Webhook payload for ``contact.topics.updated``.""" + + type: Literal["contact.topics.updated"] + created_at: str + data: ContactTopicsEventData + + class DomainCreatedEvent(TypedDict): """Webhook payload for ``domain.created``.""" @@ -516,6 +575,30 @@ class SuppressionRemovedEvent(TypedDict): data: SuppressionEventData +class TopicCreatedEvent(TypedDict): + """Webhook payload for ``topic.created``.""" + + type: Literal["topic.created"] + created_at: str + data: TopicEventData + + +class TopicUpdatedEvent(TypedDict): + """Webhook payload for ``topic.updated``.""" + + type: Literal["topic.updated"] + created_at: str + data: TopicEventData + + +class TopicDeletedEvent(TypedDict): + """Webhook payload for ``topic.deleted``.""" + + type: Literal["topic.deleted"] + created_at: str + data: TopicEventData + + WebhookEventPayload = Union[ EmailSentEvent, EmailScheduledEvent, @@ -531,11 +614,15 @@ class SuppressionRemovedEvent(TypedDict): ContactCreatedEvent, ContactUpdatedEvent, ContactDeletedEvent, + ContactTopicsUpdatedEvent, DomainCreatedEvent, DomainUpdatedEvent, DomainDeletedEvent, SuppressionAddedEvent, SuppressionRemovedEvent, + TopicCreatedEvent, + TopicUpdatedEvent, + TopicDeletedEvent, ] """ Union of all Resend webhook event payload shapes returned by ``Webhooks.verify``. From 07871e10cf633652194d1597480bc875f393d287 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Melo?= Date: Tue, 29 Sep 2026 16:55:40 +0000 Subject: [PATCH 2/3] chore: bump version to 2.49.0 --- resend/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resend/version.py b/resend/version.py index 434821a..9ada10a 100644 --- a/resend/version.py +++ b/resend/version.py @@ -1,4 +1,4 @@ -__version__ = "2.48.0" +__version__ = "2.49.0" def get_version() -> str: From d8610a82d0634f90a50e3714713a51ae3c440b86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Melo?= Date: Tue, 29 Sep 2026 17:49:16 +0000 Subject: [PATCH 3/3] fix(webhooks): type contact topic subscriptions as opt_in or opt_out --- resend/__init__.py | 2 ++ resend/webhooks/_webhook_event.py | 18 ++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/resend/__init__.py b/resend/__init__.py index 4d524b9..c2b983c 100644 --- a/resend/__init__.py +++ b/resend/__init__.py @@ -80,6 +80,7 @@ from .webhooks._webhook_event import (BaseEmailEventData, ContactCreatedEvent, ContactDeletedEvent, ContactEventData, ContactTopicsEventData, + ContactTopicSubscription, ContactTopicsUpdatedEvent, ContactUpdatedEvent, DomainCreatedEvent, DomainDeletedEvent, DomainEventData, @@ -216,6 +217,7 @@ "ContactDeletedEvent", "ContactTopicsEventData", "ContactTopicsUpdatedEvent", + "ContactTopicSubscription", "DomainCreatedEvent", "DomainUpdatedEvent", "DomainDeletedEvent", diff --git a/resend/webhooks/_webhook_event.py b/resend/webhooks/_webhook_event.py index b06b0ee..5407e81 100644 --- a/resend/webhooks/_webhook_event.py +++ b/resend/webhooks/_webhook_event.py @@ -2,7 +2,6 @@ from typing_extensions import Literal, NotRequired, TypedDict -from resend.contacts._contact_topic import TopicSubscriptionUpdate from resend.suppressions._suppression import SuppressionListItem # Functional syntax required because ``from`` is a reserved keyword. @@ -290,6 +289,21 @@ class ContactEventData(TypedDict): """ +class ContactTopicSubscription(TypedDict): + """ + Topic entry on ``contact.topics.updated`` events. + """ + + id: str + """ + The topic ID. + """ + subscription: Literal["opt_in", "opt_out"] + """ + The contact's new subscription to the topic. + """ + + class ContactTopicsEventData(TypedDict): """ ``data`` payload for ``contact.topics.updated`` events. @@ -299,7 +313,7 @@ class ContactTopicsEventData(TypedDict): """ The contact email address. """ - topics: List[TopicSubscriptionUpdate] + topics: List[ContactTopicSubscription] """ The topics changed in this update, each with its new subscription. """