A comprehensive Rust CLI for MailChimp marketing automation, designed for scripting and AI agent integration. JSON-first output with TSV fallback for human readability.
- Full campaign lifecycle — create, content, test, schedule, send, and report
- Audience management — lists, members, tags, segments, batch operations
- Template browsing — list templates and export HTML
- Campaign analytics — opens, clicks, growth reports
- OAuth2 authentication — browser-based login with multi-account support
- Secure token storage — system keyring (macOS Keychain, Linux Secret Service) with file fallback
- Structured output — JSON/NDJSON (default) and TSV (
--plain) for easy parsing - Shell completions — Bash, Zsh, Fish, PowerShell
- AI agent skill — ships with a Claude Code / Codex skill for natural language access
Requires Rust (edition 2024).
The install script builds the binary, copies it to ~/.local/bin, and installs the AI agent skill to any detected agents (Claude Code, Codex):
./install.sh# Build
cargo build --release
# Install binary
mkdir -p ~/.local/bin
cp ./target/release/mailchimp ~/.local/bin/mailchimp
# Install Claude Code skill (optional)
mkdir -p ~/.claude/skills/mailchimp
ln -sf "$(pwd)/skills/mailchimp/SKILL.md" ~/.claude/skills/mailchimp/SKILL.md
ln -sfn "$(pwd)/skills/mailchimp/docs" ~/.claude/skills/mailchimp/docsMake sure ~/.local/bin is in your PATH:
export PATH="$HOME/.local/bin:$PATH"# Authenticate (opens browser for OAuth)
mailchimp auth login
# Or use a token directly
mailchimp --token mc-XXXXXXXX audiences list
# List your audiences
mailchimp audiences list
# List subscribers
mailchimp members list <audience_id>
# Create and send a campaign
mailchimp campaigns create --audience <aud_id> --subject "Newsletter"
mailchimp campaigns content <campaign_id> --html-file email.html
mailchimp campaigns test <campaign_id> "you@example.com"
mailchimp campaigns send <campaign_id>mailchimp auth login # OAuth browser flow
mailchimp auth logout # Remove credentials
mailchimp auth accounts # List authenticated accounts
mailchimp auth switch us19 # Switch default account
mailchimp auth status # Current auth statemailchimp audiences list # All audiences
mailchimp audiences list --sort-members # Sort by subscriber count
mailchimp audiences info <id> # Audience details
mailchimp audiences info '#My Audience' # Lookup by namemailchimp members list <aud> --status subscribed --limit 100
mailchimp members add <aud> user@example.com --merge-fields '{"FNAME":"Jo"}'
mailchimp members update <aud> user@example.com --status unsubscribed
mailchimp members remove <aud> user@example.com --force
mailchimp members unsubscribe <aud> user@example.com
mailchimp members search "john" --audience <aud>mailchimp tags list <aud> # All tags with counts
mailchimp tags add <aud> user@example.com "vip,beta"
mailchimp tags remove <aud> user@example.com "beta"
mailchimp tags members <aud> "vip" # Members with tagmailchimp segments list <aud>
mailchimp segments info <aud> <segment_id>
mailchimp segments members <aud> <segment_id>
mailchimp segments create <aud> --name "Active" --conditions '<json>'
mailchimp segments update <aud> <seg_id> --name "New Name"
mailchimp segments delete <aud> <seg_id> --forcemailchimp campaigns list --status sent --sort-sent
mailchimp campaigns info <id>
mailchimp campaigns create --audience <aud> --subject "Newsletter"
mailchimp campaigns content <id> --html-file email.html
mailchimp campaigns test <id> "test@example.com"
mailchimp campaigns schedule <id> --time "tomorrow at 10am"
mailchimp campaigns unschedule <id>
mailchimp campaigns send <id> --force
mailchimp campaigns delete <id> --forcemailchimp templates list --type user --sort-recent
mailchimp templates info <id>
mailchimp templates info <id> --html-only # Raw HTML to stdout
mailchimp templates info <id> --output tpl.html # Save HTML to filemailchimp reports campaign <id> # Full campaign stats
mailchimp reports clicks <id> # Click details
mailchimp reports opens <id> --limit 100 # Open details
mailchimp reports growth <aud> --period month --months 12# Add to ~/.bashrc or ~/.zshrc
eval "$(mailchimp completions bash)"
eval "$(mailchimp completions zsh)"
# Or save to file
mailchimp completions fish > ~/.config/fish/completions/mailchimp.fishBy default, output is JSON (single objects) or NDJSON (one JSON object per line for lists). Use --plain for TSV with a header row:
# JSON (default)
mailchimp audiences list
# TSV output
mailchimp audiences list --plain# Count subscribers
mailchimp members list <aud> --plain | tail -n +2 | wc -l
# Extract a field with jq
mailchimp audiences info <id> | jq '.member_count'
# Export subscriber emails
mailchimp members list <aud> --plain | cut -f1 | tail -n +2| Flag | Env Var | Description |
|---|---|---|
--plain |
— | TSV output instead of JSON |
-a, --account <prefix> |
MAILCHIMP_ACCOUNT |
Use specific account (server prefix, e.g. us19) |
--token <token> |
MAILCHIMP_TOKEN |
API token (bypasses keyring) |
-v, --verbose |
— | Verbose logging to stderr |
--help |
— | Show help |
--version |
— | Show version |
| Variable | Description |
|---|---|
MAILCHIMP_TOKEN |
Default API token (overrides keyring) |
MAILCHIMP_ACCOUNT |
Default account (server prefix) |
MAILCHIMP_TOKEN_STORE_PATH |
Use file-based token storage instead of keyring |
MAILCHIMP_API_BASE_URL |
Override API base URL (for testing) |
MAILCHIMP_CLIENT_ID |
OAuth app client ID |
MAILCHIMP_CLIENT_SECRET |
OAuth app client secret |
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | General error |
| 2 | Authentication required |
| 3 | Invalid arguments |
| 4 | API error |
| 5 | Rate limited |
| 6 | Network error |
Commands that delete or send require confirmation. Use --force to skip the prompt:
members remove— permanent deletionsegments delete— removes segmentcampaigns send— sends to all recipientscampaigns delete— removes campaign
# List all authenticated accounts
mailchimp auth accounts
# Run a command against a specific account
mailchimp -a us19 audiences list
# Set the default account
mailchimp auth switch us19Batch commands process multiple subscribers from CSV files, making per-row API calls with immediate feedback.
email,FNAME,LNAME,status,tags
alice@example.com,Alice,Smith,subscribed,"vip,beta"
bob@example.com,Bob,Jones,pending,| Column | Required | Notes |
|---|---|---|
email |
Yes | Valid email address |
FNAME |
No | First name merge field |
LNAME |
No | Last name merge field |
status |
No | subscribed (default), pending, unsubscribed |
tags |
No | Comma-separated, quoted if they contain commas |
# Batch add subscribers from CSV
mailchimp members batch-add <aud> --file subscribers.csv
# Batch update existing subscribers
mailchimp members batch-update <aud> --file updates.csv
# Bulk tag from email list (one email per line)
mailchimp members batch-tag <aud> --emails emails.txt --tags "vip,premium"Exit codes for batch commands: 0 = all succeeded, 1 = some row errors (partial success), 4 = fatal error (aborted).
Full lifecycle for creating and sending campaigns:
# 1. Create draft
CAMPAIGN_ID=$(mailchimp campaigns create \
--audience abc123 \
--subject "March Newsletter" \
--from-name "Acme Inc" \
--reply-to news@acme.com \
--preview-text "What's new this month..." \
| jq -r '.id')
# 2. Set content from HTML file
mailchimp campaigns content "$CAMPAIGN_ID" --html-file ./newsletter.html
# 3. Send test
mailchimp campaigns test "$CAMPAIGN_ID" "team@acme.com"
# 4. Schedule for later
mailchimp campaigns schedule "$CAMPAIGN_ID" --time "tomorrow at 10am"
# 5. Check results after sending
mailchimp reports campaign "$CAMPAIGN_ID"Content sources (exactly one required):
| Flag | Description |
|---|---|
--html-file PATH |
HTML from file (most common) |
--html TEXT |
Inline HTML string |
--plain-text TEXT |
Plain text content |
--template-id ID |
Use MailChimp template (optional: --template-sections JSON) |
Schedule accepts ISO 8601 (2024-12-01T14:00:00Z), datetime (2024-12-01 14:00), or natural language (tomorrow at 10am).
Create segments to target subscriber subsets:
# Active subscribers who signed up this year
mailchimp segments create <aud> \
--name "2024 Signups" \
--conditions '{
"match": "all",
"conditions": [
{
"condition_type": "DateMerge",
"field": "timestamp_opt",
"op": "greater",
"value": "2024-01-01"
}
]
}'Condition JSON structure:
{
"match": "all | any",
"conditions": [
{
"condition_type": "TextMerge | DateMerge | Aim | MemberRating | StaticSegment | ...",
"field": "merge0 | merge1 | timestamp_opt | aim | member_rating | ...",
"op": "is | not | contains | greater | less | open | click | ...",
"value": "..."
}
]
}Common condition types:
| Type | Field | Operations |
|---|---|---|
TextMerge |
merge0 (email), merge1 (FNAME), merge2 (LNAME) |
is, not, contains, starts, ends, blank |
DateMerge |
timestamp_opt |
is, not, greater, less, blank |
Aim |
aim |
open, click, sent, noopen, noclick |
MemberRating |
member_rating |
greater, less, is |
StaticSegment |
static_segment |
static_is, static_not |
# Build
cargo build
# Run tests
cargo test
# Lint (must pass clean)
cargo clippy --all-targets --all-features -- -D warnings
# Format check
cargo fmt --check
# Run locally
cargo run -- audiences list --token TOKENAll three checks (test, clippy, fmt) must pass before submitting changes.
MIT