Skip to main content

Acumbamail

Complete Python SDK, CLI, and automation workflows for Acumbamail email marketing.

PyPI version Python versions License: MIT CI Monthly downloads

Download OpenAPI spec Download Postman collection


Table of Contents


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 clientsAcumbamailClient and AsyncAcumbamailClient share 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 jq or 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 list --status inactive          # unsubscribes + bounces, account-wide
$ acumbamail subscribers check --list-id 123 --email user@example.com
$ 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 diff shows 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 recoveryacumbamail automations export pulls 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 login once — 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 — active only
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 — active and unsubscribed only
get_inactive_subscribers(date_from, date_to, full_info) Unsubscribes and bounces in a date range — account-wide, no list filter

Before you conclude "this email isn't in the list", read What the API doesn't show you. Three of these methods return incomplete answers by design, and none of them tell you so.

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

What the API doesn't show you

Three silences in the Acumbamail API that cost real money if you trust the obvious reading. All three were found the hard way, auditing a list before a send.

A subscriber list has more people in it than any endpoint will list. get_subscribers() returns active subscribers only. lists list reports a subscribers_count that also includes unsubscribes and bounces. Compare the two numbers and it looks like subscribers went missing. They didn't: they're inactive, and inactive people are only reachable through a different endpoint.

That different endpoint is account-wide and lies about it. getInactiveSubscribers accepts a list_id, returns HTTP 200, and ignores it. Pass a list ID that doesn't exist and it still hands back every inactive address in the account. The records come back with a null list ID, so filtering afterwards isn't possible either. This SDK refuses list_id outright rather than let you believe you filtered:

client.get_inactive_subscribers(list_id=123, date_from=..., date_to=...)
# AcumbamailValidationError: get_inactive_subscribers() is global to the account...

And some subscribers are invisible to every listing there is. Anyone who signed up through a form but never confirmed the double opt-in sits at status='not verified' and appears in nothing: not get_subscribers, not search_subscriber, not get_inactive_subscribers, not the CSV exports. The only way to see one is to ask about that exact address:

$ acumbamail subscribers check --list-id 123 --email someone@example.com
[{"email": "someone@example.com", "found": true, "status": "not verified", "id": 5774498602, "fields": null}]

Run that before importing a list of addresses you believe are new. "Doesn't appear" is not the same as "isn't there", and adding someone who is already in the list at another status is how people end up emailing addresses they meant to leave alone.

One more thing worth knowing if you read reason codes: 1 is a bounce and 3 is a voluntary unsubscribe. Acumbamail's own documentation had it backwards. Verified by cross-checking 805 inactive records against the CSV exports for the same list. Use reason_label and skip the integers.

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.

n8n community node npm version

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)

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

acumbamail-0.4.0.tar.gz (200.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

acumbamail-0.4.0-py3-none-any.whl (74.5 kB view details)

Uploaded Python 3

File details

Details for the file acumbamail-0.4.0.tar.gz.

File metadata

  • Download URL: acumbamail-0.4.0.tar.gz
  • Upload date:
  • Size: 200.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for acumbamail-0.4.0.tar.gz
Algorithm Hash digest
SHA256 87f82cea4bc1ff18fa9aea0f867f4688d7163d202ae93acb3ff1b368e3b1264e
MD5 586f0af68200a33acd7d44c8d776edf9
BLAKE2b-256 512723e06a28ab37295fd6109dac3c7f77b917edb8039fdadc56e86f74980d25

See more details on using hashes here.

Provenance

The following attestation bundles were made for acumbamail-0.4.0.tar.gz:

Publisher: publish-to-pypi.yaml on cr0hn/python-acumbamail

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file acumbamail-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: acumbamail-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 74.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for acumbamail-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6b25a2b54f9fc16024671e56b96ba4c6086f5b76871639702d71f8a44291e645
MD5 565d2c8c316565f998a8c1a1676ff083
BLAKE2b-256 7c454b337ade21a83dc6327bb76eaeacfef1dea362c6ffbe3a1c0062ff32be8d

See more details on using hashes here.

Provenance

The following attestation bundles were made for acumbamail-0.4.0-py3-none-any.whl:

Publisher: publish-to-pypi.yaml on cr0hn/python-acumbamail

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.4.0 This release

2 files

0.3.1

2 files

0.1.5

1 file

0.1.4

2 files

0.1.3

2 files

0.1.2

2 files

0.1.1

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page