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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.11.0"
".": "0.12.0"
}
4 changes: 2 additions & 2 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 32
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/ark%2Fark-15736053ec21a66dfd09dff6e1eec25c17d9b7cb72853626687ab1cb9c63870f.yml
openapi_spec_hash: 0814dd86f388e3d02172997eaefc90b4
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/ark%2Fark-c7a958bca7912840fb91bf86ed7f4b6c292afd8d0a1a7f1091f9b067be87f786.yml
openapi_spec_hash: d64cce1d9d6d329cf8a870b20c3aed62
config_hash: d547cc5e7cc41bda64fd8db0d7cb1279
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

## 0.12.0 (2026-01-23)

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

### Features

* **api:** fix from in Send raw MIME email ([59d29a6](https://github.com/ArkHQ-io/ark-python/commit/59d29a64079d6e9273f7cde8936b8f2fda4ca48d))
* **api:** improve raw MIME error handling ([0f9682d](https://github.com/ArkHQ-io/ark-python/commit/0f9682db45a4d6014f39df875bc0c56db9420b63))

## 0.11.0 (2026-01-23)

Full Changelog: [v0.10.0...v0.11.0](https://github.com/ArkHQ-io/ark-python/compare/v0.10.0...v0.11.0)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "ark-email"
version = "0.11.0"
version = "0.12.0"
description = "The official Python library for the ark API"
dynamic = ["readme"]
license = "Apache-2.0"
Expand Down
2 changes: 1 addition & 1 deletion src/ark/_version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

__title__ = "ark"
__version__ = "0.11.0" # x-release-please-version
__version__ = "0.12.0" # x-release-please-version
40 changes: 34 additions & 6 deletions src/ark/resources/emails.py
Original file line number Diff line number Diff line change
Expand Up @@ -499,12 +499,26 @@ def send_raw(
Use this for advanced use cases or
when migrating from systems that generate raw email content.

The `rawMessage` field should contain the base64-encoded raw email.
**Important:** The `rawMessage` field must be base64-encoded. Your raw MIME
message (with headers like From, To, Subject, Content-Type, followed by a blank
line and the body) must be encoded to base64 before sending.

Args:
from_: Sender email address
from_: Sender email address. Must be from a verified domain.

raw_message: Base64-encoded RFC 2822 MIME message
**Supported formats:**

- Email only: `hello@yourdomain.com`
- With display name: `Acme <hello@yourdomain.com>`
- With quoted name: `"Acme Support" <support@yourdomain.com>`

The domain portion must match a verified sending domain in your account.

raw_message: Base64-encoded RFC 2822 MIME message.

**You must base64-encode your raw email before sending.** The raw email should
include headers (From, To, Subject, Content-Type, etc.) followed by a blank line
and the message body.

to: Recipient email addresses

Expand Down Expand Up @@ -998,12 +1012,26 @@ async def send_raw(
Use this for advanced use cases or
when migrating from systems that generate raw email content.

The `rawMessage` field should contain the base64-encoded raw email.
**Important:** The `rawMessage` field must be base64-encoded. Your raw MIME
message (with headers like From, To, Subject, Content-Type, followed by a blank
line and the body) must be encoded to base64 before sending.

Args:
from_: Sender email address
from_: Sender email address. Must be from a verified domain.

**Supported formats:**

- Email only: `hello@yourdomain.com`
- With display name: `Acme <hello@yourdomain.com>`
- With quoted name: `"Acme Support" <support@yourdomain.com>`

The domain portion must match a verified sending domain in your account.

raw_message: Base64-encoded RFC 2822 MIME message.

raw_message: Base64-encoded RFC 2822 MIME message
**You must base64-encode your raw email before sending.** The raw email should
include headers (From, To, Subject, Content-Type, etc.) followed by a blank line
and the message body.

to: Recipient email addresses

Expand Down
18 changes: 16 additions & 2 deletions src/ark/types/email_send_raw_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,24 @@

class EmailSendRawParams(TypedDict, total=False):
from_: Required[Annotated[str, PropertyInfo(alias="from")]]
"""Sender email address"""
"""Sender email address. Must be from a verified domain.

**Supported formats:**

- Email only: `hello@yourdomain.com`
- With display name: `Acme <hello@yourdomain.com>`
- With quoted name: `"Acme Support" <support@yourdomain.com>`

The domain portion must match a verified sending domain in your account.
"""

raw_message: Required[Annotated[str, PropertyInfo(alias="rawMessage")]]
"""Base64-encoded RFC 2822 MIME message"""
"""Base64-encoded RFC 2822 MIME message.

**You must base64-encode your raw email before sending.** The raw email should
include headers (From, To, Subject, Content-Type, etc.) followed by a blank line
and the message body.
"""

to: Required[SequenceNotStr[str]]
"""Recipient email addresses"""
Expand Down
48 changes: 24 additions & 24 deletions tests/api_resources/test_emails.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,28 +350,28 @@ def test_streaming_response_send_batch(self, client: Ark) -> None:
@parametrize
def test_method_send_raw(self, client: Ark) -> None:
email = client.emails.send_raw(
from_="dev@stainless.com",
raw_message="rawMessage",
to=["dev@stainless.com"],
from_="Acme <hello@acme.com>",
raw_message="x",
to=["user@example.com"],
)
assert_matches_type(EmailSendRawResponse, email, path=["response"])

@parametrize
def test_method_send_raw_with_all_params(self, client: Ark) -> None:
email = client.emails.send_raw(
from_="dev@stainless.com",
raw_message="rawMessage",
to=["dev@stainless.com"],
from_="Acme <hello@acme.com>",
raw_message="x",
to=["user@example.com"],
bounce=True,
)
assert_matches_type(EmailSendRawResponse, email, path=["response"])

@parametrize
def test_raw_response_send_raw(self, client: Ark) -> None:
response = client.emails.with_raw_response.send_raw(
from_="dev@stainless.com",
raw_message="rawMessage",
to=["dev@stainless.com"],
from_="Acme <hello@acme.com>",
raw_message="x",
to=["user@example.com"],
)

assert response.is_closed is True
Expand All @@ -382,9 +382,9 @@ def test_raw_response_send_raw(self, client: Ark) -> None:
@parametrize
def test_streaming_response_send_raw(self, client: Ark) -> None:
with client.emails.with_streaming_response.send_raw(
from_="dev@stainless.com",
raw_message="rawMessage",
to=["dev@stainless.com"],
from_="Acme <hello@acme.com>",
raw_message="x",
to=["user@example.com"],
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
Expand Down Expand Up @@ -724,28 +724,28 @@ async def test_streaming_response_send_batch(self, async_client: AsyncArk) -> No
@parametrize
async def test_method_send_raw(self, async_client: AsyncArk) -> None:
email = await async_client.emails.send_raw(
from_="dev@stainless.com",
raw_message="rawMessage",
to=["dev@stainless.com"],
from_="Acme <hello@acme.com>",
raw_message="x",
to=["user@example.com"],
)
assert_matches_type(EmailSendRawResponse, email, path=["response"])

@parametrize
async def test_method_send_raw_with_all_params(self, async_client: AsyncArk) -> None:
email = await async_client.emails.send_raw(
from_="dev@stainless.com",
raw_message="rawMessage",
to=["dev@stainless.com"],
from_="Acme <hello@acme.com>",
raw_message="x",
to=["user@example.com"],
bounce=True,
)
assert_matches_type(EmailSendRawResponse, email, path=["response"])

@parametrize
async def test_raw_response_send_raw(self, async_client: AsyncArk) -> None:
response = await async_client.emails.with_raw_response.send_raw(
from_="dev@stainless.com",
raw_message="rawMessage",
to=["dev@stainless.com"],
from_="Acme <hello@acme.com>",
raw_message="x",
to=["user@example.com"],
)

assert response.is_closed is True
Expand All @@ -756,9 +756,9 @@ async def test_raw_response_send_raw(self, async_client: AsyncArk) -> None:
@parametrize
async def test_streaming_response_send_raw(self, async_client: AsyncArk) -> None:
async with async_client.emails.with_streaming_response.send_raw(
from_="dev@stainless.com",
raw_message="rawMessage",
to=["dev@stainless.com"],
from_="Acme <hello@acme.com>",
raw_message="x",
to=["user@example.com"],
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
Expand Down