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
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.14.0"
".": "0.15.0"
}
6 changes: 3 additions & 3 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 35
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/ark%2Fark-1949bcfc8775c97eca880428dc93e9f97aa91144bef82584027ede5089bb2e19.yml
openapi_spec_hash: 0aa367455a067b701f18ef7892b6c7e9
config_hash: 373e654f8034a40c42234eee9ebefbb9
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/ark%2Fark-39b91ffd46b6e41924f8465ffaaff6ba3c200a68daa513d4f1eb1e4b29aba78f.yml
openapi_spec_hash: 542dd50007316698c83e8b0bdd5e40e2
config_hash: 77a3908ee910a8019f5831d3a3d53c18
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Changelog

## 0.15.0 (2026-01-30)

Full Changelog: [v0.14.0...v0.15.0](https://github.com/ArkHQ-io/ark-python/compare/v0.14.0...v0.15.0)

### Features

* **api:** api update ([0f5c166](https://github.com/ArkHQ-io/ark-python/commit/0f5c1666d7c02d1f18096610716be6cb2c39a281))
* **api:** api update ([f128894](https://github.com/ArkHQ-io/ark-python/commit/f128894ef84fffa757424b4b1684f7e4eebf4629))
* **api:** manual updates ([bcc7230](https://github.com/ArkHQ-io/ark-python/commit/bcc72308bfbfd6bfd6cefc6df9e1019637ce1b02))
* **api:** manual updates ([378cd65](https://github.com/ArkHQ-io/ark-python/commit/378cd65aadfd41edd36f4d82ad3ea12d95ec8f0c))
* **api:** manual updates ([edb503c](https://github.com/ArkHQ-io/ark-python/commit/edb503c58439ac4face4260cf1e6794cab79f8bb))
* **client:** add custom JSON encoder for extended type support ([ff51eb2](https://github.com/ArkHQ-io/ark-python/commit/ff51eb229a2eda74e3bc8c16d5f7f43758dc5c31))

## 0.14.0 (2026-01-29)

Full Changelog: [v0.13.0...v0.14.0](https://github.com/ArkHQ-io/ark-python/compare/v0.13.0...v0.14.0)
Expand Down
6 changes: 3 additions & 3 deletions api.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ from ark.types import (

Methods:

- <code title="get /emails/{emailId}">client.emails.<a href="./src/ark/resources/emails.py">retrieve</a>(email_id, \*\*<a href="src/ark/types/email_retrieve_params.py">params</a>) -> <a href="./src/ark/types/email_retrieve_response.py">EmailRetrieveResponse</a></code>
- <code title="get /emails/{id}">client.emails.<a href="./src/ark/resources/emails.py">retrieve</a>(id, \*\*<a href="src/ark/types/email_retrieve_params.py">params</a>) -> <a href="./src/ark/types/email_retrieve_response.py">EmailRetrieveResponse</a></code>
- <code title="get /emails">client.emails.<a href="./src/ark/resources/emails.py">list</a>(\*\*<a href="src/ark/types/email_list_params.py">params</a>) -> <a href="./src/ark/types/email_list_response.py">SyncPageNumberPagination[EmailListResponse]</a></code>
- <code title="get /emails/{emailId}/deliveries">client.emails.<a href="./src/ark/resources/emails.py">retrieve_deliveries</a>(email_id) -> <a href="./src/ark/types/email_retrieve_deliveries_response.py">EmailRetrieveDeliveriesResponse</a></code>
- <code title="post /emails/{emailId}/retry">client.emails.<a href="./src/ark/resources/emails.py">retry</a>(email_id) -> <a href="./src/ark/types/email_retry_response.py">EmailRetryResponse</a></code>
- <code title="get /emails/{id}/deliveries">client.emails.<a href="./src/ark/resources/emails.py">retrieve_deliveries</a>(id) -> <a href="./src/ark/types/email_retrieve_deliveries_response.py">EmailRetrieveDeliveriesResponse</a></code>
- <code title="post /emails/{id}/retry">client.emails.<a href="./src/ark/resources/emails.py">retry</a>(id) -> <a href="./src/ark/types/email_retry_response.py">EmailRetryResponse</a></code>
- <code title="post /emails">client.emails.<a href="./src/ark/resources/emails.py">send</a>(\*\*<a href="src/ark/types/email_send_params.py">params</a>) -> <a href="./src/ark/types/email_send_response.py">EmailSendResponse</a></code>
- <code title="post /emails/batch">client.emails.<a href="./src/ark/resources/emails.py">send_batch</a>(\*\*<a href="src/ark/types/email_send_batch_params.py">params</a>) -> <a href="./src/ark/types/email_send_batch_response.py">EmailSendBatchResponse</a></code>
- <code title="post /emails/raw">client.emails.<a href="./src/ark/resources/emails.py">send_raw</a>(\*\*<a href="src/ark/types/email_send_raw_params.py">params</a>) -> <a href="./src/ark/types/email_send_raw_response.py">EmailSendRawResponse</a></code>
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "ark-email"
version = "0.14.0"
version = "0.15.0"
description = "The official Python library for the ark API"
dynamic = ["readme"]
license = "Apache-2.0"
Expand Down
7 changes: 5 additions & 2 deletions src/ark/_base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
APIConnectionError,
APIResponseValidationError,
)
from ._utils._json import openapi_dumps

log: logging.Logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -554,8 +555,10 @@ def _build_request(
kwargs["content"] = options.content
elif isinstance(json_data, bytes):
kwargs["content"] = json_data
else:
kwargs["json"] = json_data if is_given(json_data) else None
elif not files:
# Don't set content when JSON is sent as multipart/form-data,
# since httpx's content param overrides other body arguments
kwargs["content"] = openapi_dumps(json_data) if is_given(json_data) and json_data is not None else None
kwargs["files"] = files
else:
headers.pop("Content-Type", None)
Expand Down
6 changes: 3 additions & 3 deletions src/ark/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ def model_dump(
exclude_defaults: bool = False,
warnings: bool = True,
mode: Literal["json", "python"] = "python",
by_alias: bool | None = None,
) -> dict[str, Any]:
if (not PYDANTIC_V1) or hasattr(model, "model_dump"):
return model.model_dump(
Expand All @@ -148,13 +149,12 @@ def model_dump(
exclude_defaults=exclude_defaults,
# warnings are not supported in Pydantic v1
warnings=True if PYDANTIC_V1 else warnings,
by_alias=by_alias,
)
return cast(
"dict[str, Any]",
model.dict( # pyright: ignore[reportDeprecated, reportUnnecessaryCast]
exclude=exclude,
exclude_unset=exclude_unset,
exclude_defaults=exclude_defaults,
exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, by_alias=bool(by_alias)
),
)

Expand Down
35 changes: 35 additions & 0 deletions src/ark/_utils/_json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import json
from typing import Any
from datetime import datetime
from typing_extensions import override

import pydantic

from .._compat import model_dump


def openapi_dumps(obj: Any) -> bytes:
"""
Serialize an object to UTF-8 encoded JSON bytes.

Extends the standard json.dumps with support for additional types
commonly used in the SDK, such as `datetime`, `pydantic.BaseModel`, etc.
"""
return json.dumps(
obj,
cls=_CustomEncoder,
# Uses the same defaults as httpx's JSON serialization
ensure_ascii=False,
separators=(",", ":"),
allow_nan=False,
).encode()


class _CustomEncoder(json.JSONEncoder):
@override
def default(self, o: Any) -> Any:
if isinstance(o, datetime):
return o.isoformat()
if isinstance(o, pydantic.BaseModel):
return model_dump(o, exclude_unset=True, mode="json", by_alias=True)
return super().default(o)
2 changes: 1 addition & 1 deletion src/ark/_version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

__title__ = "ark"
__version__ = "0.14.0" # x-release-please-version
__version__ = "0.15.0" # x-release-please-version
56 changes: 28 additions & 28 deletions src/ark/resources/emails.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def with_streaming_response(self) -> EmailsResourceWithStreamingResponse:

def retrieve(
self,
email_id: str,
id: str,
*,
expand: str | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
Expand Down Expand Up @@ -96,10 +96,10 @@ def retrieve(

timeout: Override the client-level default timeout for this request, in seconds
"""
if not email_id:
raise ValueError(f"Expected a non-empty value for `email_id` but received {email_id!r}")
if not id:
raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
return self._get(
f"/emails/{email_id}",
f"/emails/{id}",
options=make_request_options(
extra_headers=extra_headers,
extra_query=extra_query,
Expand Down Expand Up @@ -200,7 +200,7 @@ def list(

def retrieve_deliveries(
self,
email_id: str,
id: str,
*,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
Expand Down Expand Up @@ -243,8 +243,8 @@ def retrieve_deliveries(

### Can Retry Manually

Indicates whether you can call `POST /emails/{emailId}/retry` to manually retry
the email. This is `true` when the raw message content is still available (not
Indicates whether you can call `POST /emails/{id}/retry` to manually retry the
email. This is `true` when the raw message content is still available (not
expired due to retention policy).

Args:
Expand All @@ -256,10 +256,10 @@ def retrieve_deliveries(

timeout: Override the client-level default timeout for this request, in seconds
"""
if not email_id:
raise ValueError(f"Expected a non-empty value for `email_id` but received {email_id!r}")
if not id:
raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
return self._get(
f"/emails/{email_id}/deliveries",
f"/emails/{id}/deliveries",
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
Expand All @@ -268,7 +268,7 @@ def retrieve_deliveries(

def retry(
self,
email_id: str,
id: str,
*,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
Expand All @@ -293,10 +293,10 @@ def retry(

timeout: Override the client-level default timeout for this request, in seconds
"""
if not email_id:
raise ValueError(f"Expected a non-empty value for `email_id` but received {email_id!r}")
if not id:
raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
return self._post(
f"/emails/{email_id}/retry",
f"/emails/{id}/retry",
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
Expand Down Expand Up @@ -575,7 +575,7 @@ def with_streaming_response(self) -> AsyncEmailsResourceWithStreamingResponse:

async def retrieve(
self,
email_id: str,
id: str,
*,
expand: str | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
Expand Down Expand Up @@ -612,10 +612,10 @@ async def retrieve(

timeout: Override the client-level default timeout for this request, in seconds
"""
if not email_id:
raise ValueError(f"Expected a non-empty value for `email_id` but received {email_id!r}")
if not id:
raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
return await self._get(
f"/emails/{email_id}",
f"/emails/{id}",
options=make_request_options(
extra_headers=extra_headers,
extra_query=extra_query,
Expand Down Expand Up @@ -716,7 +716,7 @@ def list(

async def retrieve_deliveries(
self,
email_id: str,
id: str,
*,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
Expand Down Expand Up @@ -759,8 +759,8 @@ async def retrieve_deliveries(

### Can Retry Manually

Indicates whether you can call `POST /emails/{emailId}/retry` to manually retry
the email. This is `true` when the raw message content is still available (not
Indicates whether you can call `POST /emails/{id}/retry` to manually retry the
email. This is `true` when the raw message content is still available (not
expired due to retention policy).

Args:
Expand All @@ -772,10 +772,10 @@ async def retrieve_deliveries(

timeout: Override the client-level default timeout for this request, in seconds
"""
if not email_id:
raise ValueError(f"Expected a non-empty value for `email_id` but received {email_id!r}")
if not id:
raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
return await self._get(
f"/emails/{email_id}/deliveries",
f"/emails/{id}/deliveries",
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
Expand All @@ -784,7 +784,7 @@ async def retrieve_deliveries(

async def retry(
self,
email_id: str,
id: str,
*,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
Expand All @@ -809,10 +809,10 @@ async def retry(

timeout: Override the client-level default timeout for this request, in seconds
"""
if not email_id:
raise ValueError(f"Expected a non-empty value for `email_id` but received {email_id!r}")
if not id:
raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
return await self._post(
f"/emails/{email_id}/retry",
f"/emails/{id}/retry",
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
Expand Down
4 changes: 1 addition & 3 deletions src/ark/types/email_list_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@

class EmailListResponse(BaseModel):
id: str
"""Internal message ID"""

token: str
"""Unique message identifier (token)"""

from_: str = FieldInfo(alias="from")

Expand Down
11 changes: 4 additions & 7 deletions src/ark/types/email_retrieve_deliveries_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,12 @@ class DataRetryState(BaseModel):


class Data(BaseModel):
id: str
"""Message identifier (token)"""

can_retry_manually: bool = FieldInfo(alias="canRetryManually")
"""
Whether the message can be manually retried via `POST /emails/{emailId}/retry`.
Whether the message can be manually retried via `POST /emails/{id}/retry`.
`true` when the raw message content is still available (not expired). Messages
older than the retention period cannot be retried.
"""
Expand All @@ -101,12 +104,6 @@ class Data(BaseModel):
SMTP response codes and timestamps.
"""

message_id: int = FieldInfo(alias="messageId")
"""Internal numeric message ID"""

message_token: str = FieldInfo(alias="messageToken")
"""Unique message token for API references"""

retry_state: Optional[DataRetryState] = FieldInfo(alias="retryState", default=None)
"""
Information about the current retry state of a message that is queued for
Expand Down
9 changes: 1 addition & 8 deletions src/ark/types/email_retrieve_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,7 @@ class DataDelivery(BaseModel):

class Data(BaseModel):
id: str
"""Internal message ID"""

token: str
"""
Unique message token used to retrieve this email via API. Combined with id to
form the full message identifier: msg*{id}*{token} Use this token with GET
/emails/{emailId} where emailId = "msg*{id}*{token}"
"""
"""Unique message identifier (token)"""

from_: str = FieldInfo(alias="from")
"""Sender address"""
Expand Down
3 changes: 3 additions & 0 deletions src/ark/types/email_retry_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@


class Data(BaseModel):
id: str
"""Email identifier (token)"""

message: str


Expand Down
4 changes: 1 addition & 3 deletions src/ark/types/email_send_batch_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@

class DataMessages(BaseModel):
id: str
"""Message ID"""

token: str
"""Message identifier (token)"""


class Data(BaseModel):
Expand Down
2 changes: 1 addition & 1 deletion src/ark/types/email_send_raw_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

class Data(BaseModel):
id: str
"""Unique message ID (format: msg*{id}*{token})"""
"""Unique message identifier (token)"""

status: Literal["pending", "sent"]
"""Current delivery status"""
Expand Down
2 changes: 1 addition & 1 deletion src/ark/types/email_send_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

class Data(BaseModel):
id: str
"""Unique message ID (format: msg*{id}*{token})"""
"""Unique message identifier (token)"""

status: Literal["pending", "sent"]
"""Current delivery status"""
Expand Down
Loading