Skip to content

Commit afc9051

Browse files
committed
feat: add async methods for missing packages
1 parent 0b85867 commit afc9051

23 files changed

Lines changed: 1917 additions & 35 deletions

examples/contacts_async.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import asyncio
22
import os
3-
from typing import List
43

54
import resend
65

@@ -23,7 +22,9 @@ async def main() -> None:
2322
"unsubscribed": False,
2423
}
2524

26-
contact: resend.Contact = await resend.Contacts.create_async(create_params)
25+
contact: resend.Contacts.CreateContactResponse = await resend.Contacts.create_async(
26+
create_params
27+
)
2728
print("Created contact !")
2829
print(contact)
2930

@@ -34,7 +35,9 @@ async def main() -> None:
3435
"first_name": "Steve (Async)",
3536
}
3637

37-
updated: resend.Contact = await resend.Contacts.update_async(update_params)
38+
updated: resend.Contacts.UpdateContactResponse = await resend.Contacts.update_async(
39+
update_params
40+
)
3841
print("updated contact !")
3942
print(updated)
4043

@@ -54,12 +57,12 @@ async def main() -> None:
5457
audience_id=audience_id
5558
)
5659
print("List of contacts")
57-
for contact in contacts["data"]:
58-
print(contact)
60+
for c in contacts["data"]:
61+
print(c)
5962

6063
# remove by email
6164
rmed = await resend.Contacts.remove_async(
62-
audience_id=audience_id, email=contact["email"]
65+
audience_id=audience_id, email=cont_by_email["email"]
6366
)
6467

6568
# remove by id

examples/domains_async.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ async def main() -> None:
1616
"region": "us-east-1",
1717
"custom_return_path": "outbound",
1818
}
19-
domain: resend.Domain = await resend.Domains.create_async(params=create_params)
19+
domain: resend.Domains.CreateDomainResponse = await resend.Domains.create_async(
20+
params=create_params
21+
)
2022
print(domain)
2123

2224
retrieved: resend.Domain = await resend.Domains.get_async(domain_id=domain["id"])
@@ -37,13 +39,13 @@ async def main() -> None:
3739
domains: resend.Domains.ListResponse = await resend.Domains.list_async()
3840
if not domains:
3941
print("No domains found")
40-
for domain in domains["data"]:
41-
print(domain)
42+
for d in domains["data"]:
43+
print(d)
4244

4345
verified_domain: resend.Domain = await resend.Domains.verify_async(
4446
domain_id=domain["id"]
4547
)
46-
print(f"Verified")
48+
print("Verified")
4749
print(verified_domain)
4850

4951
rm_domain: resend.Domain = await resend.Domains.remove_async(domain_id=domain["id"])

examples/simple_email_async.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,18 @@
2626

2727
async def main() -> None:
2828
# Without Idempotency Key
29-
email_non_idempotent: resend.Email = await resend.Emails.send_async(params)
29+
email_non_idempotent: resend.Emails.SendResponse = await resend.Emails.send_async(
30+
params
31+
)
3032
print(f"Sent email without idempotency key: {email_non_idempotent['id']}")
3133

3234
# With Idempotency Key
3335
options: resend.Emails.SendOptions = {
3436
"idempotency_key": "44",
3537
}
36-
email_idempotent: resend.Email = await resend.Emails.send_async(params, options)
38+
email_idempotent: resend.Emails.SendResponse = await resend.Emails.send_async(
39+
params, options
40+
)
3741
print(f"Sent email with idempotency key: {email_idempotent['id']}")
3842

