From 56309b8f34212923562d01dbad53df307d1a6a97 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 30 Jan 2026 15:26:31 +0000 Subject: [PATCH 1/3] feat(api): api update --- .stats.yml | 6 +- api.md | 6 - src/ark/resources/emails.py | 363 +----------------- src/ark/resources/webhooks.py | 12 + src/ark/types/__init__.py | 4 - src/ark/types/email_list_response.py | 4 +- .../email_retrieve_deliveries_response.py | 130 ------- src/ark/types/email_retrieve_params.py | 21 - src/ark/types/email_retrieve_response.py | 184 --------- src/ark/types/email_retry_response.py | 23 -- src/ark/types/email_send_batch_response.py | 4 +- src/ark/types/email_send_raw_response.py | 2 +- src/ark/types/email_send_response.py | 2 +- src/ark/types/log_entry.py | 2 +- src/ark/types/webhook_create_params.py | 2 + src/ark/types/webhook_create_response.py | 2 + .../types/webhook_list_deliveries_params.py | 2 + .../types/webhook_list_deliveries_response.py | 2 + .../webhook_retrieve_delivery_response.py | 2 + src/ark/types/webhook_retrieve_response.py | 2 + src/ark/types/webhook_test_params.py | 2 + src/ark/types/webhook_update_response.py | 2 + tests/api_resources/test_emails.py | 247 ------------ 23 files changed, 41 insertions(+), 985 deletions(-) delete mode 100644 src/ark/types/email_retrieve_deliveries_response.py delete mode 100644 src/ark/types/email_retrieve_params.py delete mode 100644 src/ark/types/email_retrieve_response.py delete mode 100644 src/ark/types/email_retry_response.py diff --git a/.stats.yml b/.stats.yml index 69b13d9..631edab 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 35 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/ark%2Fark-39b91ffd46b6e41924f8465ffaaff6ba3c200a68daa513d4f1eb1e4b29aba78f.yml -openapi_spec_hash: 542dd50007316698c83e8b0bdd5e40e2 +configured_endpoints: 32 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/ark%2Fark-65d92724dca2fcf00a43883ad10e0591b457891aa45ed965b0414b27388b5e16.yml +openapi_spec_hash: 42824aaa51f95c0f485c91600969673f config_hash: 77a3908ee910a8019f5831d3a3d53c18 diff --git a/api.md b/api.md index 7baaad3..c296d0a 100644 --- a/api.md +++ b/api.md @@ -10,10 +10,7 @@ Types: ```python from ark.types import ( - EmailRetrieveResponse, EmailListResponse, - EmailRetrieveDeliveriesResponse, - EmailRetryResponse, EmailSendResponse, EmailSendBatchResponse, EmailSendRawResponse, @@ -22,10 +19,7 @@ from ark.types import ( Methods: -- client.emails.retrieve(id, \*\*params) -> EmailRetrieveResponse - client.emails.list(\*\*params) -> SyncPageNumberPagination[EmailListResponse] -- client.emails.retrieve_deliveries(id) -> EmailRetrieveDeliveriesResponse -- client.emails.retry(id) -> EmailRetryResponse - client.emails.send(\*\*params) -> EmailSendResponse - client.emails.send_batch(\*\*params) -> EmailSendBatchResponse - client.emails.send_raw(\*\*params) -> EmailSendRawResponse diff --git a/src/ark/resources/emails.py b/src/ark/resources/emails.py index 4169d3a..d4eea62 100644 --- a/src/ark/resources/emails.py +++ b/src/ark/resources/emails.py @@ -7,13 +7,7 @@ import httpx -from ..types import ( - email_list_params, - email_send_params, - email_retrieve_params, - email_send_raw_params, - email_send_batch_params, -) +from ..types import email_list_params, email_send_params, email_send_raw_params, email_send_batch_params from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given from .._utils import maybe_transform, strip_not_given, async_maybe_transform from .._compat import cached_property @@ -28,11 +22,8 @@ from .._base_client import AsyncPaginator, make_request_options from ..types.email_list_response import EmailListResponse from ..types.email_send_response import EmailSendResponse -from ..types.email_retry_response import EmailRetryResponse -from ..types.email_retrieve_response import EmailRetrieveResponse from ..types.email_send_raw_response import EmailSendRawResponse from ..types.email_send_batch_response import EmailSendBatchResponse -from ..types.email_retrieve_deliveries_response import EmailRetrieveDeliveriesResponse __all__ = ["EmailsResource", "AsyncEmailsResource"] @@ -57,59 +48,6 @@ def with_streaming_response(self) -> EmailsResourceWithStreamingResponse: """ return EmailsResourceWithStreamingResponse(self) - def retrieve( - self, - 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. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> EmailRetrieveResponse: - """ - Retrieve detailed information about a specific email including delivery status, - timestamps, and optionally the email content. - - Use the `expand` parameter to include additional data like the HTML/text body, - headers, or delivery attempts. - - Args: - expand: - Comma-separated list of fields to include: - - - `full` - Include all expanded fields in a single request - - `content` - HTML and plain text body - - `headers` - Email headers - - `deliveries` - Delivery attempt history - - `activity` - Opens and clicks tracking data - - `attachments` - File attachments with content (base64 encoded) - - `raw` - Complete raw MIME message (base64 encoded) - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return self._get( - f"/emails/{id}", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform({"expand": expand}, email_retrieve_params.EmailRetrieveParams), - ), - cast_to=EmailRetrieveResponse, - ) - def list( self, *, @@ -198,111 +136,6 @@ def list( model=EmailListResponse, ) - def retrieve_deliveries( - self, - 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. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> EmailRetrieveDeliveriesResponse: - """ - Get the complete delivery history for an email, including SMTP response codes, - timestamps, and current retry state. - - ## Response Fields - - ### Status - - The current status of the email: - - - `pending` - Awaiting first delivery attempt - - `sent` - Successfully delivered to recipient server - - `softfail` - Temporary failure, automatic retry scheduled - - `hardfail` - Permanent failure, will not retry - - `held` - Held for manual review - - `bounced` - Bounced by recipient server - - ### Retry State - - When the email is in the delivery queue (`pending` or `softfail` status), - `retryState` provides information about the retry schedule: - - - `attempt` - Current attempt number (0 = first attempt) - - `maxAttempts` - Maximum attempts before hard-fail (typically 18) - - `attemptsRemaining` - Attempts left before hard-fail - - `nextRetryAt` - When the next retry is scheduled (Unix timestamp) - - `processing` - Whether the email is currently being processed - - `manual` - Whether this was triggered by a manual retry - - When the email has finished processing (`sent`, `hardfail`, `held`, `bounced`), - `retryState` is `null`. - - ### Can Retry Manually - - 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: - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return self._get( - f"/emails/{id}/deliveries", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=EmailRetrieveDeliveriesResponse, - ) - - def retry( - self, - 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. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> EmailRetryResponse: - """Retry delivery of a failed or soft-bounced email. - - Creates a new delivery - attempt. - - Only works for emails that have failed or are in a retryable state. - - Args: - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return self._post( - f"/emails/{id}/retry", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=EmailRetryResponse, - ) - def send( self, *, @@ -573,59 +406,6 @@ def with_streaming_response(self) -> AsyncEmailsResourceWithStreamingResponse: """ return AsyncEmailsResourceWithStreamingResponse(self) - async def retrieve( - self, - 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. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> EmailRetrieveResponse: - """ - Retrieve detailed information about a specific email including delivery status, - timestamps, and optionally the email content. - - Use the `expand` parameter to include additional data like the HTML/text body, - headers, or delivery attempts. - - Args: - expand: - Comma-separated list of fields to include: - - - `full` - Include all expanded fields in a single request - - `content` - HTML and plain text body - - `headers` - Email headers - - `deliveries` - Delivery attempt history - - `activity` - Opens and clicks tracking data - - `attachments` - File attachments with content (base64 encoded) - - `raw` - Complete raw MIME message (base64 encoded) - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return await self._get( - f"/emails/{id}", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform({"expand": expand}, email_retrieve_params.EmailRetrieveParams), - ), - cast_to=EmailRetrieveResponse, - ) - def list( self, *, @@ -714,111 +494,6 @@ def list( model=EmailListResponse, ) - async def retrieve_deliveries( - self, - 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. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> EmailRetrieveDeliveriesResponse: - """ - Get the complete delivery history for an email, including SMTP response codes, - timestamps, and current retry state. - - ## Response Fields - - ### Status - - The current status of the email: - - - `pending` - Awaiting first delivery attempt - - `sent` - Successfully delivered to recipient server - - `softfail` - Temporary failure, automatic retry scheduled - - `hardfail` - Permanent failure, will not retry - - `held` - Held for manual review - - `bounced` - Bounced by recipient server - - ### Retry State - - When the email is in the delivery queue (`pending` or `softfail` status), - `retryState` provides information about the retry schedule: - - - `attempt` - Current attempt number (0 = first attempt) - - `maxAttempts` - Maximum attempts before hard-fail (typically 18) - - `attemptsRemaining` - Attempts left before hard-fail - - `nextRetryAt` - When the next retry is scheduled (Unix timestamp) - - `processing` - Whether the email is currently being processed - - `manual` - Whether this was triggered by a manual retry - - When the email has finished processing (`sent`, `hardfail`, `held`, `bounced`), - `retryState` is `null`. - - ### Can Retry Manually - - 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: - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return await self._get( - f"/emails/{id}/deliveries", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=EmailRetrieveDeliveriesResponse, - ) - - async def retry( - self, - 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. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> EmailRetryResponse: - """Retry delivery of a failed or soft-bounced email. - - Creates a new delivery - attempt. - - Only works for emails that have failed or are in a retryable state. - - Args: - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return await self._post( - f"/emails/{id}/retry", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=EmailRetryResponse, - ) - async def send( self, *, @@ -1073,18 +748,9 @@ class EmailsResourceWithRawResponse: def __init__(self, emails: EmailsResource) -> None: self._emails = emails - self.retrieve = to_raw_response_wrapper( - emails.retrieve, - ) self.list = to_raw_response_wrapper( emails.list, ) - self.retrieve_deliveries = to_raw_response_wrapper( - emails.retrieve_deliveries, - ) - self.retry = to_raw_response_wrapper( - emails.retry, - ) self.send = to_raw_response_wrapper( emails.send, ) @@ -1100,18 +766,9 @@ class AsyncEmailsResourceWithRawResponse: def __init__(self, emails: AsyncEmailsResource) -> None: self._emails = emails - self.retrieve = async_to_raw_response_wrapper( - emails.retrieve, - ) self.list = async_to_raw_response_wrapper( emails.list, ) - self.retrieve_deliveries = async_to_raw_response_wrapper( - emails.retrieve_deliveries, - ) - self.retry = async_to_raw_response_wrapper( - emails.retry, - ) self.send = async_to_raw_response_wrapper( emails.send, ) @@ -1127,18 +784,9 @@ class EmailsResourceWithStreamingResponse: def __init__(self, emails: EmailsResource) -> None: self._emails = emails - self.retrieve = to_streamed_response_wrapper( - emails.retrieve, - ) self.list = to_streamed_response_wrapper( emails.list, ) - self.retrieve_deliveries = to_streamed_response_wrapper( - emails.retrieve_deliveries, - ) - self.retry = to_streamed_response_wrapper( - emails.retry, - ) self.send = to_streamed_response_wrapper( emails.send, ) @@ -1154,18 +802,9 @@ class AsyncEmailsResourceWithStreamingResponse: def __init__(self, emails: AsyncEmailsResource) -> None: self._emails = emails - self.retrieve = async_to_streamed_response_wrapper( - emails.retrieve, - ) self.list = async_to_streamed_response_wrapper( emails.list, ) - self.retrieve_deliveries = async_to_streamed_response_wrapper( - emails.retrieve_deliveries, - ) - self.retry = async_to_streamed_response_wrapper( - emails.retry, - ) self.send = async_to_streamed_response_wrapper( emails.send, ) diff --git a/src/ark/resources/webhooks.py b/src/ark/resources/webhooks.py index 97b0202..f59179e 100644 --- a/src/ark/resources/webhooks.py +++ b/src/ark/resources/webhooks.py @@ -70,6 +70,8 @@ def create( "MessageLinkClicked", "MessageLoaded", "DomainDNSError", + "SendLimitApproaching", + "SendLimitExceeded", ] ] ] @@ -290,6 +292,8 @@ def list_deliveries( "MessageLinkClicked", "MessageLoaded", "DomainDNSError", + "SendLimitApproaching", + "SendLimitExceeded", ] | Omit = omit, page: int | Omit = omit, @@ -474,6 +478,8 @@ def test( "MessageLinkClicked", "MessageLoaded", "DomainDNSError", + "SendLimitApproaching", + "SendLimitExceeded", ], # 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. @@ -557,6 +563,8 @@ async def create( "MessageLinkClicked", "MessageLoaded", "DomainDNSError", + "SendLimitApproaching", + "SendLimitExceeded", ] ] ] @@ -777,6 +785,8 @@ async def list_deliveries( "MessageLinkClicked", "MessageLoaded", "DomainDNSError", + "SendLimitApproaching", + "SendLimitExceeded", ] | Omit = omit, page: int | Omit = omit, @@ -961,6 +971,8 @@ async def test( "MessageLinkClicked", "MessageLoaded", "DomainDNSError", + "SendLimitApproaching", + "SendLimitExceeded", ], # 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. diff --git a/src/ark/types/__init__.py b/src/ark/types/__init__.py index 6cd75fb..5c42442 100644 --- a/src/ark/types/__init__.py +++ b/src/ark/types/__init__.py @@ -15,8 +15,6 @@ from .webhook_test_params import WebhookTestParams as WebhookTestParams from .domain_create_params import DomainCreateParams as DomainCreateParams from .domain_list_response import DomainListResponse as DomainListResponse -from .email_retry_response import EmailRetryResponse as EmailRetryResponse -from .email_retrieve_params import EmailRetrieveParams as EmailRetrieveParams from .email_send_raw_params import EmailSendRawParams as EmailSendRawParams from .log_retrieve_response import LogRetrieveResponse as LogRetrieveResponse from .webhook_create_params import WebhookCreateParams as WebhookCreateParams @@ -29,7 +27,6 @@ from .tracking_create_params import TrackingCreateParams as TrackingCreateParams from .tracking_list_response import TrackingListResponse as TrackingListResponse from .tracking_update_params import TrackingUpdateParams as TrackingUpdateParams -from .email_retrieve_response import EmailRetrieveResponse as EmailRetrieveResponse from .email_send_batch_params import EmailSendBatchParams as EmailSendBatchParams from .email_send_raw_response import EmailSendRawResponse as EmailSendRawResponse from .suppression_list_params import SuppressionListParams as SuppressionListParams @@ -55,5 +52,4 @@ from .suppression_bulk_create_response import SuppressionBulkCreateResponse as SuppressionBulkCreateResponse from .webhook_list_deliveries_response import WebhookListDeliveriesResponse as WebhookListDeliveriesResponse from .webhook_replay_delivery_response import WebhookReplayDeliveryResponse as WebhookReplayDeliveryResponse -from .email_retrieve_deliveries_response import EmailRetrieveDeliveriesResponse as EmailRetrieveDeliveriesResponse from .webhook_retrieve_delivery_response import WebhookRetrieveDeliveryResponse as WebhookRetrieveDeliveryResponse diff --git a/src/ark/types/email_list_response.py b/src/ark/types/email_list_response.py index dce7d39..f8a1d1c 100644 --- a/src/ark/types/email_list_response.py +++ b/src/ark/types/email_list_response.py @@ -13,7 +13,9 @@ class EmailListResponse(BaseModel): id: str - """Unique message identifier (token)""" + """Internal message ID""" + + token: str from_: str = FieldInfo(alias="from") diff --git a/src/ark/types/email_retrieve_deliveries_response.py b/src/ark/types/email_retrieve_deliveries_response.py deleted file mode 100644 index 6406e9e..0000000 --- a/src/ark/types/email_retrieve_deliveries_response.py +++ /dev/null @@ -1,130 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List, Optional -from datetime import datetime -from typing_extensions import Literal - -from pydantic import Field as FieldInfo - -from .._models import BaseModel -from .shared.api_meta import APIMeta - -__all__ = ["EmailRetrieveDeliveriesResponse", "Data", "DataDelivery", "DataRetryState"] - - -class DataDelivery(BaseModel): - id: str - """Delivery attempt ID""" - - status: str - """Delivery status (lowercase)""" - - timestamp: float - """Unix timestamp""" - - timestamp_iso: datetime = FieldInfo(alias="timestampIso") - """ISO 8601 timestamp""" - - code: Optional[int] = None - """SMTP response code""" - - details: Optional[str] = None - """Status details""" - - output: Optional[str] = None - """SMTP server response from the receiving mail server""" - - sent_with_ssl: Optional[bool] = FieldInfo(alias="sentWithSsl", default=None) - """Whether TLS was used""" - - -class DataRetryState(BaseModel): - """ - Information about the current retry state of a message that is queued for delivery. - Only present when the message is in the delivery queue. - """ - - attempt: int - """Current attempt number (0-indexed). - - The first delivery attempt is 0, the first retry is 1, and so on. - """ - - attempts_remaining: int = FieldInfo(alias="attemptsRemaining") - """ - Number of attempts remaining before the message is hard-failed. Calculated as - `maxAttempts - attempt`. - """ - - manual: bool - """ - Whether this queue entry was created by a manual retry request. Manual retries - bypass certain hold conditions like suppression lists. - """ - - max_attempts: int = FieldInfo(alias="maxAttempts") - """ - Maximum number of delivery attempts before the message is hard-failed. - Configured at the server level. - """ - - processing: bool - """ - Whether the message is currently being processed by a delivery worker. When - `true`, the message is actively being sent. - """ - - next_retry_at: Optional[float] = FieldInfo(alias="nextRetryAt", default=None) - """ - Unix timestamp of when the next retry attempt is scheduled. `null` if the - message is ready for immediate processing or currently being processed. - """ - - next_retry_at_iso: Optional[datetime] = FieldInfo(alias="nextRetryAtIso", default=None) - """ - ISO 8601 formatted timestamp of the next retry attempt. `null` if the message is - ready for immediate processing. - """ - - -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/{id}/retry`. - `true` when the raw message content is still available (not expired). Messages - older than the retention period cannot be retried. - """ - - deliveries: List[DataDelivery] - """ - Chronological list of delivery attempts for this message. Each attempt includes - SMTP response codes and timestamps. - """ - - retry_state: Optional[DataRetryState] = FieldInfo(alias="retryState", default=None) - """ - Information about the current retry state of a message that is queued for - delivery. Only present when the message is in the delivery queue. - """ - - status: Literal["pending", "sent", "softfail", "hardfail", "held", "bounced"] - """Current message status (lowercase). Possible values: - - - `pending` - Initial state, awaiting first delivery attempt - - `sent` - Successfully delivered - - `softfail` - Temporary failure, will retry automatically - - `hardfail` - Permanent failure, will not retry - - `held` - Held for manual review (suppression list, etc.) - - `bounced` - Bounced by recipient server - """ - - -class EmailRetrieveDeliveriesResponse(BaseModel): - data: Data - - meta: APIMeta - - success: Literal[True] diff --git a/src/ark/types/email_retrieve_params.py b/src/ark/types/email_retrieve_params.py deleted file mode 100644 index 5d3fc43..0000000 --- a/src/ark/types/email_retrieve_params.py +++ /dev/null @@ -1,21 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import TypedDict - -__all__ = ["EmailRetrieveParams"] - - -class EmailRetrieveParams(TypedDict, total=False): - expand: str - """Comma-separated list of fields to include: - - - `full` - Include all expanded fields in a single request - - `content` - HTML and plain text body - - `headers` - Email headers - - `deliveries` - Delivery attempt history - - `activity` - Opens and clicks tracking data - - `attachments` - File attachments with content (base64 encoded) - - `raw` - Complete raw MIME message (base64 encoded) - """ diff --git a/src/ark/types/email_retrieve_response.py b/src/ark/types/email_retrieve_response.py deleted file mode 100644 index f60801e..0000000 --- a/src/ark/types/email_retrieve_response.py +++ /dev/null @@ -1,184 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Dict, List, Optional -from datetime import datetime -from typing_extensions import Literal - -from pydantic import Field as FieldInfo - -from .._models import BaseModel -from .shared.api_meta import APIMeta - -__all__ = [ - "EmailRetrieveResponse", - "Data", - "DataActivity", - "DataActivityClick", - "DataActivityOpen", - "DataAttachment", - "DataDelivery", -] - - -class DataActivityClick(BaseModel): - ip_address: Optional[str] = FieldInfo(alias="ipAddress", default=None) - """IP address of the clicker""" - - timestamp: Optional[float] = None - """Unix timestamp of the click event""" - - timestamp_iso: Optional[datetime] = FieldInfo(alias="timestampIso", default=None) - """ISO 8601 timestamp of the click event""" - - url: Optional[str] = None - """URL that was clicked""" - - user_agent: Optional[str] = FieldInfo(alias="userAgent", default=None) - """User agent of the email client""" - - -class DataActivityOpen(BaseModel): - ip_address: Optional[str] = FieldInfo(alias="ipAddress", default=None) - """IP address of the opener""" - - timestamp: Optional[float] = None - """Unix timestamp of the open event""" - - timestamp_iso: Optional[datetime] = FieldInfo(alias="timestampIso", default=None) - """ISO 8601 timestamp of the open event""" - - user_agent: Optional[str] = FieldInfo(alias="userAgent", default=None) - """User agent of the email client""" - - -class DataActivity(BaseModel): - """Opens and clicks tracking data (included if expand=activity)""" - - clicks: Optional[List[DataActivityClick]] = None - """List of link click events""" - - opens: Optional[List[DataActivityOpen]] = None - """List of email open events""" - - -class DataAttachment(BaseModel): - """An email attachment retrieved from a sent message""" - - content_type: str = FieldInfo(alias="contentType") - """MIME type of the attachment""" - - data: str - """Base64 encoded attachment content. Decode this to get the raw file bytes.""" - - filename: str - """Original filename of the attachment""" - - hash: str - """SHA256 hash of the attachment content for verification""" - - size: int - """Size of the attachment in bytes""" - - -class DataDelivery(BaseModel): - id: str - """Delivery attempt ID""" - - status: str - """Delivery status (lowercase)""" - - timestamp: float - """Unix timestamp""" - - timestamp_iso: datetime = FieldInfo(alias="timestampIso") - """ISO 8601 timestamp""" - - code: Optional[int] = None - """SMTP response code""" - - details: Optional[str] = None - """Status details""" - - output: Optional[str] = None - """SMTP server response from the receiving mail server""" - - sent_with_ssl: Optional[bool] = FieldInfo(alias="sentWithSsl", default=None) - """Whether TLS was used""" - - -class Data(BaseModel): - id: str - """Unique message identifier (token)""" - - from_: str = FieldInfo(alias="from") - """Sender address""" - - scope: Literal["outgoing", "incoming"] - """Message direction""" - - status: Literal["pending", "sent", "softfail", "hardfail", "bounced", "held"] - """Current delivery status: - - - `pending` - Email accepted, waiting to be processed - - `sent` - Email transmitted to recipient's mail server - - `softfail` - Temporary delivery failure, will retry - - `hardfail` - Permanent delivery failure - - `bounced` - Email bounced back - - `held` - Held for manual review - """ - - subject: str - """Email subject line""" - - timestamp: float - """Unix timestamp when the email was sent""" - - timestamp_iso: datetime = FieldInfo(alias="timestampIso") - """ISO 8601 formatted timestamp""" - - to: str - """Recipient address""" - - activity: Optional[DataActivity] = None - """Opens and clicks tracking data (included if expand=activity)""" - - attachments: Optional[List[DataAttachment]] = None - """File attachments (included if expand=attachments)""" - - deliveries: Optional[List[DataDelivery]] = None - """Delivery attempt history (included if expand=deliveries)""" - - headers: Optional[Dict[str, str]] = None - """Email headers (included if expand=headers)""" - - html_body: Optional[str] = FieldInfo(alias="htmlBody", default=None) - """HTML body content (included if expand=content)""" - - message_id: Optional[str] = FieldInfo(alias="messageId", default=None) - """SMTP Message-ID header""" - - plain_body: Optional[str] = FieldInfo(alias="plainBody", default=None) - """Plain text body (included if expand=content)""" - - raw_message: Optional[str] = FieldInfo(alias="rawMessage", default=None) - """ - Complete raw MIME message, base64 encoded (included if expand=raw). Decode this - to get the original RFC 2822 formatted email. - """ - - spam: Optional[bool] = None - """Whether the message was flagged as spam""" - - spam_score: Optional[float] = FieldInfo(alias="spamScore", default=None) - """Spam score (if applicable)""" - - tag: Optional[str] = None - """Optional categorization tag""" - - -class EmailRetrieveResponse(BaseModel): - data: Data - - meta: APIMeta - - success: Literal[True] diff --git a/src/ark/types/email_retry_response.py b/src/ark/types/email_retry_response.py deleted file mode 100644 index f63cc99..0000000 --- a/src/ark/types/email_retry_response.py +++ /dev/null @@ -1,23 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing_extensions import Literal - -from .._models import BaseModel -from .shared.api_meta import APIMeta - -__all__ = ["EmailRetryResponse", "Data"] - - -class Data(BaseModel): - id: str - """Email identifier (token)""" - - message: str - - -class EmailRetryResponse(BaseModel): - data: Data - - meta: APIMeta - - success: Literal[True] diff --git a/src/ark/types/email_send_batch_response.py b/src/ark/types/email_send_batch_response.py index d32e3fc..272119c 100644 --- a/src/ark/types/email_send_batch_response.py +++ b/src/ark/types/email_send_batch_response.py @@ -11,7 +11,9 @@ class DataMessages(BaseModel): id: str - """Message identifier (token)""" + """Message ID""" + + token: str class Data(BaseModel): diff --git a/src/ark/types/email_send_raw_response.py b/src/ark/types/email_send_raw_response.py index 46e7c96..f1e8c03 100644 --- a/src/ark/types/email_send_raw_response.py +++ b/src/ark/types/email_send_raw_response.py @@ -13,7 +13,7 @@ class Data(BaseModel): id: str - """Unique message identifier (token)""" + """Unique message ID (format: msg*{id}*{token})""" status: Literal["pending", "sent"] """Current delivery status""" diff --git a/src/ark/types/email_send_response.py b/src/ark/types/email_send_response.py index cb7d814..39e1d36 100644 --- a/src/ark/types/email_send_response.py +++ b/src/ark/types/email_send_response.py @@ -13,7 +13,7 @@ class Data(BaseModel): id: str - """Unique message identifier (token)""" + """Unique message ID (format: msg*{id}*{token})""" status: Literal["pending", "sent"] """Current delivery status""" diff --git a/src/ark/types/log_entry.py b/src/ark/types/log_entry.py index 4dfdd6c..9fad7e7 100644 --- a/src/ark/types/log_entry.py +++ b/src/ark/types/log_entry.py @@ -57,7 +57,7 @@ class Email(BaseModel): """Email-specific data (for email endpoints)""" id: Optional[str] = None - """Email message identifier (token)""" + """Email message ID""" recipient_count: Optional[int] = FieldInfo(alias="recipientCount", default=None) """Number of recipients""" diff --git a/src/ark/types/webhook_create_params.py b/src/ark/types/webhook_create_params.py index 9899826..8b7a63e 100644 --- a/src/ark/types/webhook_create_params.py +++ b/src/ark/types/webhook_create_params.py @@ -34,6 +34,8 @@ class WebhookCreateParams(TypedDict, total=False): "MessageLinkClicked", "MessageLoaded", "DomainDNSError", + "SendLimitApproaching", + "SendLimitExceeded", ] ] ] diff --git a/src/ark/types/webhook_create_response.py b/src/ark/types/webhook_create_response.py index d78bb58..07f75ec 100644 --- a/src/ark/types/webhook_create_response.py +++ b/src/ark/types/webhook_create_response.py @@ -34,6 +34,8 @@ class Data(BaseModel): "MessageLinkClicked", "MessageLoaded", "DomainDNSError", + "SendLimitApproaching", + "SendLimitExceeded", ] ] """Subscribed events""" diff --git a/src/ark/types/webhook_list_deliveries_params.py b/src/ark/types/webhook_list_deliveries_params.py index c4048e4..cce2c61 100644 --- a/src/ark/types/webhook_list_deliveries_params.py +++ b/src/ark/types/webhook_list_deliveries_params.py @@ -25,6 +25,8 @@ class WebhookListDeliveriesParams(TypedDict, total=False): "MessageLinkClicked", "MessageLoaded", "DomainDNSError", + "SendLimitApproaching", + "SendLimitExceeded", ] """Filter by event type""" diff --git a/src/ark/types/webhook_list_deliveries_response.py b/src/ark/types/webhook_list_deliveries_response.py index 71a5fe2..cb5033d 100644 --- a/src/ark/types/webhook_list_deliveries_response.py +++ b/src/ark/types/webhook_list_deliveries_response.py @@ -30,6 +30,8 @@ class Data(BaseModel): "MessageLinkClicked", "MessageLoaded", "DomainDNSError", + "SendLimitApproaching", + "SendLimitExceeded", ] """Event type that triggered this delivery""" diff --git a/src/ark/types/webhook_retrieve_delivery_response.py b/src/ark/types/webhook_retrieve_delivery_response.py index efec5c4..9a0c38b 100644 --- a/src/ark/types/webhook_retrieve_delivery_response.py +++ b/src/ark/types/webhook_retrieve_delivery_response.py @@ -50,6 +50,8 @@ class Data(BaseModel): "MessageLinkClicked", "MessageLoaded", "DomainDNSError", + "SendLimitApproaching", + "SendLimitExceeded", ] """Event type that triggered this delivery""" diff --git a/src/ark/types/webhook_retrieve_response.py b/src/ark/types/webhook_retrieve_response.py index 6cb2f2e..db780ca 100644 --- a/src/ark/types/webhook_retrieve_response.py +++ b/src/ark/types/webhook_retrieve_response.py @@ -34,6 +34,8 @@ class Data(BaseModel): "MessageLinkClicked", "MessageLoaded", "DomainDNSError", + "SendLimitApproaching", + "SendLimitExceeded", ] ] """Subscribed events""" diff --git a/src/ark/types/webhook_test_params.py b/src/ark/types/webhook_test_params.py index 3f91fca..5648540 100644 --- a/src/ark/types/webhook_test_params.py +++ b/src/ark/types/webhook_test_params.py @@ -18,6 +18,8 @@ class WebhookTestParams(TypedDict, total=False): "MessageLinkClicked", "MessageLoaded", "DomainDNSError", + "SendLimitApproaching", + "SendLimitExceeded", ] ] """Event type to simulate""" diff --git a/src/ark/types/webhook_update_response.py b/src/ark/types/webhook_update_response.py index 4d9a0c6..75c6d59 100644 --- a/src/ark/types/webhook_update_response.py +++ b/src/ark/types/webhook_update_response.py @@ -34,6 +34,8 @@ class Data(BaseModel): "MessageLinkClicked", "MessageLoaded", "DomainDNSError", + "SendLimitApproaching", + "SendLimitExceeded", ] ] """Subscribed events""" diff --git a/tests/api_resources/test_emails.py b/tests/api_resources/test_emails.py index 7a54df7..e416353 100644 --- a/tests/api_resources/test_emails.py +++ b/tests/api_resources/test_emails.py @@ -11,11 +11,8 @@ from ark.types import ( EmailListResponse, EmailSendResponse, - EmailRetryResponse, EmailSendRawResponse, - EmailRetrieveResponse, EmailSendBatchResponse, - EmailRetrieveDeliveriesResponse, ) from tests.utils import assert_matches_type from ark.pagination import SyncPageNumberPagination, AsyncPageNumberPagination @@ -26,52 +23,6 @@ class TestEmails: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @parametrize - def test_method_retrieve(self, client: Ark) -> None: - email = client.emails.retrieve( - id="aBc123XyZ", - ) - assert_matches_type(EmailRetrieveResponse, email, path=["response"]) - - @parametrize - def test_method_retrieve_with_all_params(self, client: Ark) -> None: - email = client.emails.retrieve( - id="aBc123XyZ", - expand="full", - ) - assert_matches_type(EmailRetrieveResponse, email, path=["response"]) - - @parametrize - def test_raw_response_retrieve(self, client: Ark) -> None: - response = client.emails.with_raw_response.retrieve( - id="aBc123XyZ", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - email = response.parse() - assert_matches_type(EmailRetrieveResponse, email, path=["response"]) - - @parametrize - def test_streaming_response_retrieve(self, client: Ark) -> None: - with client.emails.with_streaming_response.retrieve( - id="aBc123XyZ", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - email = response.parse() - assert_matches_type(EmailRetrieveResponse, email, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - def test_path_params_retrieve(self, client: Ark) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.emails.with_raw_response.retrieve( - id="", - ) - @parametrize def test_method_list(self, client: Ark) -> None: email = client.emails.list() @@ -111,82 +62,6 @@ def test_streaming_response_list(self, client: Ark) -> None: assert cast(Any, response.is_closed) is True - @parametrize - def test_method_retrieve_deliveries(self, client: Ark) -> None: - email = client.emails.retrieve_deliveries( - "aBc123XyZ", - ) - assert_matches_type(EmailRetrieveDeliveriesResponse, email, path=["response"]) - - @parametrize - def test_raw_response_retrieve_deliveries(self, client: Ark) -> None: - response = client.emails.with_raw_response.retrieve_deliveries( - "aBc123XyZ", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - email = response.parse() - assert_matches_type(EmailRetrieveDeliveriesResponse, email, path=["response"]) - - @parametrize - def test_streaming_response_retrieve_deliveries(self, client: Ark) -> None: - with client.emails.with_streaming_response.retrieve_deliveries( - "aBc123XyZ", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - email = response.parse() - assert_matches_type(EmailRetrieveDeliveriesResponse, email, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - def test_path_params_retrieve_deliveries(self, client: Ark) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.emails.with_raw_response.retrieve_deliveries( - "", - ) - - @parametrize - def test_method_retry(self, client: Ark) -> None: - email = client.emails.retry( - "aBc123XyZ", - ) - assert_matches_type(EmailRetryResponse, email, path=["response"]) - - @parametrize - def test_raw_response_retry(self, client: Ark) -> None: - response = client.emails.with_raw_response.retry( - "aBc123XyZ", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - email = response.parse() - assert_matches_type(EmailRetryResponse, email, path=["response"]) - - @parametrize - def test_streaming_response_retry(self, client: Ark) -> None: - with client.emails.with_streaming_response.retry( - "aBc123XyZ", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - email = response.parse() - assert_matches_type(EmailRetryResponse, email, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - def test_path_params_retry(self, client: Ark) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.emails.with_raw_response.retry( - "", - ) - @parametrize def test_method_send(self, client: Ark) -> None: email = client.emails.send( @@ -400,52 +275,6 @@ class TestAsyncEmails: "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) - @parametrize - async def test_method_retrieve(self, async_client: AsyncArk) -> None: - email = await async_client.emails.retrieve( - id="aBc123XyZ", - ) - assert_matches_type(EmailRetrieveResponse, email, path=["response"]) - - @parametrize - async def test_method_retrieve_with_all_params(self, async_client: AsyncArk) -> None: - email = await async_client.emails.retrieve( - id="aBc123XyZ", - expand="full", - ) - assert_matches_type(EmailRetrieveResponse, email, path=["response"]) - - @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncArk) -> None: - response = await async_client.emails.with_raw_response.retrieve( - id="aBc123XyZ", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - email = await response.parse() - assert_matches_type(EmailRetrieveResponse, email, path=["response"]) - - @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncArk) -> None: - async with async_client.emails.with_streaming_response.retrieve( - id="aBc123XyZ", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - email = await response.parse() - assert_matches_type(EmailRetrieveResponse, email, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - async def test_path_params_retrieve(self, async_client: AsyncArk) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.emails.with_raw_response.retrieve( - id="", - ) - @parametrize async def test_method_list(self, async_client: AsyncArk) -> None: email = await async_client.emails.list() @@ -485,82 +314,6 @@ async def test_streaming_response_list(self, async_client: AsyncArk) -> None: assert cast(Any, response.is_closed) is True - @parametrize - async def test_method_retrieve_deliveries(self, async_client: AsyncArk) -> None: - email = await async_client.emails.retrieve_deliveries( - "aBc123XyZ", - ) - assert_matches_type(EmailRetrieveDeliveriesResponse, email, path=["response"]) - - @parametrize - async def test_raw_response_retrieve_deliveries(self, async_client: AsyncArk) -> None: - response = await async_client.emails.with_raw_response.retrieve_deliveries( - "aBc123XyZ", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - email = await response.parse() - assert_matches_type(EmailRetrieveDeliveriesResponse, email, path=["response"]) - - @parametrize - async def test_streaming_response_retrieve_deliveries(self, async_client: AsyncArk) -> None: - async with async_client.emails.with_streaming_response.retrieve_deliveries( - "aBc123XyZ", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - email = await response.parse() - assert_matches_type(EmailRetrieveDeliveriesResponse, email, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - async def test_path_params_retrieve_deliveries(self, async_client: AsyncArk) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.emails.with_raw_response.retrieve_deliveries( - "", - ) - - @parametrize - async def test_method_retry(self, async_client: AsyncArk) -> None: - email = await async_client.emails.retry( - "aBc123XyZ", - ) - assert_matches_type(EmailRetryResponse, email, path=["response"]) - - @parametrize - async def test_raw_response_retry(self, async_client: AsyncArk) -> None: - response = await async_client.emails.with_raw_response.retry( - "aBc123XyZ", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - email = await response.parse() - assert_matches_type(EmailRetryResponse, email, path=["response"]) - - @parametrize - async def test_streaming_response_retry(self, async_client: AsyncArk) -> None: - async with async_client.emails.with_streaming_response.retry( - "aBc123XyZ", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - email = await response.parse() - assert_matches_type(EmailRetryResponse, email, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - async def test_path_params_retry(self, async_client: AsyncArk) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.emails.with_raw_response.retry( - "", - ) - @parametrize async def test_method_send(self, async_client: AsyncArk) -> None: email = await async_client.emails.send( From a6fdf54cba7744137502a2e97f9105ef204f4d79 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 30 Jan 2026 15:29:37 +0000 Subject: [PATCH 2/3] feat(api): manual updates --- .stats.yml | 6 +- api.md | 6 + src/ark/resources/emails.py | 363 +++++++++++++++++- src/ark/resources/webhooks.py | 12 - src/ark/types/__init__.py | 4 + src/ark/types/email_list_response.py | 4 +- .../email_retrieve_deliveries_response.py | 130 +++++++ src/ark/types/email_retrieve_params.py | 21 + src/ark/types/email_retrieve_response.py | 184 +++++++++ src/ark/types/email_retry_response.py | 23 ++ src/ark/types/email_send_batch_response.py | 4 +- src/ark/types/email_send_raw_response.py | 2 +- src/ark/types/email_send_response.py | 2 +- src/ark/types/log_entry.py | 2 +- src/ark/types/webhook_create_params.py | 2 - src/ark/types/webhook_create_response.py | 2 - .../types/webhook_list_deliveries_params.py | 2 - .../types/webhook_list_deliveries_response.py | 2 - .../webhook_retrieve_delivery_response.py | 2 - src/ark/types/webhook_retrieve_response.py | 2 - src/ark/types/webhook_test_params.py | 2 - src/ark/types/webhook_update_response.py | 2 - tests/api_resources/test_emails.py | 247 ++++++++++++ 23 files changed, 985 insertions(+), 41 deletions(-) create mode 100644 src/ark/types/email_retrieve_deliveries_response.py create mode 100644 src/ark/types/email_retrieve_params.py create mode 100644 src/ark/types/email_retrieve_response.py create mode 100644 src/ark/types/email_retry_response.py diff --git a/.stats.yml b/.stats.yml index 631edab..69b13d9 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 32 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/ark%2Fark-65d92724dca2fcf00a43883ad10e0591b457891aa45ed965b0414b27388b5e16.yml -openapi_spec_hash: 42824aaa51f95c0f485c91600969673f +configured_endpoints: 35 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/ark%2Fark-39b91ffd46b6e41924f8465ffaaff6ba3c200a68daa513d4f1eb1e4b29aba78f.yml +openapi_spec_hash: 542dd50007316698c83e8b0bdd5e40e2 config_hash: 77a3908ee910a8019f5831d3a3d53c18 diff --git a/api.md b/api.md index c296d0a..7baaad3 100644 --- a/api.md +++ b/api.md @@ -10,7 +10,10 @@ Types: ```python from ark.types import ( + EmailRetrieveResponse, EmailListResponse, + EmailRetrieveDeliveriesResponse, + EmailRetryResponse, EmailSendResponse, EmailSendBatchResponse, EmailSendRawResponse, @@ -19,7 +22,10 @@ from ark.types import ( Methods: +- client.emails.retrieve(id, \*\*params) -> EmailRetrieveResponse - client.emails.list(\*\*params) -> SyncPageNumberPagination[EmailListResponse] +- client.emails.retrieve_deliveries(id) -> EmailRetrieveDeliveriesResponse +- client.emails.retry(id) -> EmailRetryResponse - client.emails.send(\*\*params) -> EmailSendResponse - client.emails.send_batch(\*\*params) -> EmailSendBatchResponse - client.emails.send_raw(\*\*params) -> EmailSendRawResponse diff --git a/src/ark/resources/emails.py b/src/ark/resources/emails.py index d4eea62..4169d3a 100644 --- a/src/ark/resources/emails.py +++ b/src/ark/resources/emails.py @@ -7,7 +7,13 @@ import httpx -from ..types import email_list_params, email_send_params, email_send_raw_params, email_send_batch_params +from ..types import ( + email_list_params, + email_send_params, + email_retrieve_params, + email_send_raw_params, + email_send_batch_params, +) from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given from .._utils import maybe_transform, strip_not_given, async_maybe_transform from .._compat import cached_property @@ -22,8 +28,11 @@ from .._base_client import AsyncPaginator, make_request_options from ..types.email_list_response import EmailListResponse from ..types.email_send_response import EmailSendResponse +from ..types.email_retry_response import EmailRetryResponse +from ..types.email_retrieve_response import EmailRetrieveResponse from ..types.email_send_raw_response import EmailSendRawResponse from ..types.email_send_batch_response import EmailSendBatchResponse +from ..types.email_retrieve_deliveries_response import EmailRetrieveDeliveriesResponse __all__ = ["EmailsResource", "AsyncEmailsResource"] @@ -48,6 +57,59 @@ def with_streaming_response(self) -> EmailsResourceWithStreamingResponse: """ return EmailsResourceWithStreamingResponse(self) + def retrieve( + self, + 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. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> EmailRetrieveResponse: + """ + Retrieve detailed information about a specific email including delivery status, + timestamps, and optionally the email content. + + Use the `expand` parameter to include additional data like the HTML/text body, + headers, or delivery attempts. + + Args: + expand: + Comma-separated list of fields to include: + + - `full` - Include all expanded fields in a single request + - `content` - HTML and plain text body + - `headers` - Email headers + - `deliveries` - Delivery attempt history + - `activity` - Opens and clicks tracking data + - `attachments` - File attachments with content (base64 encoded) + - `raw` - Complete raw MIME message (base64 encoded) + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._get( + f"/emails/{id}", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform({"expand": expand}, email_retrieve_params.EmailRetrieveParams), + ), + cast_to=EmailRetrieveResponse, + ) + def list( self, *, @@ -136,6 +198,111 @@ def list( model=EmailListResponse, ) + def retrieve_deliveries( + self, + 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. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> EmailRetrieveDeliveriesResponse: + """ + Get the complete delivery history for an email, including SMTP response codes, + timestamps, and current retry state. + + ## Response Fields + + ### Status + + The current status of the email: + + - `pending` - Awaiting first delivery attempt + - `sent` - Successfully delivered to recipient server + - `softfail` - Temporary failure, automatic retry scheduled + - `hardfail` - Permanent failure, will not retry + - `held` - Held for manual review + - `bounced` - Bounced by recipient server + + ### Retry State + + When the email is in the delivery queue (`pending` or `softfail` status), + `retryState` provides information about the retry schedule: + + - `attempt` - Current attempt number (0 = first attempt) + - `maxAttempts` - Maximum attempts before hard-fail (typically 18) + - `attemptsRemaining` - Attempts left before hard-fail + - `nextRetryAt` - When the next retry is scheduled (Unix timestamp) + - `processing` - Whether the email is currently being processed + - `manual` - Whether this was triggered by a manual retry + + When the email has finished processing (`sent`, `hardfail`, `held`, `bounced`), + `retryState` is `null`. + + ### Can Retry Manually + + 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: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._get( + f"/emails/{id}/deliveries", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=EmailRetrieveDeliveriesResponse, + ) + + def retry( + self, + 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. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> EmailRetryResponse: + """Retry delivery of a failed or soft-bounced email. + + Creates a new delivery + attempt. + + Only works for emails that have failed or are in a retryable state. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._post( + f"/emails/{id}/retry", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=EmailRetryResponse, + ) + def send( self, *, @@ -406,6 +573,59 @@ def with_streaming_response(self) -> AsyncEmailsResourceWithStreamingResponse: """ return AsyncEmailsResourceWithStreamingResponse(self) + async def retrieve( + self, + 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. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> EmailRetrieveResponse: + """ + Retrieve detailed information about a specific email including delivery status, + timestamps, and optionally the email content. + + Use the `expand` parameter to include additional data like the HTML/text body, + headers, or delivery attempts. + + Args: + expand: + Comma-separated list of fields to include: + + - `full` - Include all expanded fields in a single request + - `content` - HTML and plain text body + - `headers` - Email headers + - `deliveries` - Delivery attempt history + - `activity` - Opens and clicks tracking data + - `attachments` - File attachments with content (base64 encoded) + - `raw` - Complete raw MIME message (base64 encoded) + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._get( + f"/emails/{id}", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform({"expand": expand}, email_retrieve_params.EmailRetrieveParams), + ), + cast_to=EmailRetrieveResponse, + ) + def list( self, *, @@ -494,6 +714,111 @@ def list( model=EmailListResponse, ) + async def retrieve_deliveries( + self, + 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. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> EmailRetrieveDeliveriesResponse: + """ + Get the complete delivery history for an email, including SMTP response codes, + timestamps, and current retry state. + + ## Response Fields + + ### Status + + The current status of the email: + + - `pending` - Awaiting first delivery attempt + - `sent` - Successfully delivered to recipient server + - `softfail` - Temporary failure, automatic retry scheduled + - `hardfail` - Permanent failure, will not retry + - `held` - Held for manual review + - `bounced` - Bounced by recipient server + + ### Retry State + + When the email is in the delivery queue (`pending` or `softfail` status), + `retryState` provides information about the retry schedule: + + - `attempt` - Current attempt number (0 = first attempt) + - `maxAttempts` - Maximum attempts before hard-fail (typically 18) + - `attemptsRemaining` - Attempts left before hard-fail + - `nextRetryAt` - When the next retry is scheduled (Unix timestamp) + - `processing` - Whether the email is currently being processed + - `manual` - Whether this was triggered by a manual retry + + When the email has finished processing (`sent`, `hardfail`, `held`, `bounced`), + `retryState` is `null`. + + ### Can Retry Manually + + 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: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._get( + f"/emails/{id}/deliveries", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=EmailRetrieveDeliveriesResponse, + ) + + async def retry( + self, + 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. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> EmailRetryResponse: + """Retry delivery of a failed or soft-bounced email. + + Creates a new delivery + attempt. + + Only works for emails that have failed or are in a retryable state. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._post( + f"/emails/{id}/retry", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=EmailRetryResponse, + ) + async def send( self, *, @@ -748,9 +1073,18 @@ class EmailsResourceWithRawResponse: def __init__(self, emails: EmailsResource) -> None: self._emails = emails + self.retrieve = to_raw_response_wrapper( + emails.retrieve, + ) self.list = to_raw_response_wrapper( emails.list, ) + self.retrieve_deliveries = to_raw_response_wrapper( + emails.retrieve_deliveries, + ) + self.retry = to_raw_response_wrapper( + emails.retry, + ) self.send = to_raw_response_wrapper( emails.send, ) @@ -766,9 +1100,18 @@ class AsyncEmailsResourceWithRawResponse: def __init__(self, emails: AsyncEmailsResource) -> None: self._emails = emails + self.retrieve = async_to_raw_response_wrapper( + emails.retrieve, + ) self.list = async_to_raw_response_wrapper( emails.list, ) + self.retrieve_deliveries = async_to_raw_response_wrapper( + emails.retrieve_deliveries, + ) + self.retry = async_to_raw_response_wrapper( + emails.retry, + ) self.send = async_to_raw_response_wrapper( emails.send, ) @@ -784,9 +1127,18 @@ class EmailsResourceWithStreamingResponse: def __init__(self, emails: EmailsResource) -> None: self._emails = emails + self.retrieve = to_streamed_response_wrapper( + emails.retrieve, + ) self.list = to_streamed_response_wrapper( emails.list, ) + self.retrieve_deliveries = to_streamed_response_wrapper( + emails.retrieve_deliveries, + ) + self.retry = to_streamed_response_wrapper( + emails.retry, + ) self.send = to_streamed_response_wrapper( emails.send, ) @@ -802,9 +1154,18 @@ class AsyncEmailsResourceWithStreamingResponse: def __init__(self, emails: AsyncEmailsResource) -> None: self._emails = emails + self.retrieve = async_to_streamed_response_wrapper( + emails.retrieve, + ) self.list = async_to_streamed_response_wrapper( emails.list, ) + self.retrieve_deliveries = async_to_streamed_response_wrapper( + emails.retrieve_deliveries, + ) + self.retry = async_to_streamed_response_wrapper( + emails.retry, + ) self.send = async_to_streamed_response_wrapper( emails.send, ) diff --git a/src/ark/resources/webhooks.py b/src/ark/resources/webhooks.py index f59179e..97b0202 100644 --- a/src/ark/resources/webhooks.py +++ b/src/ark/resources/webhooks.py @@ -70,8 +70,6 @@ def create( "MessageLinkClicked", "MessageLoaded", "DomainDNSError", - "SendLimitApproaching", - "SendLimitExceeded", ] ] ] @@ -292,8 +290,6 @@ def list_deliveries( "MessageLinkClicked", "MessageLoaded", "DomainDNSError", - "SendLimitApproaching", - "SendLimitExceeded", ] | Omit = omit, page: int | Omit = omit, @@ -478,8 +474,6 @@ def test( "MessageLinkClicked", "MessageLoaded", "DomainDNSError", - "SendLimitApproaching", - "SendLimitExceeded", ], # 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. @@ -563,8 +557,6 @@ async def create( "MessageLinkClicked", "MessageLoaded", "DomainDNSError", - "SendLimitApproaching", - "SendLimitExceeded", ] ] ] @@ -785,8 +777,6 @@ async def list_deliveries( "MessageLinkClicked", "MessageLoaded", "DomainDNSError", - "SendLimitApproaching", - "SendLimitExceeded", ] | Omit = omit, page: int | Omit = omit, @@ -971,8 +961,6 @@ async def test( "MessageLinkClicked", "MessageLoaded", "DomainDNSError", - "SendLimitApproaching", - "SendLimitExceeded", ], # 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. diff --git a/src/ark/types/__init__.py b/src/ark/types/__init__.py index 5c42442..6cd75fb 100644 --- a/src/ark/types/__init__.py +++ b/src/ark/types/__init__.py @@ -15,6 +15,8 @@ from .webhook_test_params import WebhookTestParams as WebhookTestParams from .domain_create_params import DomainCreateParams as DomainCreateParams from .domain_list_response import DomainListResponse as DomainListResponse +from .email_retry_response import EmailRetryResponse as EmailRetryResponse +from .email_retrieve_params import EmailRetrieveParams as EmailRetrieveParams from .email_send_raw_params import EmailSendRawParams as EmailSendRawParams from .log_retrieve_response import LogRetrieveResponse as LogRetrieveResponse from .webhook_create_params import WebhookCreateParams as WebhookCreateParams @@ -27,6 +29,7 @@ from .tracking_create_params import TrackingCreateParams as TrackingCreateParams from .tracking_list_response import TrackingListResponse as TrackingListResponse from .tracking_update_params import TrackingUpdateParams as TrackingUpdateParams +from .email_retrieve_response import EmailRetrieveResponse as EmailRetrieveResponse from .email_send_batch_params import EmailSendBatchParams as EmailSendBatchParams from .email_send_raw_response import EmailSendRawResponse as EmailSendRawResponse from .suppression_list_params import SuppressionListParams as SuppressionListParams @@ -52,4 +55,5 @@ from .suppression_bulk_create_response import SuppressionBulkCreateResponse as SuppressionBulkCreateResponse from .webhook_list_deliveries_response import WebhookListDeliveriesResponse as WebhookListDeliveriesResponse from .webhook_replay_delivery_response import WebhookReplayDeliveryResponse as WebhookReplayDeliveryResponse +from .email_retrieve_deliveries_response import EmailRetrieveDeliveriesResponse as EmailRetrieveDeliveriesResponse from .webhook_retrieve_delivery_response import WebhookRetrieveDeliveryResponse as WebhookRetrieveDeliveryResponse diff --git a/src/ark/types/email_list_response.py b/src/ark/types/email_list_response.py index f8a1d1c..dce7d39 100644 --- a/src/ark/types/email_list_response.py +++ b/src/ark/types/email_list_response.py @@ -13,9 +13,7 @@ class EmailListResponse(BaseModel): id: str - """Internal message ID""" - - token: str + """Unique message identifier (token)""" from_: str = FieldInfo(alias="from") diff --git a/src/ark/types/email_retrieve_deliveries_response.py b/src/ark/types/email_retrieve_deliveries_response.py new file mode 100644 index 0000000..6406e9e --- /dev/null +++ b/src/ark/types/email_retrieve_deliveries_response.py @@ -0,0 +1,130 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from .._models import BaseModel +from .shared.api_meta import APIMeta + +__all__ = ["EmailRetrieveDeliveriesResponse", "Data", "DataDelivery", "DataRetryState"] + + +class DataDelivery(BaseModel): + id: str + """Delivery attempt ID""" + + status: str + """Delivery status (lowercase)""" + + timestamp: float + """Unix timestamp""" + + timestamp_iso: datetime = FieldInfo(alias="timestampIso") + """ISO 8601 timestamp""" + + code: Optional[int] = None + """SMTP response code""" + + details: Optional[str] = None + """Status details""" + + output: Optional[str] = None + """SMTP server response from the receiving mail server""" + + sent_with_ssl: Optional[bool] = FieldInfo(alias="sentWithSsl", default=None) + """Whether TLS was used""" + + +class DataRetryState(BaseModel): + """ + Information about the current retry state of a message that is queued for delivery. + Only present when the message is in the delivery queue. + """ + + attempt: int + """Current attempt number (0-indexed). + + The first delivery attempt is 0, the first retry is 1, and so on. + """ + + attempts_remaining: int = FieldInfo(alias="attemptsRemaining") + """ + Number of attempts remaining before the message is hard-failed. Calculated as + `maxAttempts - attempt`. + """ + + manual: bool + """ + Whether this queue entry was created by a manual retry request. Manual retries + bypass certain hold conditions like suppression lists. + """ + + max_attempts: int = FieldInfo(alias="maxAttempts") + """ + Maximum number of delivery attempts before the message is hard-failed. + Configured at the server level. + """ + + processing: bool + """ + Whether the message is currently being processed by a delivery worker. When + `true`, the message is actively being sent. + """ + + next_retry_at: Optional[float] = FieldInfo(alias="nextRetryAt", default=None) + """ + Unix timestamp of when the next retry attempt is scheduled. `null` if the + message is ready for immediate processing or currently being processed. + """ + + next_retry_at_iso: Optional[datetime] = FieldInfo(alias="nextRetryAtIso", default=None) + """ + ISO 8601 formatted timestamp of the next retry attempt. `null` if the message is + ready for immediate processing. + """ + + +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/{id}/retry`. + `true` when the raw message content is still available (not expired). Messages + older than the retention period cannot be retried. + """ + + deliveries: List[DataDelivery] + """ + Chronological list of delivery attempts for this message. Each attempt includes + SMTP response codes and timestamps. + """ + + retry_state: Optional[DataRetryState] = FieldInfo(alias="retryState", default=None) + """ + Information about the current retry state of a message that is queued for + delivery. Only present when the message is in the delivery queue. + """ + + status: Literal["pending", "sent", "softfail", "hardfail", "held", "bounced"] + """Current message status (lowercase). Possible values: + + - `pending` - Initial state, awaiting first delivery attempt + - `sent` - Successfully delivered + - `softfail` - Temporary failure, will retry automatically + - `hardfail` - Permanent failure, will not retry + - `held` - Held for manual review (suppression list, etc.) + - `bounced` - Bounced by recipient server + """ + + +class EmailRetrieveDeliveriesResponse(BaseModel): + data: Data + + meta: APIMeta + + success: Literal[True] diff --git a/src/ark/types/email_retrieve_params.py b/src/ark/types/email_retrieve_params.py new file mode 100644 index 0000000..5d3fc43 --- /dev/null +++ b/src/ark/types/email_retrieve_params.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["EmailRetrieveParams"] + + +class EmailRetrieveParams(TypedDict, total=False): + expand: str + """Comma-separated list of fields to include: + + - `full` - Include all expanded fields in a single request + - `content` - HTML and plain text body + - `headers` - Email headers + - `deliveries` - Delivery attempt history + - `activity` - Opens and clicks tracking data + - `attachments` - File attachments with content (base64 encoded) + - `raw` - Complete raw MIME message (base64 encoded) + """ diff --git a/src/ark/types/email_retrieve_response.py b/src/ark/types/email_retrieve_response.py new file mode 100644 index 0000000..f60801e --- /dev/null +++ b/src/ark/types/email_retrieve_response.py @@ -0,0 +1,184 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from .._models import BaseModel +from .shared.api_meta import APIMeta + +__all__ = [ + "EmailRetrieveResponse", + "Data", + "DataActivity", + "DataActivityClick", + "DataActivityOpen", + "DataAttachment", + "DataDelivery", +] + + +class DataActivityClick(BaseModel): + ip_address: Optional[str] = FieldInfo(alias="ipAddress", default=None) + """IP address of the clicker""" + + timestamp: Optional[float] = None + """Unix timestamp of the click event""" + + timestamp_iso: Optional[datetime] = FieldInfo(alias="timestampIso", default=None) + """ISO 8601 timestamp of the click event""" + + url: Optional[str] = None + """URL that was clicked""" + + user_agent: Optional[str] = FieldInfo(alias="userAgent", default=None) + """User agent of the email client""" + + +class DataActivityOpen(BaseModel): + ip_address: Optional[str] = FieldInfo(alias="ipAddress", default=None) + """IP address of the opener""" + + timestamp: Optional[float] = None + """Unix timestamp of the open event""" + + timestamp_iso: Optional[datetime] = FieldInfo(alias="timestampIso", default=None) + """ISO 8601 timestamp of the open event""" + + user_agent: Optional[str] = FieldInfo(alias="userAgent", default=None) + """User agent of the email client""" + + +class DataActivity(BaseModel): + """Opens and clicks tracking data (included if expand=activity)""" + + clicks: Optional[List[DataActivityClick]] = None + """List of link click events""" + + opens: Optional[List[DataActivityOpen]] = None + """List of email open events""" + + +class DataAttachment(BaseModel): + """An email attachment retrieved from a sent message""" + + content_type: str = FieldInfo(alias="contentType") + """MIME type of the attachment""" + + data: str + """Base64 encoded attachment content. Decode this to get the raw file bytes.""" + + filename: str + """Original filename of the attachment""" + + hash: str + """SHA256 hash of the attachment content for verification""" + + size: int + """Size of the attachment in bytes""" + + +class DataDelivery(BaseModel): + id: str + """Delivery attempt ID""" + + status: str + """Delivery status (lowercase)""" + + timestamp: float + """Unix timestamp""" + + timestamp_iso: datetime = FieldInfo(alias="timestampIso") + """ISO 8601 timestamp""" + + code: Optional[int] = None + """SMTP response code""" + + details: Optional[str] = None + """Status details""" + + output: Optional[str] = None + """SMTP server response from the receiving mail server""" + + sent_with_ssl: Optional[bool] = FieldInfo(alias="sentWithSsl", default=None) + """Whether TLS was used""" + + +class Data(BaseModel): + id: str + """Unique message identifier (token)""" + + from_: str = FieldInfo(alias="from") + """Sender address""" + + scope: Literal["outgoing", "incoming"] + """Message direction""" + + status: Literal["pending", "sent", "softfail", "hardfail", "bounced", "held"] + """Current delivery status: + + - `pending` - Email accepted, waiting to be processed + - `sent` - Email transmitted to recipient's mail server + - `softfail` - Temporary delivery failure, will retry + - `hardfail` - Permanent delivery failure + - `bounced` - Email bounced back + - `held` - Held for manual review + """ + + subject: str + """Email subject line""" + + timestamp: float + """Unix timestamp when the email was sent""" + + timestamp_iso: datetime = FieldInfo(alias="timestampIso") + """ISO 8601 formatted timestamp""" + + to: str + """Recipient address""" + + activity: Optional[DataActivity] = None + """Opens and clicks tracking data (included if expand=activity)""" + + attachments: Optional[List[DataAttachment]] = None + """File attachments (included if expand=attachments)""" + + deliveries: Optional[List[DataDelivery]] = None + """Delivery attempt history (included if expand=deliveries)""" + + headers: Optional[Dict[str, str]] = None + """Email headers (included if expand=headers)""" + + html_body: Optional[str] = FieldInfo(alias="htmlBody", default=None) + """HTML body content (included if expand=content)""" + + message_id: Optional[str] = FieldInfo(alias="messageId", default=None) + """SMTP Message-ID header""" + + plain_body: Optional[str] = FieldInfo(alias="plainBody", default=None) + """Plain text body (included if expand=content)""" + + raw_message: Optional[str] = FieldInfo(alias="rawMessage", default=None) + """ + Complete raw MIME message, base64 encoded (included if expand=raw). Decode this + to get the original RFC 2822 formatted email. + """ + + spam: Optional[bool] = None + """Whether the message was flagged as spam""" + + spam_score: Optional[float] = FieldInfo(alias="spamScore", default=None) + """Spam score (if applicable)""" + + tag: Optional[str] = None + """Optional categorization tag""" + + +class EmailRetrieveResponse(BaseModel): + data: Data + + meta: APIMeta + + success: Literal[True] diff --git a/src/ark/types/email_retry_response.py b/src/ark/types/email_retry_response.py new file mode 100644 index 0000000..f63cc99 --- /dev/null +++ b/src/ark/types/email_retry_response.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .._models import BaseModel +from .shared.api_meta import APIMeta + +__all__ = ["EmailRetryResponse", "Data"] + + +class Data(BaseModel): + id: str + """Email identifier (token)""" + + message: str + + +class EmailRetryResponse(BaseModel): + data: Data + + meta: APIMeta + + success: Literal[True] diff --git a/src/ark/types/email_send_batch_response.py b/src/ark/types/email_send_batch_response.py index 272119c..d32e3fc 100644 --- a/src/ark/types/email_send_batch_response.py +++ b/src/ark/types/email_send_batch_response.py @@ -11,9 +11,7 @@ class DataMessages(BaseModel): id: str - """Message ID""" - - token: str + """Message identifier (token)""" class Data(BaseModel): diff --git a/src/ark/types/email_send_raw_response.py b/src/ark/types/email_send_raw_response.py index f1e8c03..46e7c96 100644 --- a/src/ark/types/email_send_raw_response.py +++ b/src/ark/types/email_send_raw_response.py @@ -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""" diff --git a/src/ark/types/email_send_response.py b/src/ark/types/email_send_response.py index 39e1d36..cb7d814 100644 --- a/src/ark/types/email_send_response.py +++ b/src/ark/types/email_send_response.py @@ -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""" diff --git a/src/ark/types/log_entry.py b/src/ark/types/log_entry.py index 9fad7e7..4dfdd6c 100644 --- a/src/ark/types/log_entry.py +++ b/src/ark/types/log_entry.py @@ -57,7 +57,7 @@ class Email(BaseModel): """Email-specific data (for email endpoints)""" id: Optional[str] = None - """Email message ID""" + """Email message identifier (token)""" recipient_count: Optional[int] = FieldInfo(alias="recipientCount", default=None) """Number of recipients""" diff --git a/src/ark/types/webhook_create_params.py b/src/ark/types/webhook_create_params.py index 8b7a63e..9899826 100644 --- a/src/ark/types/webhook_create_params.py +++ b/src/ark/types/webhook_create_params.py @@ -34,8 +34,6 @@ class WebhookCreateParams(TypedDict, total=False): "MessageLinkClicked", "MessageLoaded", "DomainDNSError", - "SendLimitApproaching", - "SendLimitExceeded", ] ] ] diff --git a/src/ark/types/webhook_create_response.py b/src/ark/types/webhook_create_response.py index 07f75ec..d78bb58 100644 --- a/src/ark/types/webhook_create_response.py +++ b/src/ark/types/webhook_create_response.py @@ -34,8 +34,6 @@ class Data(BaseModel): "MessageLinkClicked", "MessageLoaded", "DomainDNSError", - "SendLimitApproaching", - "SendLimitExceeded", ] ] """Subscribed events""" diff --git a/src/ark/types/webhook_list_deliveries_params.py b/src/ark/types/webhook_list_deliveries_params.py index cce2c61..c4048e4 100644 --- a/src/ark/types/webhook_list_deliveries_params.py +++ b/src/ark/types/webhook_list_deliveries_params.py @@ -25,8 +25,6 @@ class WebhookListDeliveriesParams(TypedDict, total=False): "MessageLinkClicked", "MessageLoaded", "DomainDNSError", - "SendLimitApproaching", - "SendLimitExceeded", ] """Filter by event type""" diff --git a/src/ark/types/webhook_list_deliveries_response.py b/src/ark/types/webhook_list_deliveries_response.py index cb5033d..71a5fe2 100644 --- a/src/ark/types/webhook_list_deliveries_response.py +++ b/src/ark/types/webhook_list_deliveries_response.py @@ -30,8 +30,6 @@ class Data(BaseModel): "MessageLinkClicked", "MessageLoaded", "DomainDNSError", - "SendLimitApproaching", - "SendLimitExceeded", ] """Event type that triggered this delivery""" diff --git a/src/ark/types/webhook_retrieve_delivery_response.py b/src/ark/types/webhook_retrieve_delivery_response.py index 9a0c38b..efec5c4 100644 --- a/src/ark/types/webhook_retrieve_delivery_response.py +++ b/src/ark/types/webhook_retrieve_delivery_response.py @@ -50,8 +50,6 @@ class Data(BaseModel): "MessageLinkClicked", "MessageLoaded", "DomainDNSError", - "SendLimitApproaching", - "SendLimitExceeded", ] """Event type that triggered this delivery""" diff --git a/src/ark/types/webhook_retrieve_response.py b/src/ark/types/webhook_retrieve_response.py index db780ca..6cb2f2e 100644 --- a/src/ark/types/webhook_retrieve_response.py +++ b/src/ark/types/webhook_retrieve_response.py @@ -34,8 +34,6 @@ class Data(BaseModel): "MessageLinkClicked", "MessageLoaded", "DomainDNSError", - "SendLimitApproaching", - "SendLimitExceeded", ] ] """Subscribed events""" diff --git a/src/ark/types/webhook_test_params.py b/src/ark/types/webhook_test_params.py index 5648540..3f91fca 100644 --- a/src/ark/types/webhook_test_params.py +++ b/src/ark/types/webhook_test_params.py @@ -18,8 +18,6 @@ class WebhookTestParams(TypedDict, total=False): "MessageLinkClicked", "MessageLoaded", "DomainDNSError", - "SendLimitApproaching", - "SendLimitExceeded", ] ] """Event type to simulate""" diff --git a/src/ark/types/webhook_update_response.py b/src/ark/types/webhook_update_response.py index 75c6d59..4d9a0c6 100644 --- a/src/ark/types/webhook_update_response.py +++ b/src/ark/types/webhook_update_response.py @@ -34,8 +34,6 @@ class Data(BaseModel): "MessageLinkClicked", "MessageLoaded", "DomainDNSError", - "SendLimitApproaching", - "SendLimitExceeded", ] ] """Subscribed events""" diff --git a/tests/api_resources/test_emails.py b/tests/api_resources/test_emails.py index e416353..7a54df7 100644 --- a/tests/api_resources/test_emails.py +++ b/tests/api_resources/test_emails.py @@ -11,8 +11,11 @@ from ark.types import ( EmailListResponse, EmailSendResponse, + EmailRetryResponse, EmailSendRawResponse, + EmailRetrieveResponse, EmailSendBatchResponse, + EmailRetrieveDeliveriesResponse, ) from tests.utils import assert_matches_type from ark.pagination import SyncPageNumberPagination, AsyncPageNumberPagination @@ -23,6 +26,52 @@ class TestEmails: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + @parametrize + def test_method_retrieve(self, client: Ark) -> None: + email = client.emails.retrieve( + id="aBc123XyZ", + ) + assert_matches_type(EmailRetrieveResponse, email, path=["response"]) + + @parametrize + def test_method_retrieve_with_all_params(self, client: Ark) -> None: + email = client.emails.retrieve( + id="aBc123XyZ", + expand="full", + ) + assert_matches_type(EmailRetrieveResponse, email, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Ark) -> None: + response = client.emails.with_raw_response.retrieve( + id="aBc123XyZ", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + email = response.parse() + assert_matches_type(EmailRetrieveResponse, email, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Ark) -> None: + with client.emails.with_streaming_response.retrieve( + id="aBc123XyZ", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + email = response.parse() + assert_matches_type(EmailRetrieveResponse, email, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Ark) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.emails.with_raw_response.retrieve( + id="", + ) + @parametrize def test_method_list(self, client: Ark) -> None: email = client.emails.list() @@ -62,6 +111,82 @@ def test_streaming_response_list(self, client: Ark) -> None: assert cast(Any, response.is_closed) is True + @parametrize + def test_method_retrieve_deliveries(self, client: Ark) -> None: + email = client.emails.retrieve_deliveries( + "aBc123XyZ", + ) + assert_matches_type(EmailRetrieveDeliveriesResponse, email, path=["response"]) + + @parametrize + def test_raw_response_retrieve_deliveries(self, client: Ark) -> None: + response = client.emails.with_raw_response.retrieve_deliveries( + "aBc123XyZ", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + email = response.parse() + assert_matches_type(EmailRetrieveDeliveriesResponse, email, path=["response"]) + + @parametrize + def test_streaming_response_retrieve_deliveries(self, client: Ark) -> None: + with client.emails.with_streaming_response.retrieve_deliveries( + "aBc123XyZ", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + email = response.parse() + assert_matches_type(EmailRetrieveDeliveriesResponse, email, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve_deliveries(self, client: Ark) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.emails.with_raw_response.retrieve_deliveries( + "", + ) + + @parametrize + def test_method_retry(self, client: Ark) -> None: + email = client.emails.retry( + "aBc123XyZ", + ) + assert_matches_type(EmailRetryResponse, email, path=["response"]) + + @parametrize + def test_raw_response_retry(self, client: Ark) -> None: + response = client.emails.with_raw_response.retry( + "aBc123XyZ", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + email = response.parse() + assert_matches_type(EmailRetryResponse, email, path=["response"]) + + @parametrize + def test_streaming_response_retry(self, client: Ark) -> None: + with client.emails.with_streaming_response.retry( + "aBc123XyZ", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + email = response.parse() + assert_matches_type(EmailRetryResponse, email, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retry(self, client: Ark) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.emails.with_raw_response.retry( + "", + ) + @parametrize def test_method_send(self, client: Ark) -> None: email = client.emails.send( @@ -275,6 +400,52 @@ class TestAsyncEmails: "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) + @parametrize + async def test_method_retrieve(self, async_client: AsyncArk) -> None: + email = await async_client.emails.retrieve( + id="aBc123XyZ", + ) + assert_matches_type(EmailRetrieveResponse, email, path=["response"]) + + @parametrize + async def test_method_retrieve_with_all_params(self, async_client: AsyncArk) -> None: + email = await async_client.emails.retrieve( + id="aBc123XyZ", + expand="full", + ) + assert_matches_type(EmailRetrieveResponse, email, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncArk) -> None: + response = await async_client.emails.with_raw_response.retrieve( + id="aBc123XyZ", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + email = await response.parse() + assert_matches_type(EmailRetrieveResponse, email, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncArk) -> None: + async with async_client.emails.with_streaming_response.retrieve( + id="aBc123XyZ", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + email = await response.parse() + assert_matches_type(EmailRetrieveResponse, email, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncArk) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.emails.with_raw_response.retrieve( + id="", + ) + @parametrize async def test_method_list(self, async_client: AsyncArk) -> None: email = await async_client.emails.list() @@ -314,6 +485,82 @@ async def test_streaming_response_list(self, async_client: AsyncArk) -> None: assert cast(Any, response.is_closed) is True + @parametrize + async def test_method_retrieve_deliveries(self, async_client: AsyncArk) -> None: + email = await async_client.emails.retrieve_deliveries( + "aBc123XyZ", + ) + assert_matches_type(EmailRetrieveDeliveriesResponse, email, path=["response"]) + + @parametrize + async def test_raw_response_retrieve_deliveries(self, async_client: AsyncArk) -> None: + response = await async_client.emails.with_raw_response.retrieve_deliveries( + "aBc123XyZ", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + email = await response.parse() + assert_matches_type(EmailRetrieveDeliveriesResponse, email, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve_deliveries(self, async_client: AsyncArk) -> None: + async with async_client.emails.with_streaming_response.retrieve_deliveries( + "aBc123XyZ", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + email = await response.parse() + assert_matches_type(EmailRetrieveDeliveriesResponse, email, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve_deliveries(self, async_client: AsyncArk) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.emails.with_raw_response.retrieve_deliveries( + "", + ) + + @parametrize + async def test_method_retry(self, async_client: AsyncArk) -> None: + email = await async_client.emails.retry( + "aBc123XyZ", + ) + assert_matches_type(EmailRetryResponse, email, path=["response"]) + + @parametrize + async def test_raw_response_retry(self, async_client: AsyncArk) -> None: + response = await async_client.emails.with_raw_response.retry( + "aBc123XyZ", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + email = await response.parse() + assert_matches_type(EmailRetryResponse, email, path=["response"]) + + @parametrize + async def test_streaming_response_retry(self, async_client: AsyncArk) -> None: + async with async_client.emails.with_streaming_response.retry( + "aBc123XyZ", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + email = await response.parse() + assert_matches_type(EmailRetryResponse, email, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retry(self, async_client: AsyncArk) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.emails.with_raw_response.retry( + "", + ) + @parametrize async def test_method_send(self, async_client: AsyncArk) -> None: email = await async_client.emails.send( From 8540e7cd143d28f9935bc71d279f343297fe5b26 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 30 Jan 2026 15:29:54 +0000 Subject: [PATCH 3/3] release: 0.16.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 9 +++++++++ pyproject.toml | 2 +- src/ark/_version.py | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 8f3e0a4..b4e9013 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.15.0" + ".": "0.16.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 33dca82..b2c54d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 0.16.0 (2026-01-30) + +Full Changelog: [v0.15.0...v0.16.0](https://github.com/ArkHQ-io/ark-python/compare/v0.15.0...v0.16.0) + +### Features + +* **api:** api update ([56309b8](https://github.com/ArkHQ-io/ark-python/commit/56309b8f34212923562d01dbad53df307d1a6a97)) +* **api:** manual updates ([a6fdf54](https://github.com/ArkHQ-io/ark-python/commit/a6fdf54cba7744137502a2e97f9105ef204f4d79)) + ## 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) diff --git a/pyproject.toml b/pyproject.toml index 6c28b5f..c95a3cf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "ark-email" -version = "0.15.0" +version = "0.16.0" description = "The official Python library for the ark API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/ark/_version.py b/src/ark/_version.py index 1750a65..1f10948 100644 --- a/src/ark/_version.py +++ b/src/ark/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "ark" -__version__ = "0.15.0" # x-release-please-version +__version__ = "0.16.0" # x-release-please-version