SDKs
Python SDK
The official Python SDK for zend.sh. Includes both synchronous and asynchronous clients with Pydantic-typed responses.
Installation
pip install zend-shRequires Python 3.9+.
Sync client
from zend_sh import ZendClient
client = ZendClient(api_key="zk_live_...")Async client
from zend_sh import AsyncZendClient
client = AsyncZendClient(api_key="zk_live_...")
# Use with async/await
accounts = await client.accounts.list()Resources
Accounts
# List all accounts
result = client.accounts.list()
for account in result.accounts:
print(account.email, account.health_score)
# Connect a new mailbox
account = client.accounts.create(
email="outreach@yourcompany.com",
provider="google",
smtp_host="smtp.gmail.com",
smtp_port=465,
smtp_username="outreach@yourcompany.com",
smtp_password="your-app-password",
imap_host="imap.gmail.com",
imap_port=993,
imap_username="outreach@yourcompany.com",
imap_password="your-app-password",
)
# Get, update, delete
account = client.accounts.get("account-id")
account = client.accounts.update("account-id", daily_send_limit=50)
client.accounts.delete("account-id")
# Send a test email
result = client.accounts.test("account-id")Campaigns
# List campaigns
result = client.campaigns.list()
# Create a campaign
campaign = client.campaigns.create(
name="Q1 Outreach",
stop_on_reply=True,
send_window={
"timezone": "America/New_York",
"days": ["monday", "tuesday", "wednesday", "thursday", "friday"],
"start_hour": 9,
"end_hour": 17,
},
)
# Get, update, delete
campaign = client.campaigns.get(campaign.id)
campaign = client.campaigns.update(campaign.id, name="Updated Name")
client.campaigns.delete(campaign.id) # only draft/archived
# Start and pause
client.campaigns.start(campaign.id)
client.campaigns.pause(campaign.id)Campaign Steps
# List steps
result = client.campaigns.steps.list(campaign.id)
# Add an email step
client.campaigns.steps.create(
campaign.id,
type="email",
position=1,
subject="Hi {{first_name|there}}",
body="Your email body with {{company}} variables...",
)
# Add a delay step
client.campaigns.steps.create(
campaign.id,
type="delay",
position=2,
delay_days=3,
)Campaign Leads
# Add leads
result = client.campaigns.leads.add(
campaign.id,
leads=[
{"email": "jane@acme.com", "first_name": "Jane", "company": "Acme"},
{"email": "bob@globex.com", "first_name": "Bob", "company": "Globex"},
],
)
print(f"Enrolled: {result.enrolled}")
# List enrolled leads
result = client.campaigns.leads.list(campaign.id)
# Import with duplicate handling
result = client.campaigns.leads.import_leads(
campaign.id,
leads=[{"email": "jane@acme.com", "first_name": "Jane"}],
)Analytics
# Team-wide overview
overview = client.analytics.overview()
print(f"Sent: {overview.sent}, Replied: {overview.replied}")
# Per-campaign analytics
stats = client.analytics.campaign(campaign.id)
# Per-account health
health = client.analytics.account("account-id")
print(f"Health score: {health.health_score}")Webhooks
# Create a webhook
webhook = client.webhooks.create(
url="https://yourapp.com/webhooks/zend",
events=["email.sent", "email.bounced", "email.replied"],
)
# List, update, delete
result = client.webhooks.list()
client.webhooks.update(webhook.id, events=["email.replied"])
client.webhooks.delete(webhook.id)Events
result = client.events.list(type="email.replied", limit=50)
for event in result.events:
print(event.type, event.created_at)Suppressions
# Add a suppression
client.suppressions.create(email="unsubscribed@example.com")
# Check if suppressed
result = client.suppressions.check("user@example.com")
print(result.suppressed) # True or False
# List and delete
result = client.suppressions.list()
client.suppressions.delete("unsubscribed@example.com")API Keys
# Create a new key
result = client.keys.create(name="Production Key")
print(result.key) # 'zk_live_...' (shown only once)
# List keys (masked)
result = client.keys.list()
# Revoke a key
client.keys.delete("key-id")Async usage
The async client mirrors the sync client exactly, but all methods are awaitable:
import asyncio
from zend_sh import AsyncZendClient
async def main():
client = AsyncZendClient(api_key="zk_live_...")
# All the same methods, just with await
result = await client.accounts.list()
campaign = await client.campaigns.create(
name="Async Campaign",
stop_on_reply=True,
)
await client.campaigns.start(campaign.id)
asyncio.run(main())Pydantic models
All responses are parsed into Pydantic v2 models with full type information:
from zend_sh.types import Account, Campaign, Lead, AnalyticsOverview
# Response objects have typed attributes
account: Account = client.accounts.get("account-id")
print(account.email) # str
print(account.health_score) # float
print(account.provider) # Literal["google", "microsoft", "smtp"]Error handling
API errors raise ZendError with structured information:
from zend_sh import ZendClient, ZendError
client = ZendClient(api_key="zk_live_...")
try:
client.campaigns.start("nonexistent-id")
except ZendError as e:
print(e.type) # 'not_found'
print(e.message) # 'Campaign not found'
print(e.status) # 404Configuration
client = ZendClient(
api_key="zk_live_...",
base_url="https://app.zend.sh", # optional, defaults to production
)