Documentation Index Fetch the complete documentation index at: https://mintlify.com/buttondown/cli/llms.txt
Use this file to discover all available pages before exploring further.
Automations allow you to send emails automatically based on triggers and conditions. The Buttondown CLI lets you manage automations as JSON files in your local folder.
Automation Files
Automations are stored in the automations/ directory as JSON files:
my-newsletter/
├── automations/
│ ├── welcome-series.json
│ ├── re-engagement.json
│ └── premium-onboarding.json
├── emails/
└── media/
Automation Structure
Each automation is a JSON file with the following structure:
automations/welcome-series.json
{
"id" : "auto_abc123" ,
"name" : "Welcome Series" ,
"status" : "active" ,
"trigger" : {
"type" : "subscriber_created"
},
"actions" : [
{
"type" : "send_email" ,
"delay" : 0 ,
"email_id" : "welcome-day-1"
},
{
"type" : "send_email" ,
"delay" : 86400 ,
"email_id" : "welcome-day-2"
},
{
"type" : "send_email" ,
"delay" : 172800 ,
"email_id" : "welcome-day-3"
}
],
"filters" : {
"predicate" : "and" ,
"filters" : [],
"groups" : []
},
"metadata" : {
"series" : "onboarding" ,
"version" : "2.0"
},
"should_evaluate_filter_after_delay" : false
}
Automation Fields
Buttondown’s unique identifier. Automatically added after first push. Don’t modify manually.
The automation name displayed in your Buttondown dashboard.
Automation status: active, paused, or archived.
What event starts the automation. See Triggers below.
List of actions to perform. See Actions below.
Subscriber targeting rules. See Filters below.
Custom key-value pairs for organizing automations. "metadata" : {
"category" : "onboarding" ,
"owner" : "marketing-team"
}
should_evaluate_filter_after_delay
If true, filters are re-evaluated after each action delay. This allows you to stop sending if a subscriber no longer matches the filter.
Triggers
Triggers define when an automation starts:
Subscriber Created
Runs when someone subscribes:
{
"trigger" : {
"type" : "subscriber_created"
}
}
Email Opened
Runs when a subscriber opens a specific email:
{
"trigger" : {
"type" : "email_opened" ,
"email_id" : "welcome-email"
}
}
Email Clicked
Runs when a subscriber clicks a link in a specific email:
{
"trigger" : {
"type" : "email_clicked" ,
"email_id" : "announcement" ,
"url" : "https://example.com/pricing"
}
}
Tag Added
Runs when a specific tag is added to a subscriber:
{
"trigger" : {
"type" : "tag_added" ,
"tag" : "premium"
}
}
Custom Event
Runs when a custom event occurs:
{
"trigger" : {
"type" : "custom_event" ,
"event_name" : "course_completed"
}
}
Actions
Actions define what happens when the automation runs:
Send Email
Send a specific email after a delay:
{
"type" : "send_email" ,
"delay" : 86400 ,
"email_id" : "follow-up-day-2"
}
delay: Seconds to wait before sending (0 = immediate)
email_id: The email’s slug or Buttondown ID
Delays are in seconds:
1 hour = 3600
1 day = 86400
1 week = 604800
30 days = 2592000
Add Tag
Add a tag to the subscriber:
{
"type" : "add_tag" ,
"delay" : 0 ,
"tag" : "completed-onboarding"
}
Remove Tag
Remove a tag from the subscriber:
{
"type" : "remove_tag" ,
"delay" : 0 ,
"tag" : "trial-user"
}
Update subscriber metadata:
{
"type" : "update_metadata" ,
"delay" : 0 ,
"metadata" : {
"onboarding_completed" : "2024-03-15" ,
"cohort" : "march-2024"
}
}
Multiple Actions
Combine multiple actions in sequence:
{
"actions" : [
{
"type" : "send_email" ,
"delay" : 0 ,
"email_id" : "welcome"
},
{
"type" : "send_email" ,
"delay" : 86400 ,
"email_id" : "day-2-tips"
},
{
"type" : "send_email" ,
"delay" : 259200 ,
"email_id" : "day-3-resources"
},
{
"type" : "add_tag" ,
"delay" : 604800 ,
"tag" : "onboarding-complete"
}
]
}
Filters
Target specific subscribers using filter rules:
{
"filters" : {
"predicate" : "and" ,
"filters" : [
{
"field" : "tags" ,
"operator" : "contains" ,
"value" : "premium"
},
{
"field" : "email" ,
"operator" : "ends_with" ,
"value" : "@company.com"
}
],
"groups" : []
}
}
Filter Predicates
and: All filters must match
or: At least one filter must match
Filter Fields
tags - Subscriber tags
email - Email address
metadata - Subscriber metadata
subscription_date - When they subscribed
referrer_url - Referral source
Filter Operators
contains / does_not_contain
equals / does_not_equal
starts_with / ends_with
greater_than / less_than
Common Automation Patterns
Welcome Series
Onboard new subscribers with a sequence of emails:
automations/welcome-series.json
{
"name" : "Welcome Series" ,
"status" : "active" ,
"trigger" : {
"type" : "subscriber_created"
},
"actions" : [
{
"type" : "send_email" ,
"delay" : 0 ,
"email_id" : "welcome"
},
{
"type" : "send_email" ,
"delay" : 86400 ,
"email_id" : "getting-started"
},
{
"type" : "send_email" ,
"delay" : 259200 ,
"email_id" : "best-practices"
},
{
"type" : "add_tag" ,
"delay" : 604800 ,
"tag" : "welcomed"
}
]
}
Re-engagement Campaign
Re-engage inactive subscribers:
automations/re-engagement.json
{
"name" : "Re-engagement Campaign" ,
"status" : "active" ,
"trigger" : {
"type" : "tag_added" ,
"tag" : "inactive"
},
"actions" : [
{
"type" : "send_email" ,
"delay" : 0 ,
"email_id" : "we-miss-you"
},
{
"type" : "send_email" ,
"delay" : 604800 ,
"email_id" : "last-chance"
}
],
"filters" : {
"predicate" : "and" ,
"filters" : [
{
"field" : "tags" ,
"operator" : "does_not_contain" ,
"value" : "premium"
}
]
}
}
Product Launch Drip
Nurture interest after a product announcement:
automations/product-launch.json
{
"name" : "Product Launch Drip" ,
"status" : "active" ,
"trigger" : {
"type" : "email_clicked" ,
"email_id" : "product-announcement" ,
"url" : "https://example.com/new-product"
},
"actions" : [
{
"type" : "add_tag" ,
"delay" : 0 ,
"tag" : "interested-in-product"
},
{
"type" : "send_email" ,
"delay" : 86400 ,
"email_id" : "product-features"
},
{
"type" : "send_email" ,
"delay" : 259200 ,
"email_id" : "customer-stories"
},
{
"type" : "send_email" ,
"delay" : 432000 ,
"email_id" : "special-offer"
}
]
}
Conditional Filter Evaluation
Stop sending if subscriber upgrades:
automations/trial-sequence.json
{
"name" : "Trial User Sequence" ,
"status" : "active" ,
"trigger" : {
"type" : "tag_added" ,
"tag" : "trial"
},
"actions" : [
{
"type" : "send_email" ,
"delay" : 0 ,
"email_id" : "trial-start"
},
{
"type" : "send_email" ,
"delay" : 604800 ,
"email_id" : "trial-reminder"
},
{
"type" : "send_email" ,
"delay" : 1209600 ,
"email_id" : "trial-ending"
}
],
"filters" : {
"predicate" : "and" ,
"filters" : [
{
"field" : "tags" ,
"operator" : "contains" ,
"value" : "trial"
},
{
"field" : "tags" ,
"operator" : "does_not_contain" ,
"value" : "paid"
}
]
},
"should_evaluate_filter_after_delay" : true
}
With should_evaluate_filter_after_delay: true, the automation stops sending emails if the subscriber gets the “paid” tag during the sequence.
Managing Automations
Pulling Automations
Download your automations from Buttondown:
Automations are saved to automations/ with slugified filenames based on their names.
Creating New Automations
Create a new JSON file in automations/:
touch automations/new-automation.json
Edit the file with your automation configuration (without an id field for new automations):
automations/new-automation.json
{
"name" : "New Automation" ,
"status" : "active" ,
"trigger" : {
"type" : "subscriber_created"
},
"actions" : [
{
"type" : "send_email" ,
"delay" : 0 ,
"email_id" : "welcome"
}
]
}
Pushing Automations
Upload your automations to Buttondown:
The CLI:
Creates new automations (without an id)
Updates existing automations (with an id)
Editing Automations
Edit the JSON file directly:
code automations/welcome-series.json
Then push your changes:
File Naming
Automation filenames are generated by slugifying the automation name:
function slugify ( name : string ) : string {
return name
. toLowerCase ()
. replace ( / [ ^ a-z0-9 ] + / g , "-" )
. replace ( / ^ - + | - + $ / g , "" );
}
Examples:
“Welcome Series” → welcome-series.json
“Re-engagement Campaign” → re-engagement-campaign.json
“Product Launch (2024)” → product-launch-2024.json
Validation
The CLI validates automation files when reading them. From src/sync/automations.ts:55:
export function deserialize ( content : string ) : {
automation : Partial < Automation >;
isValid : boolean ;
error ?: string ;
} {
try {
const parsed = JSON . parse ( content );
// ...
if ( ! automation . name ) {
return {
automation ,
isValid: false ,
error: "Missing required field: name" ,
};
}
return { automation , isValid: true };
} catch {
return {
automation: {},
isValid: false ,
error: "Invalid JSON" ,
};
}
}
Common validation errors:
Invalid JSON syntax
Missing required field: name
Invalid trigger type
Invalid action type
Best Practices
Test with paused status first
Create new automations with "status": "paused" to test them before activating: {
"name" : "New Automation" ,
"status" : "paused" ,
// ...
}
Choose clear automation names that explain their purpose: ✓ "Welcome Series - 3 Day Onboarding"
✓ "Re-engage Inactive Subscribers"
✗ "Automation 1"
✗ "Test"
Add metadata for organization
Document delay calculations
Add comments (in metadata) explaining delay timing: {
"type" : "send_email" ,
"delay" : 604800 ,
"email_id" : "week-1-followup" ,
"metadata" : {
"delay_note" : "7 days = 604800 seconds"
}
}
Troubleshooting
”Missing required field: name”
Make sure every automation has a name field:
{
"name" : "My Automation" ,
// ...
}
“Invalid JSON”
Check for:
Missing commas between fields
Trailing commas in arrays or objects
Unquoted string values
Unclosed brackets or braces
Use a JSON validator or editor with syntax highlighting.
”Email not found”
Make sure the email_id in actions matches an existing email slug:
{
"type" : "send_email" ,
"email_id" : "welcome" // Must match an email's slug field
}
Next Steps
Manage Emails Create emails to use in automations
Push Content Deploy your automations to Buttondown