Mailchimp Skill
Manage Mailchimp email campaigns, audiences, templates, and reports via the Mailchimp Marketing API v3.
Authentication
Set MAILCHIMP_API_KEY in your environment or .env file. The API key format is {key}-{dc} where dc is the datacenter (e.g., us7). The scripts auto-extract the datacenter to build the correct base URL.
export MAILCHIMP_API_KEY="your-api-key-us7"
Important: Template Types & Content Population
Mailchimp has two editor types with very different API support:
| Feature | Classic/Legacy (content_type: "template") |
New Editor (content_type: "multichannel") |
|---|---|---|
| Editable regions | mc:edit="section_name" attributes |
data-block-id attributes |
| API content population | Full support via template.sections |
NOT supported |
| Template creation via API | POST /templates |
Not available |
| Default content discovery | GET /templates/{id}/default-content |
Returns empty |
For programmatic content control, always use classic/legacy templates with mc:edit regions. The Mailchimp API v3 has no endpoints for editing multichannel (new editor) content blocks.
How Template Sections Work
Build an HTML email template with
mc:editattributes on editable elements:<td mc:edit="header_title"><h1>Default Title</h1></td> <td mc:edit="hero_image"><img src="placeholder.jpg"/></td> <td mc:edit="body_text"><p>Default body content</p></td>Upload the template via
POST /templates(creates a classic template)Discover available sections:
python scripts/templates.py content --template-id 12345 # Returns { "sections": { "header_title": "...", "hero_image": "...", ... } }Create a campaign and populate all sections in one call:
python scripts/campaign_content.py set --campaign-id abc123 \ --template-id 12345 --sections '{"header_title": "<h1>April News</h1>", "hero_image": "<img src=\"https://example.com/photo.jpg\"/>"}'Both text and image sections are populated via
template.sections. Image sections contain<img>tags with the desiredsrcURL.
Working with Existing Multichannel Templates
If existing templates use the multichannel editor, the create-from-template command provides a workaround:
- It finds a sent campaign that used the template
- Replicates it (preserving the multichannel content)
- Updates campaign settings (subject, from, etc.)
Warning: Setting raw HTML via PUT /content on a multichannel campaign converts it to legacy builder and breaks the visual editor rendering. Only update settings on replicated multichannel campaigns, never the content.
Scripts
All scripts live in scripts/ and share a common auth.py module. They:
- Accept
--api-keyflag or readMAILCHIMP_API_KEYenv var - Support
--outputflag to save JSON results to a file - Print JSON to stdout by default
- Handle pagination (
--count,--offset) - Include retry logic with exponential backoff on 429/5xx errors
Audiences (audiences.py)
Manage audiences (lists), members, search, and tags.
# List all audiences
python scripts/audiences.py list [--count 10] [--offset 0]
# Get audience details
python scripts/audiences.py get --list-id abc123
# List members of an audience
python scripts/audiences.py members --list-id abc123 [--status subscribed] [--count 100]
# Add a member
python scripts/audiences.py add-member --list-id abc123 --email user@example.com \
[--status subscribed] [--merge-fields '{"FNAME":"John","LNAME":"Doe"}']
# Update a member
python scripts/audiences.py update-member --list-id abc123 --email user@example.com \
[--status unsubscribed] [--merge-fields '{"FNAME":"Johnny"}']
# Remove (archive) a member
python scripts/audiences.py remove-member --list-id abc123 --email user@example.com
# Search members across all audiences (or within one)
python scripts/audiences.py search --query "john" [--list-id abc123]
# List tags for an audience
python scripts/audiences.py tags --list-id abc123
# Add tags to a member
python scripts/audiences.py tag-member --list-id abc123 --email user@example.com \
--tags '["VIP","Newsletter"]' --status active
# Remove tags from a member
python scripts/audiences.py untag-member --list-id abc123 --email user@example.com \
--tags '["VIP"]'
Campaigns (campaigns.py)
CRUD operations plus send, schedule, unschedule, replicate, and create-from-template.
# List campaigns
python scripts/campaigns.py list [--status sent] [--count 20] [--offset 0] \
[--since-send-time 2025-01-01] [--before-send-time 2025-12-31]
# Get campaign details
python scripts/campaigns.py get --campaign-id abc123
# Create a campaign (blank)
python scripts/campaigns.py create --list-id abc123 --subject "Newsletter" \
--from-name "Company" --reply-to "info@company.com" \
[--type regular] [--title "Internal Title"] [--preview-text "Preview..."]
# Create from template (finds and replicates a sent campaign using the template)
python scripts/campaigns.py create-from-template --template-id 12345 \
--subject "April Newsletter" --from-name "Company" --reply-to "info@co.com" \
[--title "April 2026"] [--preview-text "Preview..."] \
[--source-campaign-id abc123] [--list-id xyz789]
# Update campaign settings
python scripts/campaigns.py update --campaign-id abc123 \
[--subject "New Subject"] [--from-name "New Name"] [--reply-to "new@co.com"] \
[--title "New Title"] [--preview-text "New preview"]
# Delete a campaign
python scripts/campaigns.py delete --campaign-id abc123
# Send a campaign (IRREVERSIBLE - sends to all recipients)
python scripts/campaigns.py send --campaign-id abc123
# Schedule a campaign
python scripts/campaigns.py schedule --campaign-id abc123 \
--schedule-time "2025-06-01T10:00:00+00:00"
# Unschedule a campaign
python scripts/campaigns.py unschedule --campaign-id abc123
# Replicate (copy) a campaign
python scripts/campaigns.py replicate --campaign-id abc123
Campaign Content (campaign_content.py)
Get or set campaign HTML content. Supports raw HTML, HTML files, template references, and template sections for programmatic content population.
# Get campaign content (returns HTML, plain_text, archive_html)
python scripts/campaign_content.py get --campaign-id abc123
# Set content from inline HTML
python scripts/campaign_content.py set --campaign-id abc123 --html "<html>...</html>"
# Set content from an HTML file
python scripts/campaign_content.py set --campaign-id abc123 --html-file path/to/email.html
# Set content from a template (uses template defaults)
python scripts/campaign_content.py set --campaign-id abc123 --template-id 12345
# Set content from a template with custom sections (programmatic population)
python scripts/campaign_content.py set --campaign-id abc123 --template-id 12345 \
--sections '{"header_title": "<h1>Custom Title</h1>", "body": "<p>Custom body</p>"}'
Reports (reports.py)
Campaign performance analytics.
# Campaign report summary (opens, clicks, bounce rate, etc.)
python scripts/reports.py summary --campaign-id abc123
# Open details (who opened)
python scripts/reports.py opens --campaign-id abc123 [--count 100] [--offset 0]
# Click details (which links were clicked)
python scripts/reports.py clicks --campaign-id abc123 [--count 100] [--offset 0]
# Subscriber activity for a campaign
python scripts/reports.py activity --campaign-id abc123 [--count 100] [--offset 0]
Templates (templates.py)
List, inspect, and get default content for email templates.
# List templates
python scripts/templates.py list [--count 20] [--offset 0] [--type user]
# Get template info (name, type, content_type, dates)
python scripts/templates.py get --template-id 12345
# Get template default content (returns mc:edit sections with their default HTML)
python scripts/templates.py content --template-id 12345
Typical Workflow: Create & Populate a Newsletter
# 1. Create a campaign
python scripts/campaigns.py create --list-id 090ac27822 \
--subject "May 2026 Newsletter" --from-name "Your Company" \
--reply-to "news@example.com" --title "May 2026 Newsletter" \
--preview-text "The latest product news"
# Returns: {"id": "abc123", ...}
# 2. Discover template sections
python scripts/templates.py content --template-id 13613235
# Returns: {"sections": {"header_title": "...", "hero_image": "...", ...}}
# 3. Populate content via template + sections
python scripts/campaign_content.py set --campaign-id abc123 --template-id 13613235 \
--sections '{
"header_title": "<h1>MAY IN REWIND<br><span style=\"color:#FBAB13;\">AT YOUR COMPANY</span></h1>",
"hero_image": "<img src=\"https://images.unsplash.com/photo-xxx?w=600\" style=\"width:100%;border-radius:12px;\"/>",
"key_takeaways": "<ul><li>First item</li><li>Second item</li></ul>",
"success_progress": "<p>We shipped feature X...</p>"
}'
# 4. Verify content
python scripts/campaign_content.py get --campaign-id abc123
# 5. Schedule or send
python scripts/campaigns.py schedule --campaign-id abc123 \
--schedule-time "2026-05-01T08:00:00+00:00"
Pagination
Mailchimp uses offset and count parameters:
count: Number of records per request (default 10, max 1000)offset: Number of records to skip
All list commands accept --count and --offset. To fetch all records, increase --count to 1000 or paginate manually.
Error Handling
Scripts handle common HTTP errors:
400Bad Request: Invalid parameters401Unauthorized: Invalid API key403Forbidden: Insufficient permissions404Not Found: Invalid resource ID429Too Many Requests: Rate limited (auto-retry with backoff)5xxServer Error: Transient (auto-retry with backoff)
API Reference
- Base URL:
https://{dc}.api.mailchimp.com/3.0/ - Auth: HTTP Basic (
("anystring", api_key)) - Full docs: https://mailchimp.com/developer/marketing/api/