3943
email_resp: resend.Email = await resend.Emails.get_async(

resend/contact_properties/_contact_properties.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66
from resend._base_response import BaseResponse
77
from resend.pagination_helper import PaginationHelper
88

9+
# Async imports (optional - only available with pip install resend[async])
10+
try:
11+
from resend.async_request import AsyncRequest
12+
except ImportError:
13+
pass
14+
915
from ._contact_property import ContactProperty
1016

1117

@@ -261,3 +267,96 @@ def remove(cls, id: str) -> RemoveResponse:
261267
path=path, params={}, verb="delete"
262268
).perform_with_content()
263269
return resp
270+
271+
@classmethod
272+
async def create_async(cls, params: CreateParams) -> CreateResponse:
273+
"""
274+
Create a new contact property (async).
275+
see more: https://resend.com/docs/api-reference/contact-properties/create-contact-property
276+
277+
Args:
278+
params (CreateParams): The contact property creation parameters
279+
280+
Returns:
281+
CreateResponse: The created contact property response
282+
"""
283+
path = "/contact-properties"
284+
resp = await AsyncRequest[ContactProperties.CreateResponse](
285+
path=path, params=cast(Dict[Any, Any], params), verb="post"
286+
).perform_with_content()
287+
return resp
288+
289+
@classmethod
290+
async def get_async(cls, id: str) -> ContactProperty:
291+
"""
292+
Get a contact property by ID (async).
293+
see more: https://resend.com/docs/api-reference/contact-properties/get-contact-property
294+
295+
Args:
296+
id (str): The contact property ID
297+
298+
Returns:
299+
ContactProperty: The contact property object
300+
"""
301+
path = f"/contact-properties/{id}"
302+
resp = await AsyncRequest[ContactProperty](
303+
path=path, params={}, verb="get"
304+
).perform_with_content()
305+
return resp
306+
307+
@classmethod
308+
async def list_async(cls, params: Optional[ListParams] = None) -> ListResponse:
309+
"""
310+
List all contact properties (async).
311+
see more: https://resend.com/docs/api-reference/contact-properties/list-contact-properties
312+
313+
Args:
314+
params (Optional[ListParams]): Optional pagination parameters
315+
316+
Returns:
317+
ListResponse: A list of contact property objects
318+
"""
319+
base_path = "/contact-properties"
320+
query_params = cast(Dict[Any, Any], params) if params else None
321+
path = PaginationHelper.build_paginated_path(base_path, query_params)
322+
resp = await AsyncRequest[ContactProperties.ListResponse](
323+
path=path, params={}, verb="get"
324+
).perform_with_content()
325+
return resp
326+
327+
@classmethod
328+
async def update_async(cls, params: UpdateParams) -> UpdateResponse:
329+
"""
330+
Update an existing contact property (async).
331+
see more: https://resend.com/docs/api-reference/contact-properties/update-contact-property
332+
333+
Args:
334+
params (UpdateParams): The contact property update parameters
335+
336+
Returns:
337+
UpdateResponse: The updated contact property response
338+
"""
339+
path = f"/contact-properties/{params['id']}"
340+
payload: Dict[str, Any] = {"fallback_value": params["fallback_value"]}
341+
resp = await AsyncRequest[ContactProperties.UpdateResponse](
342+
path=path, params=payload, verb="patch"
343+
).perform_with_content()
344+
return resp
345+
346+
@classmethod
347+
async def remove_async(cls, id: str) -> RemoveResponse:
348+
"""
349+
Remove a contact property by ID (async).
350+
see more: https://resend.com/docs/api-reference/contact-properties/delete-contact-property
351+
352+
Args:
353+
id (str): The contact property ID
354+
355+
Returns:
356+
RemoveResponse: The removed contact property response object
357+
"""
358+
path = f"/contact-properties/{id}"
359+
resp = await AsyncRequest[ContactProperties.RemoveResponse](
360+
path=path, params={}, verb="delete"
361+
).perform_with_content()
362+
return resp

