Skip to main content

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.

The Buttondown CLI makes it easy to manage your newsletter emails as Markdown files with YAML frontmatter, enabling a local-first workflow with your favorite tools.

Creating New Emails

Use the create command to generate a new draft email:
buttondown create "My First Newsletter"
This creates a new file at emails/my-first-newsletter.md:
emails/my-first-newsletter.md
---
subject: My First Newsletter
status: draft
email_type: public
slug: my-first-newsletter
created: 2024-03-15T10:30:00.000Z
modified: 2024-03-15T10:30:00.000Z
---

Write your email content here...
The slug is automatically generated from the title by converting to lowercase and replacing non-alphanumeric characters with hyphens.

How Create Works

From src/commands/create.tsx:23:
const slug = title
  .toLowerCase()
  .replaceAll(/[^a-z\d]+/g, "-")
  .replaceAll(/(^-|-$)/g, "");

const filePath = path.join(emailsDir, `${slug}.md`);
The command:
  1. Creates the emails/ directory if it doesn’t exist
  2. Generates a URL-safe slug from the title
  3. Checks if a file with that slug already exists
  4. Creates a new Markdown file with default frontmatter

Email File Structure

Emails are stored as Markdown files with YAML frontmatter:
---
# Required fields
subject: Your email subject line

# Status: draft, scheduled, or sent
status: draft

# Type: public, private, or premium
email_type: public

# URL-friendly identifier
slug: email-slug

# Optional fields
id: email_abc123           # Buttondown ID (added after first push)
publish_date: 2024-03-20T10:00:00Z
description: Email preview text
image: https://example.com/og-image.png
canonical_url: https://example.com/blog/post
featured: false
commenting_mode: enabled   # enabled, disabled, or paid_only

# Metadata (custom key-value pairs)
metadata:
  campaign: spring-2024
  category: announcement

# Filters (subscriber targeting)
filters:
  predicate: and
  filters:
    - field: tags
      operator: contains
      value: premium
  groups: []

# Related emails
related_email_ids:
  - email_xyz789
  - email_def456

# Attachments
attachments:
  - https://example.com/file.pdf
  - https://example.com/document.zip
---

# Email content starts here

This is your email body written in **Markdown**.

You can use all standard Markdown features:

- Lists
- **Bold** and *italic*
- [Links](https://example.com)
- Images

![Hero Image](../media/hero.png)

Frontmatter Fields

Core Fields

subject
string
required
The email subject line that subscribers will see in their inbox.
status
string
default:"draft"
Email status: draft, scheduled, or sent.
email_type
string
default:"public"
Audience type: public, private, or premium.
slug
string
required
URL-friendly identifier used in web archive links.

Optional Fields

id
string
Buttondown’s unique identifier. Automatically added after first push. Don’t modify this manually.
publish_date
string
ISO 8601 timestamp for when to send/publish the email.
publish_date: 2024-03-20T15:30:00Z
description
string
Preview text shown in email clients and web archives.
image
string
Open Graph image URL for social media sharing.
canonical_url
string
Original URL if this email is republished content.
Whether to feature this email prominently in your web archive.
commenting_mode
string
default:"enabled"
Comment settings: enabled, disabled, or paid_only.
metadata
object
Custom key-value pairs for organizing and filtering emails.
metadata:
  campaign: product-launch
  category: feature-update
  internal_id: "2024-Q1-003"
filters
object
Subscriber targeting rules. See Subscriber Filters below.
List of related email IDs to show in the web archive.
related_email_ids:
  - email_abc123
  - email_def456
attachments
array
URLs of files to attach to the email.
attachments:
  - https://example.com/report.pdf
  - https://example.com/slides.pptx

Subscriber Filters

Target specific subscribers using filter rules:
filters:
  predicate: and  # or "or"
  filters:
    - field: tags
      operator: contains
      value: premium
    - field: email
      operator: ends_with
      value: "@company.com"
  groups: []

Filter Fields

  • tags - Subscriber tags
  • email - Email address
  • metadata - Subscriber metadata
  • subscription_date - When they subscribed
  • referrer_url - How they found you

Filter Operators

  • contains / does_not_contain
  • equals / does_not_equal
  • starts_with / ends_with
  • greater_than / less_than

Working with Email Content

Using Markdown

Write your email body using standard Markdown:
# Main Headline

Welcome to this week's newsletter! Here's what we're covering:

1. **New feature launch** - We've added dark mode
2. **Community spotlight** - Meet our power users
3. **Tips & tricks** - Keyboard shortcuts

## Feature Launch

We're excited to announce dark mode!

![Dark Mode Screenshot](../media/dark-mode.png)

[Try it now](https://example.com/settings)

Adding Images

Reference images from your media/ directory using relative paths:
![Alt text](../media/image-name.png)
When you push, the CLI automatically uploads the image and converts the path to a Buttondown URL.

Using Variables

Buttondown supports template variables:
Hello {{ subscriber.name }},

Thanks for subscribing!

Unsubscribe: {{ unsubscribe_url }}
View online: {{ view_online_url }}
Common variables:
  • {{ subscriber.name }} - Subscriber’s name
  • {{ subscriber.email }} - Subscriber’s email
  • {{ unsubscribe_url }} - Unsubscribe link
  • {{ view_online_url }} - Web archive link
  • {{ manage_subscription_url }} - Preferences page

Including Snippets

Reference reusable snippets:
{{ snippet.footer }}
{{ snippet.social-links }}
See Working with Snippets for more details.

Editing Existing Emails

Pull emails from Buttondown to edit them locally:
1

Pull content

buttondown pull
2

Edit the email file

Open any email in emails/ with your text editor:
code emails/weekly-update.md
3

Push changes

buttondown push
Never modify the id field manually. This is Buttondown’s unique identifier and changing it will cause the CLI to create a duplicate email.

Organizing Emails

You can organize emails in subdirectories:
emails/
├── drafts/
│   ├── idea-1.md
│   └── idea-2.md
├── series/
│   ├── onboarding-day-1.md
│   ├── onboarding-day-2.md
│   └── onboarding-day-3.md
└── sent/
    ├── 2024-03-01-weekly.md
    └── 2024-03-08-weekly.md
The CLI will find all .md files recursively in the emails/ directory.

Email Lifecycle

Draft → Scheduled → Sent

# 1. Start as draft
---
status: draft
---

# 2. Schedule for sending
---
status: scheduled
publish_date: 2024-03-20T10:00:00Z
---

# 3. After sending
---
status: sent
publish_date: 2024-03-20T10:00:00Z  # When it was sent
---

Validation

The CLI validates email files when reading them. From src/sync/emails.ts:76:
export function deserialize(content: string): {
  email: Partial<Email>;
  isValid: boolean;
  error?: string;
} {
  const parts = content.split("---");
  if (parts.length < 3) {
    return {
      email: { body: content },
      isValid: false,
      error: "Invalid format (missing frontmatter)",
    };
  }
  // ...
}
Common validation errors:
  • Missing frontmatter delimiters (---)
  • Invalid YAML syntax
  • Missing required fields

Best Practices

Choose clear, descriptive slugs that make sense in your web archive URL:
# Good
slug: march-2024-product-updates

# Not ideal
slug: newsletter-123
Always store images in the media/ directory and use relative paths:
![Logo](../media/logo.png)  ✓
![Logo](&lt;absolute-path&gt;/logo.png)  ✗
Use a Markdown previewer to check formatting before pushing to Buttondown.
Use the metadata field to organize and filter emails:
metadata:
  series: onboarding
  week: 1
  category: tutorial

Next Steps

Push Content

Push your emails to Buttondown

Manage Media

Work with images and attachments