Requirements: Python 3.9 or higher
Installation
For async support with aiohttp (recommended for high-throughput applications):
pip install ark-email[aiohttp]
Quick Start
import os
from ark import Ark
# Initialize the client
client = Ark( api_key = os.environ.get( "ARK_API_KEY" ))
# Send an email
email = client.emails.send(
from_ = "Security <[email protected] >" ,
to = [ "[email protected] " ],
subject = "Reset your password" ,
html = "<h1>Password Reset</h1><p>Click the link below to reset your password.</p>" ,
text = "Password Reset \n\n Click the link below to reset your password."
)
print ( f "Email ID: { email.data.id } " )
print ( f "Status: { email.data.status } " )
The from_ parameter uses an underscore because from is a reserved keyword in Python.
Configuration
Client Options
from ark import Ark
client = Ark(
api_key = "ark_..." , # Required (or set ARK_API_KEY env var)
timeout = 60.0 , # Request timeout in seconds (default: 60)
max_retries = 2 , # Number of retry attempts (default: 2)
base_url = "https://api.arkhq.io/v1" , # API base URL (rarely needed)
)
Environment Variables
export ARK_API_KEY = "ark_live_..."
export ARK_LOG = "debug" # Enable debug logging
API Reference
Emails
Send a single email. email = client.emails.send(
from_ = "[email protected] " ,
to = [ "[email protected] " ],
subject = "Welcome!" ,
html = "<h1>Hello</h1>" ,
text = "Hello" , # Optional but recommended
cc = [ "[email protected] " ], # Optional
bcc = [ "[email protected] " ], # Optional
reply_to = "[email protected] " , # Optional
tags = [ "welcome" , "onboarding" ], # Optional
metadata = { "user_id" : "123" }, # Optional
track_opens = True , # Optional
track_clicks = True , # Optional
scheduled_at = "2024-01-20T09:00:00Z" , # Optional - ISO 8601
attachments = [ # Optional
{
"filename" : "invoice.pdf" ,
"content" : base64_encoded_content,
"content_type" : "application/pdf"
}
],
headers = { # Optional custom headers
"X-Custom-Header" : "value"
}
)
Returns: SendEmailResponse with data.id, data.status, etc.
Send multiple emails in a single request.
List emails with filtering and pagination. # List recent emails
emails = client.emails.list(
page = 1 ,
per_page = 25 ,
status = "sent" , # Optional filter
tag = "welcome" , # Optional filter
)
for email in emails.data:
print ( f " { email.id } : { email.subject } - { email.status } " )
# Pagination info
print ( f "Page { emails.page } of { emails.total_pages } " )
Get a single email by ID. email = client.emails.retrieve( "msg_abc123xyz" )
print ( f "Subject: { email.data.subject } " )
print ( f "Status: { email.data.status } " )
print ( f "Created: { email.data.created_at } " )
Get delivery attempts for an email. deliveries = client.emails.get_deliveries( "msg_abc123xyz" )
for delivery in deliveries.data:
print ( f "Attempt at { delivery.timestamp } : { delivery.status } " )
if delivery.error:
print ( f " Error: { delivery.error } " )
Retry a failed email. result = client.emails.retry( "msg_abc123xyz" )
print ( f "Retry scheduled: { result.data.id } " )
Domains
Register a new sending domain. domain = client.domains.create( name = "mail.yourdomain.com" )
print ( f "Domain ID: { domain.data.id } " )
print ( "DNS Records to configure:" )
for record in domain.data.dns_records:
print ( f " { record.type } { record.name } -> { record.value } " )
List all domains. domains = client.domains.list()
for domain in domains.data:
status = "Verified" if domain.verified else "Pending"
print ( f " { domain.name } : { status } " )
Get domain details. domain = client.domains.retrieve( "dom_abc123" )
print ( f "Domain: { domain.data.name } " )
print ( f "Verified: { domain.data.verified } " )
Trigger DNS verification. result = client.domains.verify( "dom_abc123" )
if result.data.verified:
print ( "Domain verified successfully!" )
else :
print ( "DNS records not found. Please check your configuration." )
Remove a domain. client.domains.delete( "dom_abc123" )
print ( "Domain deleted" )
Suppressions
Add an email to the suppression list. suppression = client.suppressions.create(
email = "[email protected] " ,
reason = "hard_bounce"
)
Add multiple emails to the suppression list. result = client.suppressions.bulk_create(
suppressions = [
{ "email" : "[email protected] " , "reason" : "hard_bounce" },
{ "email" : "[email protected] " , "reason" : "complaint" },
]
)
print ( f "Added { result.data.created_count } suppressions" )
List suppressed emails. suppressions = client.suppressions.list(
page = 1 ,
per_page = 50 ,
reason = "hard_bounce" # Optional filter
)
for s in suppressions.data:
print ( f " { s.email } : { s.reason } (added { s.created_at } )" )
Check if an email is suppressed. try :
suppression = client.suppressions.retrieve( "[email protected] " )
print ( f "Suppressed: { suppression.data.reason } " )
except ark.NotFoundError:
print ( "Email is not suppressed" )
Remove from suppression list. client.suppressions.delete( "[email protected] " )
print ( "Removed from suppression list" )
Webhooks
Create a webhook endpoint. webhook = client.webhooks.create(
url = "https://yourapp.com/webhooks/ark" ,
events = [ "MessageSent" , "MessageBounced" , "MessageLoaded" ]
)
print ( f "Webhook ID: { webhook.data.id } " )
print ( f "Signing Secret: { webhook.data.signing_secret } " ) # Store this!
List all webhooks. webhooks = client.webhooks.list()
for wh in webhooks.data:
print ( f " { wh.id } : { wh.url } " )
print ( f " Events: { ', ' .join(wh.events) } " )
Update webhook configuration. webhook = client.webhooks.update(
"wh_abc123" ,
events = [ "MessageSent" , "MessageBounced" ] # Updated events
)
Send a test event to your webhook. result = client.webhooks.test(
"wh_abc123" ,
event = "MessageSent"
)
print ( f "Test result: { result.data.status } " )
Delete a webhook. client.webhooks.delete( "wh_abc123" )
Tracking
Configure a custom tracking domain. tracking = client.tracking.create(
domain = "track.yourdomain.com"
)
print ( "Add this CNAME record:" )
print ( f " { tracking.data.cname_target } " )
List tracking domains. tracking_domains = client.tracking.list()
for t in tracking_domains.data:
status = "Verified" if t.verified else "Pending"
print ( f " { t.domain } : { status } " )
Verify tracking domain DNS. result = client.tracking.verify( "trk_abc123" )
print ( f "Verified: { result.data.verified } " )
Remove a tracking domain. client.tracking.delete( "trk_abc123" )
Async Usage
For high-throughput applications, use the async client:
import asyncio
from ark import AsyncArk
async def send_welcome_emails ( user_emails : list[ str ]):
client = AsyncArk()
# Send emails concurrently
tasks = [
client.emails.send(
from_ = "[email protected] " ,
to = [email],
subject = "Welcome to MyApp!" ,
html = "<h1>Welcome!</h1>"
)
for email in user_emails
]
results = await asyncio.gather( * tasks)
for result in results:
print ( f "Sent to { result.data.to[ 0 ] } : { result.data.id } " )
# Run it
asyncio.run(send_welcome_emails([
"[email protected] " ,
"[email protected] " ,
"[email protected] "
]))
Context Manager
Both sync and async clients support context managers for proper resource cleanup:
# Sync
with Ark() as client:
client.emails.send( ... )
# Async
async with AsyncArk() as client:
await client.emails.send( ... )
Error Handling
The SDK throws typed exceptions for different error scenarios:
from ark import Ark
import ark
client = Ark()
try :
email = client.emails.send(
from_ = "[email protected] " ,
to = [ "invalid" ],
subject = "Test" ,
html = "<p>Test</p>"
)
except ark.BadRequestError as e:
print ( f "Invalid request: { e.message } " )
print ( f "Error code: { e.code } " )
except ark.AuthenticationError:
print ( "Invalid API key" )
except ark.RateLimitError as e:
print ( f "Rate limited. Retry after: { e.response.headers.get( 'Retry-After' ) } " )
except ark.APIConnectionError:
print ( "Network error - check your connection" )
except ark.APIStatusError as e:
print ( f "API error { e.status_code } : { e.message } " )
Exception Hierarchy
ark.APIError
├── ark.APIConnectionError # Network/connection issues
├── ark.APITimeoutError # Request timeout
└── ark.APIStatusError # HTTP error responses
├── ark.BadRequestError # 400
├── ark.AuthenticationError # 401
├── ark.PermissionDeniedError # 403
├── ark.NotFoundError # 404
├── ark.UnprocessableEntityError # 422
├── ark.RateLimitError # 429
└── ark.InternalServerError # 5xx
Advanced Usage
Raw Response Access
Access HTTP headers and status codes:
response = client.emails.with_raw_response.send(
from_ = "[email protected] " ,
to = [ "[email protected] " ],
subject = "Test" ,
html = "<p>Test</p>"
)
print ( f "Status: { response.status_code } " )
print ( f "Headers: { response.headers } " )
print ( f "Request ID: { response.headers.get( 'x-request-id' ) } " )
# Parse the response body
email = response.parse()
print ( f "Email ID: { email.data.id } " )
Streaming Responses
For large responses, use streaming to reduce memory usage:
with client.emails.with_streaming_response.list() as response:
for chunk in response.iter_bytes():
process(chunk)
Custom HTTP Client
Use a custom httpx client for proxies or advanced configuration:
import httpx
from ark import Ark
# Configure proxy
http_client = httpx.Client(
proxies = "http://proxy.example.com:8080" ,
verify = False , # Skip SSL verification (not recommended for production)
)
client = Ark( http_client = http_client)
Idempotency
Use idempotency keys to safely retry requests:
import uuid
idempotency_key = str (uuid.uuid4())
# This request can be safely retried with the same key
email = client.emails.send(
from_ = "[email protected] " ,
to = [ "[email protected] " ],
subject = "Order Confirmation" ,
html = "<p>Your order is confirmed.</p>" ,
idempotency_key = idempotency_key
)
Accessing Undocumented Fields
The SDK models allow access to undocumented API fields:
email = client.emails.retrieve( "msg_abc123" )
# Access known fields
print (email.data.subject)
# Access any additional fields returned by the API
print (email.data.model_extra) # Dict of extra fields
Type Hints
The SDK is fully typed. Your IDE will provide autocomplete and type checking:
from ark import Ark
from ark.types import EmailSendParams
def send_email ( params : EmailSendParams) -> str :
client = Ark()
response = client.emails.send( ** params)
return response.data.id
Framework Integration
Django
# settings.py
ARK_API_KEY = os.environ.get( "ARK_API_KEY" )
# emails.py
from django.conf import settings
from ark import Ark
_client = None
def get_ark_client ():
global _client
if _client is None :
_client = Ark( api_key = settings. ARK_API_KEY )
return _client
def send_welcome_email ( user ):
client = get_ark_client()
return client.emails.send(
from_ = "[email protected] " ,
to = [user.email],
subject = f "Welcome, { user.first_name } !" ,
html = render_to_string( "emails/welcome.html" , { "user" : user})
)
FastAPI
from fastapi import FastAPI, Depends
from ark import AsyncArk
from contextlib import asynccontextmanager
app = FastAPI()
ark_client: AsyncArk | None = None
@asynccontextmanager
async def lifespan ( app : FastAPI):
global ark_client
ark_client = AsyncArk()
yield
await ark_client.close()
app = FastAPI( lifespan = lifespan)
@app.post ( "/send-email" )
async def send_email ( to : str , subject : str , body : str ):
email = await ark_client.emails.send(
from_ = "[email protected] " ,
to = [to],
subject = subject,
html = body
)
return { "email_id" : email.data.id}
Flask
from flask import Flask, g
from ark import Ark
app = Flask( __name__ )
def get_ark ():
if 'ark' not in g:
g.ark = Ark()
return g.ark
@app.teardown_appcontext
def close_ark ( error ):
ark = g.pop( 'ark' , None )
if ark is not None :
ark.close()
@app.route ( "/send" , methods = [ "POST" ])
def send_email ():
client = get_ark()
email = client.emails.send( ... )
return { "email_id" : email.data.id}
Resources