resend/contacts/_contacts.py

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -357,42 +357,62 @@ def remove(
357357
return resp
358358

359359
@classmethod
360-
async def create_async(cls, params: CreateParams) -> Contact:
360+
async def create_async(cls, params: CreateParams) -> CreateContactResponse:
361361
"""
362362
Create a new contact (async).
363+
Can create either a global contact or an audience-specific contact.
363364
see more: https://resend.com/docs/api-reference/contacts/create-contact
364365
365366
Args:
366367
params (CreateParams): The contact creation parameters
368+
- If audience_id is provided: creates audience-specific contact
369+
- If audience_id is omitted: creates global contact with optional properties field
367370
368371
Returns:
369-
Contact: The new contact object
372+
CreateContactResponse: The created contact response
370373
"""
371-
path = f"/audiences/{params['audience_id']}/contacts"
372-
resp = await AsyncRequest[Contact](
374+
audience_id = params.get("audience_id")
375+
376+
if audience_id:
377+
path = f"/audiences/{audience_id}/contacts"
378+
else:
379+
path = "/contacts"
380+
381+
resp = await AsyncRequest[Contacts.CreateContactResponse](
373382
path=path, params=cast(Dict[Any, Any], params), verb="post"
374383
).perform_with_content()
375384
return resp
376385

377386
@classmethod
378-
async def update_async(cls, params: UpdateParams) -> Contact:
387+
async def update_async(cls, params: UpdateParams) -> UpdateContactResponse:
379388
"""
380389
Update an existing contact (async).
390+
Can update either a global contact or an audience-specific contact.
381391
see more: https://resend.com/docs/api-reference/contacts/update-contact
382392
383393
Args:
384394
params (UpdateParams): The contact update parameters
395+
- If audience_id is provided: updates audience-specific contact
396+
- If audience_id is omitted: updates global contact with optional properties field
385397
386398
Returns:
387-
Contact: The updated contact object
399+
UpdateContactResponse: The updated contact response.
388400
"""
389401
if params.get("id") is None and params.get("email") is None:
390402
raise ValueError("id or email must be provided")
391403

392-
val = params.get("id") if params.get("id") is not None else params.get("email")
404+
# Email takes precedence over id (matching Node.js behavior)
405+
contact_identifier = (
406+
params.get("email") if params.get("email") is not None else params.get("id")
407+
)
408+
audience_id = params.get("audience_id")
393409

394-
path = f"/audiences/{params['audience_id']}/contacts/{val}"
395-
resp = await AsyncRequest[Contact](
410+
if audience_id:
411+
path = f"/audiences/{audience_id}/contacts/{contact_identifier}"
412+
else:
413+
path = f"/contacts/{contact_identifier}"
414+
415+
resp = await AsyncRequest[Contacts.UpdateContactResponse](
396416
path=path, params=cast(Dict[Any, Any], params), verb="patch"
397417
).perform_with_content()
398418
return resp
@@ -403,6 +423,7 @@ async def list_async(
403423
) -> ListResponse:
404424
"""
405425
List all contacts (async).
426+
Can list either global contacts or audience-specific contacts.
406427
see more: https://resend.com/docs/api-reference/contacts/list-contacts
407428
408429
Args:
@@ -426,25 +447,34 @@ async def list_async(
426447

427448
@classmethod
428449
async def get_async(
429-
cls, audience_id: str, id: Optional[str] = None, email: Optional[str] = None
450+
cls,
451+
audience_id: Optional[str] = None,
452+
id: Optional[str] = None,
453+
email: Optional[str] = None,
430454
) -> Contact:
431455
"""
432456
Get a contact (async).
457+
Can retrieve either a global contact or an audience-specific contact.
433458
see more: https://resend.com/docs/api-reference/contacts/get-contact
434459
435460
Args:
436-
audience_id (str): The audience ID
437-
id (Optional[str]): The contact ID
438-
email (Optional[str]): The contact email
461+
audience_id (Optional[str]): The audience ID. If not provided, retrieves global contact.
462+
id (Optional[str]): The contact ID. Either id or email must be provided.
463+
email (Optional[str]): The contact email. Either id or email must be provided.
439464
440465
Returns:
441466
Contact: The contact object
442467
"""
443-
contact = email if id is None else id
444-
if contact is None:
468+
# Email takes precedence over id (matching Node.js behavior)
469+
contact_identifier = email if email is not None else id
470+
if contact_identifier is None:
445471
raise ValueError("id or email must be provided")
446472

447-
path = f"/audiences/{audience_id}/contacts/{contact}"
473+
if audience_id:
474+
path = f"/audiences/{audience_id}/contacts/{contact_identifier}"
475+
else:
476+
path = f"/contacts/{contact_identifier}"
477+
448478
resp = await AsyncRequest[Contact](
449479
path=path, params={}, verb="get"
450480
).perform_with_content()
@@ -459,6 +489,7 @@ async def remove_async(
459489
) -> RemoveContactResponse:
460490
"""
461491
Remove a contact by ID or by Email (async).
492+
Can remove either a global contact or an audience-specific contact.
462493
see more: https://resend.com/docs/api-reference/contacts/delete-contact
463494
464495
Args:

0 commit comments

Comments
 (0)