Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 0 additions & 53 deletions AGENTS.md

This file was deleted.

16 changes: 12 additions & 4 deletions src/courier/resources/users/tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,8 +257,12 @@ def add_single(
Args:
device: Information about the device the token came from.

expiry_date: ISO 8601 formatted date the token expires. Defaults to 2 months. Set to false to
disable expiration.
expiry_date: When the token expires. Accepts a date, or the boolean `false` to disable
expiration entirely. ISO 8601 is recommended (for example
`2026-10-25T00:00:00.000Z`). A value that cannot be parsed as a date is
rejected; it is not treated as "no expiration" and does not fall back to the
default. `true` is not a supported value. Omit the field to use the default,
which expires a token that has not been re-registered for 60 days.

properties: Properties about the token.

Expand Down Expand Up @@ -528,8 +532,12 @@ async def add_single(
Args:
device: Information about the device the token came from.

expiry_date: ISO 8601 formatted date the token expires. Defaults to 2 months. Set to false to
disable expiration.
expiry_date: When the token expires. Accepts a date, or the boolean `false` to disable
expiration entirely. ISO 8601 is recommended (for example
`2026-10-25T00:00:00.000Z`). A value that cannot be parsed as a date is
rejected; it is not treated as "no expiration" and does not fall back to the
default. `true` is not a supported value. Omit the field to use the default,
which expires a token that has not been re-registered for 60 days.

properties: Properties about the token.

Expand Down
56 changes: 53 additions & 3 deletions src/courier/types/email_footer.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,60 @@

from .._models import BaseModel

__all__ = ["EmailFooter"]
__all__ = [
"EmailFooter",
"Social",
"SocialFacebook",
"SocialInstagram",
"SocialLinkedin",
"SocialMedium",
"SocialTwitter",
]


class EmailFooter(BaseModel):
content: Optional[str] = None
class SocialFacebook(BaseModel):
url: Optional[str] = None


class SocialInstagram(BaseModel):
url: Optional[str] = None


class SocialLinkedin(BaseModel):
url: Optional[str] = None


class SocialMedium(BaseModel):
url: Optional[str] = None


class SocialTwitter(BaseModel):
url: Optional[str] = None


class Social(BaseModel):
"""Social links rendered in the email footer."""

facebook: Optional[SocialFacebook] = None

instagram: Optional[SocialInstagram] = None

linkedin: Optional[SocialLinkedin] = None

medium: Optional[SocialMedium] = None

twitter: Optional[SocialTwitter] = None


class EmailFooter(BaseModel):
inherit_default: Optional[bool] = FieldInfo(alias="inheritDefault", default=None)

markdown: Optional[str] = None
"""The footer body, as markdown.

This is the field the API returns and accepts; it is omitted entirely when no
footer body is set. Sending null is accepted and treated as no footer body.
"""

social: Optional[Social] = None
"""Social links rendered in the email footer."""
56 changes: 53 additions & 3 deletions src/courier/types/email_footer_param.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,60 @@

from .._utils import PropertyInfo

__all__ = ["EmailFooterParam"]
__all__ = [
"EmailFooterParam",
"Social",
"SocialFacebook",
"SocialInstagram",
"SocialLinkedin",
"SocialMedium",
"SocialTwitter",
]


class EmailFooterParam(TypedDict, total=False):
content: Optional[str]
class SocialFacebook(TypedDict, total=False):
url: Optional[str]


class SocialInstagram(TypedDict, total=False):
url: Optional[str]


class SocialLinkedin(TypedDict, total=False):
url: Optional[str]


class SocialMedium(TypedDict, total=False):
url: Optional[str]


class SocialTwitter(TypedDict, total=False):
url: Optional[str]


class Social(TypedDict, total=False):
"""Social links rendered in the email footer."""

facebook: Optional[SocialFacebook]

instagram: Optional[SocialInstagram]

linkedin: Optional[SocialLinkedin]

medium: Optional[SocialMedium]

twitter: Optional[SocialTwitter]


class EmailFooterParam(TypedDict, total=False):
inherit_default: Annotated[Optional[bool], PropertyInfo(alias="inheritDefault")]

markdown: Optional[str]
"""The footer body, as markdown.

This is the field the API returns and accepts; it is omitted entirely when no
footer body is set. Sending null is accepted and treated as no footer body.
"""

social: Optional[Social]
"""Social links rendered in the email footer."""
8 changes: 6 additions & 2 deletions src/courier/types/users/token_add_single_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@ class TokenAddSingleParams(TypedDict, total=False):
"""Information about the device the token came from."""

expiry_date: Union[str, bool, None]
"""ISO 8601 formatted date the token expires.
"""When the token expires.

Defaults to 2 months. Set to false to disable expiration.
Accepts a date, or the boolean `false` to disable expiration entirely. ISO 8601
is recommended (for example `2026-10-25T00:00:00.000Z`). A value that cannot be
parsed as a date is rejected; it is not treated as "no expiration" and does not
fall back to the default. `true` is not a supported value. Omit the field to use
the default, which expires a token that has not been re-registered for 60 days.
"""

properties: object
Expand Down
10 changes: 7 additions & 3 deletions src/courier/types/users/token_update_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from __future__ import annotations

from typing import Iterable, Optional
from typing import Dict, Union, Iterable
from typing_extensions import Required, TypedDict

__all__ = ["TokenUpdateParams", "Patch"]
Expand All @@ -21,5 +21,9 @@ class Patch(TypedDict, total=False):
path: Required[str]
"""The JSON path specifying the part of the profile to operate on."""

value: Optional[str]
"""The value for the operation."""
value: Union[str, bool, Dict[str, object], None]
"""The value for the operation.

A string for most fields; boolean `false` when disabling token expiration via
`expiry_date`, which cannot be expressed as a string.
"""
8 changes: 6 additions & 2 deletions src/courier/types/users/user_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,13 @@ class UserToken(BaseModel):
"""Information about the device the token came from."""

expiry_date: Union[str, bool, None] = None
"""ISO 8601 formatted date the token expires.
"""When the token expires.

Defaults to 2 months. Set to false to disable expiration.
Accepts a date, or the boolean `false` to disable expiration entirely. ISO 8601
is recommended (for example `2026-10-25T00:00:00.000Z`). A value that cannot be
parsed as a date is rejected; it is not treated as "no expiration" and does not
fall back to the default. `true` is not a supported value. Omit the field to use
the default, which expires a token that has not been re-registered for 60 days.
"""

properties: Optional[object] = None
Expand Down
4 changes: 0 additions & 4 deletions src/courier_docs/lib/.keep

This file was deleted.

36 changes: 32 additions & 4 deletions tests/api_resources/test_brands.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,15 @@ def test_method_create_with_all_params(self, client: Courier) -> None:
},
"email": {
"footer": {
"content": "content",
"inherit_default": True,
"markdown": "markdown",
"social": {
"facebook": {"url": "url"},
"instagram": {"url": "url"},
"linkedin": {"url": "url"},
"medium": {"url": "url"},
"twitter": {"url": "url"},
},
},
"head": {
"inherit_default": True,
Expand Down Expand Up @@ -202,8 +209,15 @@ def test_method_update_with_all_params(self, client: Courier) -> None:
},
"email": {
"footer": {
"content": "content",
"inherit_default": True,
"markdown": "markdown",
"social": {
"facebook": {"url": "url"},
"instagram": {"url": "url"},
"linkedin": {"url": "url"},
"medium": {"url": "url"},
"twitter": {"url": "url"},
},
},
"head": {
"inherit_default": True,
Expand Down Expand Up @@ -410,8 +424,15 @@ async def test_method_create_with_all_params(self, async_client: AsyncCourier) -
},
"email": {
"footer": {
"content": "content",
"inherit_default": True,
"markdown": "markdown",
"social": {
"facebook": {"url": "url"},
"instagram": {"url": "url"},
"linkedin": {"url": "url"},
"medium": {"url": "url"},
"twitter": {"url": "url"},
},
},
"head": {
"inherit_default": True,
Expand Down Expand Up @@ -571,8 +592,15 @@ async def test_method_update_with_all_params(self, async_client: AsyncCourier) -
},
"email": {
"footer": {
"content": "content",
"inherit_default": True,
"markdown": "markdown",
"social": {
"facebook": {"url": "url"},
"instagram": {"url": "url"},
"linkedin": {"url": "url"},
"medium": {"url": "url"},
"twitter": {"url": "url"},
},
},
"head": {
"inherit_default": True,
Expand Down