Acumamail SDK client for Python
Project description
Complete Python SDK, CLI, and automation workflows for Acumbamail email marketing.
Table of Contents
- Features
- Installation
- Getting Started
- Usage
- API Reference
- Error Handling
- Claude Code Skill
- n8n Integration
- Examples
- Contributing
- License
acumbamail is a Python SDK and CLI with complete coverage of the Acumbamail REST API. It provides sync and async clients, a JSON-outputting CLI for shell scripting and CI pipelines, and — uniquely — automation workflows as YAML code so you can version-control and deploy email sequences the same way you deploy software.
>>> from acumbamail import AcumbamailClient
>>> client = AcumbamailClient(auth_token="...", default_sender_email="hello@company.com")
>>> lst = client.create_list("Newsletter")
>>> client.add_subscriber("user@example.com", lst.id, fields={"name": "Alice"})
>>> campaign = client.create_campaign(
... name="Welcome", subject="Thanks for subscribing!",
... content='<p>Hello!</p><a href="*|UNSUBSCRIBE_URL|*">Unsubscribe</a>',
... list_ids=[lst.id],
... )
>>> stats = client.get_campaign_total_information(campaign.id)
>>> f"{stats.opened / stats.total_delivered:.1%} open rate"
'28.3% open rate'
Features
- Sync and async clients —
AcumbamailClientandAsyncAcumbamailClientshare the same API surface; async client is an async context manager. - Full API coverage — 48 endpoints across lists, subscribers, campaigns, templates, SMTP, and webhooks.
- JSON CLI — every command outputs JSON, piped straight into
jqor CI scripts. - Automations as code — define email sequences in YAML, deploy with
acumbamail automations deploy. Version-controlled, idempotent, reviewable. - Automatic retry — 429 responses are retried up to 3× with exponential backoff; no extra configuration needed.
- Typed models — every response is a typed dataclass (
MailList,Campaign,CampaignTotalInformation, …). - OpenAPI 3.0.3 spec and Postman collection included in the repository.
- Claude Code skill — install a bundled skill so AI assistants understand the full CLI and SDK.
Installation
# Using pip
pip install acumbamail
# Using uv
uv add acumbamail
# CLI only (no Python import needed)
pipx install acumbamail
Requires Python 3.13+.
Getting Started
1. Get your API token — log in to acumbamail.com, go to Settings → API, and copy your token.
2. Set the environment variable:
export ACUMBAMAIL_TOKEN=your-token-here
3. Create a list and send your first campaign:
from acumbamail import AcumbamailClient
client = AcumbamailClient(
auth_token="your-token",
default_sender_email="hello@mycompany.com",
default_sender_name="My Company",
)
# Create a mailing list
lst = client.create_list("My Newsletter")
print(f"List created: {lst.id}")
# Add a subscriber
client.add_subscriber("alice@example.com", lst.id, fields={"nombre": "Alice"})
# Send your first campaign
campaign = client.create_campaign(
name="First Campaign",
subject="Hello from acumbamail!",
content="""
<h1>Hello *|FNAME|*!</h1>
<p>Welcome to our newsletter.</p>
<a href="*|UNSUBSCRIBE_URL|*">Unsubscribe</a>
""",
list_ids=[lst.id],
)
print(f"Campaign sent: {campaign.id}")
# Check results the next day
stats = client.get_campaign_total_information(campaign.id)
print(f"Delivered: {stats.total_delivered}, Opened: {stats.opened}")
Or with the CLI:
$ acumbamail lists create --name "My Newsletter" --sender-email hello@mycompany.com
{"id": 1138335, "name": "My Newsletter"}
$ acumbamail subscribers add --list-id 1138335 --email alice@example.com
{"email": "alice@example.com", "list_id": 1138335}
$ acumbamail campaigns create \
--name "First Campaign" --subject "Hello!" \
--html '<h1>Hi!</h1><a href="*|UNSUBSCRIBE_URL|*">Unsubscribe</a>' \
--list-id 1138335 --from-email hello@mycompany.com
{"id": 3294239, "name": "First Campaign", ...}
[!TIP] Your sender email must be verified in Acumbamail before sending campaigns. Add it at Settings → Verified senders.
Usage
Python SDK
from acumbamail import AcumbamailClient
client = AcumbamailClient(
auth_token="your-token",
default_sender_name="My Company",
default_sender_email="hello@mycompany.com",
)
# Lists and subscribers
lists = client.get_lists()
client.add_subscriber("user@example.com", list_id=lists[0].id)
client.batch_add_subscribers(lists[0].id, [
{"email": "a@example.com", "nombre": "Alice"},
{"email": "b@example.com", "nombre": "Bob"},
])
# Campaigns
campaign = client.create_campaign(
name="May Newsletter",
subject="What's new this month",
content='<h1>Hello *|FNAME|*</h1><a href="*|UNSUBSCRIBE_URL|*">Unsubscribe</a>',
list_ids=[lists[0].id],
)
# Analytics
stats = client.get_campaign_total_information(campaign.id)
clicks = client.get_campaign_clicks(campaign.id)
openers = client.get_campaign_openers_by_browser(campaign.id)
Or use async with AsyncAcumbamailClient…
import asyncio
from acumbamail import AsyncAcumbamailClient
async def main():
async with AsyncAcumbamailClient(auth_token="your-token") as client:
lists = await client.get_lists()
stats = await client.get_campaign_total_information(campaign_id=123)
print(f"Open rate: {stats.opened / stats.total_delivered:.1%}")
asyncio.run(main())
The async client is identical in API to the sync client. Use async with or call .close() manually.
CLI
All commands output JSON. Pipe into jq for filtering.
$ export ACUMBAMAIL_TOKEN=your-token
$ acumbamail lists list
[{"id": 1138335, "name": "Newsletter", "subscribers_count": 1200, ...}]
$ acumbamail subscribers batch-add --list-id 1138335 --file subscribers.json
[{"email": "a@x.com", "subscriber_id": 111}, ...]
$ acumbamail campaigns create \
--name "May Newsletter" \
--subject "What's new this month" \
--html-file email.html \
--list-id 1138335 \
--from-email hello@mycompany.com
{"id": 3294239, "name": "May Newsletter", "subject": "What's new this month", ...}
$ acumbamail campaigns stats --campaign-id 3294239 | \
jq '{open_rate: (.opened / .total_delivered * 100 | round | tostring + "%")}'
{"open_rate": "28%"}
All available commands…
$ acumbamail lists list
$ acumbamail lists create --name "My List" --sender-email hello@mycompany.com
$ acumbamail lists delete --list-id 123
$ acumbamail lists stats --list-id 123
$ acumbamail subscribers list --list-id 123
$ acumbamail subscribers add --list-id 123 --email user@example.com
$ acumbamail subscribers delete --list-id 123 --email user@example.com
$ acumbamail subscribers search --query user@example.com
$ acumbamail subscribers unsubscribe --list-id 123 --email user@example.com
$ acumbamail subscribers batch-add --list-id 123 --file subscribers.json
$ acumbamail campaigns list
$ acumbamail campaigns create --name "..." --subject "..." --html-file email.html --list-id 123
$ acumbamail campaigns info --campaign-id 456
$ acumbamail campaigns stats --campaign-id 456
$ acumbamail webhooks smtp-get
$ acumbamail webhooks smtp-config --url https://myapp.com/hooks/smtp --delivered --active
$ acumbamail webhooks list-get --list-id 123
$ acumbamail webhooks list-config --list-id 123 --url https://myapp.com/hooks/list --subscribes --active
$ acumbamail automations login
$ acumbamail automations list
$ acumbamail automations deploy workflow.yaml
$ acumbamail automations export --name "welcome-sequence"
$ acumbamail automations delete --name "welcome-sequence"
Use --token <token> as an alternative to ACUMBAMAIL_TOKEN. Every command accepts --help.
Automations as Code
Most email platforms treat automations as a GUI-only feature: you click through a visual editor, and the result lives exclusively in their cloud — invisible to Git, impossible to review in a PR, painful to replicate across accounts.
acumbamail takes a different approach. Your automation workflows live as YAML files in your repository, right next to your code. You deploy them with a single command, the same way you'd apply a Terraform plan or a Kubernetes manifest.
What you get:
- Version control — every change to a workflow goes through a commit.
git diffshows exactly what changed between the old and new sequence. - Code review — workflows are PR-reviewable. Your team can catch logic errors before a broken sequence goes out to thousands of subscribers.
- Reproducibility — recreate any automation on a new account or staging environment in seconds, from a file.
- Idempotency — deploying the same file twice is always safe. Acumbamail identifies workflows by
name, replaces in place, and reports"action": "updated". - Backup and recovery —
acumbamail automations exportpulls any existing workflow back to YAML. Even automations built in the GUI can be exported, put under version control, and managed from that point on.
# welcome-sequence.yaml ← lives in your repo, reviewed in PRs
name: welcome-sequence
trigger:
list_id: 1138335
event: subscriber_added # fires when someone joins the list
steps:
- type: email_template
subject: "Welcome aboard!"
template_id: 8986619
- type: delay
wait: 3
unit: days
- type: email_template
subject: "Getting started guide"
template_id: 8986620
- type: delay
wait: 7
unit: days
# Branch on engagement: reward active subscribers, re-engage quiet ones
- type: condition
on_match:
- type: email_template
subject: "Special offer for active subscribers"
template_id: 8986621
on_no_match:
- type: email_template
subject: "We miss you — come back?"
template_id: 8986622
$ acumbamail automations login # one-time: opens Chrome, saves session for 30 days
$ acumbamail automations deploy welcome-sequence.yaml
{"workflow_id": 36215, "action": "created", "active": false}
$ # Edit the YAML, redeploy — safe and idempotent
$ acumbamail automations deploy welcome-sequence.yaml
{"workflow_id": 36215, "action": "updated", "active": false}
$ # Pull an existing automation back to YAML (even ones built in the GUI)
$ acumbamail automations export --name "welcome-sequence" > welcome-sequence.yaml
$ acumbamail automations list | jq '[.[] | select(.active == true)]'
[!NOTE] Automations use Acumbamail's web session (not the API token). Run
acumbamail automations loginonce — the session lasts 30 days.
Supported step types: email_template, delay, condition, until, webhook, update_field, move_to.
API Reference
Mailing Lists
| Method | Description |
|---|---|
get_lists() |
All lists with subscriber counts |
create_list(name, description) |
Create a list |
delete_list(list_id) |
Delete a list |
get_list_stats(list_id) |
Subscriber, bounce, and unsubscribe counts |
get_fields(list_id) |
{field_name: field_type} mapping |
add_merge_tag(list_id, field_name, field_type) |
Add a custom field |
Subscribers
| Method | Description |
|---|---|
get_subscribers(list_id, ...) |
Paginated subscriber list |
add_subscriber(email, list_id, fields, double_optin, update_subscriber) |
Add one subscriber |
delete_subscriber(email, list_id) |
Permanently remove |
unsubscribe_subscriber(list_id, email) |
Mark as unsubscribed without deleting |
batch_add_subscribers(list_id, subscribers_data, update_subscriber) |
Bulk add |
delete_all_subscribers(list_id) |
Remove all (background process) |
get_subscriber_details(list_id, subscriber_email) |
Full subscriber record |
search_subscriber(query) |
Search across all lists |
get_inactive_subscribers(list_id, date_from, date_to, full_info) |
Inactive subscribers in a date range |
Campaigns
| Method | Description |
|---|---|
create_campaign(name, subject, content, list_ids, scheduled_at, ...) |
Create and send (or schedule) |
send_template_campaign(name, subject, template_id, list_ids, ...) |
Send from a saved template |
get_campaigns() |
List all campaigns |
get_campaign_basic_information(campaign_id) |
Name, subject, status |
get_campaign_total_information(campaign_id) |
Full stats — CampaignTotalInformation |
get_campaign_clicks(campaign_id) |
Per-URL click data |
get_campaign_openers(campaign_id) |
Per-subscriber open data |
get_campaign_openers_by_browser(campaign_id) |
Opens grouped by browser |
get_campaign_openers_by_os(campaign_id) |
Opens grouped by OS |
get_campaign_openers_by_countries(campaign_id) |
Opens grouped by country |
get_campaign_soft_bounces(campaign_id) |
Soft bounce records |
get_stats_by_date(list_id, date_from, date_to) |
Aggregate stats for a list over a date range |
[!IMPORTANT] Campaign HTML must include
*|UNSUBSCRIBE_URL|*— the API rejects content without it. Merge tags*|FNAME|*,*|LNAME|*,*|EMAIL|*, and*|FULLNAME|*are also available.
Templates
| Method | Description |
|---|---|
get_templates() |
All templates |
create_template(template_name, html_content, subject) |
Create a template |
duplicate_template(template_name, origin_template_id) |
Clone a template |
SMTP Transactional
[!NOTE] Requires SMTP plan activation in your Acumbamail account.
| Method | Description |
|---|---|
send_single_email(to_email, subject, content, ...) |
One transactional email |
send_emails(messages) |
Batch transactional emails |
send_certified_email(to_email, subject, content, ...) |
Certified email |
get_email_status(email_key) |
Delivery status |
get_smtp_credits() |
Remaining credits |
Webhooks
| Method | Description |
|---|---|
get_smtp_webhook() |
Current SMTP webhook config |
config_smtp_webhook(callback_url, ...) |
Set SMTP webhook |
get_list_webhook(list_id) |
Current list webhook config |
config_list_webhook(list_id, callback_url, ...) |
Set list webhook |
Error Handling
from acumbamail import (
AcumbamailError, # base class
AcumbamailValidationError, # bad arguments — 400 (e.g. missing UNSUBSCRIBE_URL)
AcumbamailAPIError, # HTTP errors — 4xx/5xx
AcumbamailRateLimitError, # 429 — retried automatically up to 3×
)
try:
client.create_campaign(
name="Test", subject="Hi",
content="<p>No unsubscribe link here</p>", # will raise
list_ids=[123],
)
except AcumbamailValidationError as e:
print(f"Validation error: {e}")
except AcumbamailRateLimitError:
print("Rate limited — the SDK already retried 3× with backoff")
except AcumbamailAPIError as e:
print(f"API error: {e}")
Claude Code Skill
If you use Claude Code, install the bundled skill so your AI assistant knows the complete CLI API and quirks:
$ acumbamail install-skills # project-local → .claude/skills/
$ acumbamail install-skills -g # user-global → ~/.claude/skills/
Two skills are installed: acumbamail-cli (lists, subscribers, campaigns, webhooks, templates) and acumbamail-automations (YAML schema, deploy/export/delete workflows).
n8n Integration
If you use n8n for workflow automation, the n8n-nodes-acumbamail community node brings the full Acumbamail API into your visual workflows — no code required.
46 operations across 6 resources — lists, subscribers, campaigns, templates, SMTP, and webhooks — plus a trigger node for real-time webhook events (new subscriptions, bounces, clicks, opens).
| Resource | Operations |
|---|---|
| List | Get All, Create, Delete, Get Stats, Get Fields, Get Merge Fields, Add Merge Tag, Get Forms, … |
| Subscriber | Get All, Add, Delete, Unsubscribe, Batch Add, Get Details, Search, Get Inactive, … |
| Campaign | Get All, Create, Get Stats, Get Clicks, Get Openers by Browser / OS / Country, … |
| Template | Get All, Create, Duplicate, Send Campaign |
| SMTP | Send Email, Send Batch, Send Certified, Get Status, Get Credits |
| Webhook (trigger) | New subscription, unsubscription, bounce, spam complaint, open, click, delivered |
Install in n8n:
Settings → Community Nodes → n8n-nodes-acumbamail
→ github.com/cr0hn/n8n-nodes-acumbamail
Examples
Runnable examples are in examples/:
| File | What it shows |
|---|---|
sync_example.py |
Basic synchronous workflow |
async_example.py |
Async workflow with AsyncAcumbamailClient |
campaign_analytics.py |
Open/click rates, browser and OS breakdown |
bulk_operations.py |
batch_add_subscribers, inactive subscriber cleanup |
ab_testing.py |
Two-variant campaign comparison |
automated_workflows.py |
List creation, bulk import, campaign, webhook setup |
error_handling.py |
Catching and handling every exception type |
$ export ACUMBAMAIL_TOKEN=your-token
$ python examples/sync_example.py
Contributing
Contributions are welcome. Please open an issue before starting significant work.
git clone https://github.com/cr0hn/python-acumbamail
cd python-acumbamail
uv sync
uv run pytest # unit + structural tests (no network)
uv run pytest -m contract # contract tests against the real API (requires ACUMBAMAIL_TOKEN)
See CONTRIBUTING.md for the full guide.
License
MIT © Daniel Garcia (cr0hn)
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file acumbamail-0.3.1.tar.gz.
File metadata
- Download URL: acumbamail-0.3.1.tar.gz
- Upload date:
- Size: 182.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.12 {"installer":{"name":"uv","version":"0.11.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c43a961708355140f04e02c382113d26859a463f7467edfe22fce7e8a3f1421e
|
|
| MD5 |
32371b093025c026e2d1faba2e6e3580
|
|
| BLAKE2b-256 |
6142b747fd5267d02d7db148bab58b0438c92ae2d782635ea05fefb0d70d1fbe
|
File details
Details for the file acumbamail-0.3.1-py3-none-any.whl.
File metadata
- Download URL: acumbamail-0.3.1-py3-none-any.whl
- Upload date:
- Size: 62.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.12 {"installer":{"name":"uv","version":"0.11.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ff2b608725571be04f71d1f3a188c5fc38fdded08a973a221eef1e9d9c8acd0a
|
|
| MD5 |
6519c3dadf10676c3e8c779f25c0eaa6
|
|
| BLAKE2b-256 |
ecd19967526baff886cd350d5b4be015e3e8f81d140f8e2fe989701e93096cd3
|