Command-line interface for Bento email marketing. Manage subscribers, tags, events, and broadcasts directly from your terminal.
# Recommended: Run with npx (always uses latest version)
npx @bentonow/bento-cli --help
# Or install globally
npm install -g @bentonow/bento-cli
# Or with Bun
bun install -g @bentonow/bento-cliTip: Using
npxis recommended when possibleβit always fetches the latest version, requires no installation, and is ideal for CI/CD pipelines.
# 1. Authenticate with your Bento credentials
bento auth login
# 2. Verify your connection
bento stats site
# 3. Start managing your subscribers
bento subscribers search --email user@example.com
bento tags listThe CLI uses your Bento API credentials. Get them from Settings > Teams in your Bento dashboard.
# Interactive login (prompts for credentials)
bento auth login
# Non-interactive login (for CI/scripts)
bento auth login \
--publishable-key "your-publishable-key" \
--secret-key "your-secret-key" \
--site-uuid "your-site-uuid"
# Or run directly with npx (no install required)
npx @bentonow/bento-cli auth login \
--publishable-key "your-publishable-key" \
--secret-key "your-secret-key" \
--site-uuid "your-site-uuid"
# Check authentication status
bento auth status
# Log out (removes stored credentials)
bento auth logoutManage multiple Bento accounts (e.g., production, staging):
# Add a named profile
bento profile add production
bento profile add staging
# Switch between profiles
bento profile use staging
# List all profiles
bento profile list
# Remove a profile
bento profile remove stagingCredentials are stored securely at:
- macOS:
~/Library/Application Support/bento/config.json - Linux:
~/.config/bento/config.json - Windows:
%APPDATA%/bento/config.json
# Open the Bento dashboard for your active profile
bento dashboard
# Target a specific profile
bento dashboard --profile staging
# JSON mode still opens the browser but prints machine-readable output
bento dashboard --json# Look up a subscriber
bento subscribers search --email user@example.com
# Look up + check if they have a tag
bento subscribers search --email user@example.com --tag vip
# Look up + check field value
bento subscribers search --email user@example.com --field plan=pro
# Import subscribers from CSV (email column required)
bento subscribers import contacts.csv
bento subscribers import contacts.csv --dry-run # Preview first
bento subscribers import contacts.csv --limit 100 # Import first 100
# Add/remove tags from subscribers
bento subscribers tag --email user@example.com --add vip
bento subscribers tag --email user@example.com --remove trial
bento subscribers tag --file users.csv --add customer,active --confirm
# Unsubscribe (stop email delivery)
bento subscribers unsubscribe --email user@example.com
bento subscribers unsubscribe --file unsubscribes.csv --confirm
# Re-subscribe (restore email delivery)
bento subscribers subscribe --email user@example.com# List all tags
bento tags list
# Search tags by name
bento tags list news # Fuzzy match on tag name
# Create a new tag
bento tags create "new-feature-announcement"
# Delete a tag (via web interface - API limitation)
bento tags delete "old-tag"# List all custom fields
bento fields list
# Search fields by key or name
bento fields list company # Fuzzy match on field key + name
# Create a new field
bento fields create company_sizeTrack custom events to trigger automations:
# Track a simple event
bento events track --email user@example.com --event signed_up
# Track with details
bento events track \
--email user@example.com \
--event purchase \
--details '{"product": "Pro Plan", "amount": 99}'# List all broadcasts
bento broadcasts list
# Paginate results
bento broadcasts list --page 1 --per-page 10
# Notes:
# - `--page` by itself uses Bento's API pagination (25 results per page, minimal memory).
# - Adding `--per-page` tells the CLI to fetch the full list once and slice locally, so prefer
# smaller values here when you have very large broadcast histories.
# Create a broadcast draft
bento broadcasts create \
--name "January Newsletter" \
--subject "What's new this month" \
--content "<h1>Hello!</h1><p>Here's our update...</p>" \
--type html \
--include-tags "newsletter,active"# List all sequences
bento sequences list
# Create a sequence email with inline HTML
bento sequences create-email \
--sequence-id sequence_abc123 \
--subject "Welcome to Bento" \
--html "<h1>Hi there</h1>" \
--delay-interval days \
--delay-count 7
# Create from an HTML file
bento sequences create-email \
--sequence-id sequence_abc123 \
--subject "Day 2 follow-up" \
--html-file ./emails/day-2.html
# Update an existing sequence email template
bento sequences update-email \
--template-id 12345 \
--subject "Updated Welcome Subject"
# Update subject + HTML inline
bento sequences update-email \
--template-id 12345 \
--subject "Refined Subject" \
--html "<h1>Updated body</h1>"
# Update HTML from file
bento sequences update-email \
--template-id 12345 \
--html-file ./emails/updated-welcome.htmlMaintainer release order for this feature:
- Ship the API endpoint in
bento. - Publish a
bento-node-sdkversion that includescreateSequenceEmail. - Release
bento-cliagainst that SDK version.
Current API limitation: sequence email updates support subject and html fields only.
# View site-wide stats
bento stats site| Flag | Description |
|---|---|
| (none) | Human-readable tables with colors |
--json |
Machine-readable JSON for scripting |
--quiet |
Minimal output (errors only) |
# Default: pretty tables
bento subscribers search --email user@example.com
# JSON for scripting
bento subscribers search --email user@example.com --json | jq '.data[].email'
# Quiet for automation (exit code only)
bento tags create "test-tag" --quiet && echo "Created!"All --json output follows a consistent schema:
{
"success": true,
"error": null,
"data": { ... },
"meta": {
"count": 10,
"total": 100
}
}Bulk operations include safety flags to prevent mistakes:
| Flag | Description |
|---|---|
--dry-run |
Preview what would happen without making changes |
--limit <n> |
Only process the first N items |
--sample <n> |
Show N sample items in the preview |
--confirm |
Skip interactive confirmation (for scripts) |
# Preview an import without executing
bento subscribers import big-list.csv --dry-run
# Import only the first 10 rows to test
bento subscribers import big-list.csv --limit 10
# Tag subscribers non-interactively (CI/scripts)
bento subscribers tag --file users.csv --add customer --confirm| Variable | Description |
|---|---|
BENTO_CONFIG_PATH |
Override config file location |
BENTO_API_BASE_URL |
Override API endpoint (for testing) |
BENTO_AUTO_CONFIRM |
Set to true to skip all confirmations |
DEBUG |
Set to bento for verbose SDK logging |
CSV files must have an email column (case-insensitive). The CLI recognizes these special columns:
| Column | Description |
|---|---|
email |
Required. Subscriber email address |
name |
Optional. Subscriber display name |
tags |
Optional. Tags to add (comma or semicolon separated) |
remove_tags |
Optional. Tags to remove (comma or semicolon separated) |
| (other) | Any other columns become custom fields |
Basic import with custom fields:
email,first_name,last_name,plan
alice@example.com,Alice,Smith,pro
bob@example.com,Bob,Jones,starterImport with inline tag assignment:
email,name,tags,remove_tags,company
jesse@example.com,Jesse Hanley,"customer,mql",lead,Acme Inc
alice@example.com,Alice Smith,"newsletter,active",,Widgets CoA simple CSV with an email column:
email
alice@example.com
bob@example.comOr a plain text file with one email per line (no header needed):
alice@example.com
bob@example.com
Note: Email lists are automatically deduplicated and normalized to lowercase.
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | General error |
| 2 | Invalid arguments or usage |
| 6 | CSV parsing error |
#!/bin/bash
# Export active users and sync to Bento
psql -c "COPY (SELECT email, name FROM users WHERE active) TO STDOUT CSV HEADER" \
> /tmp/active-users.csv
# Using npx (no install required)
npx @bentonow/bento-cli subscribers import /tmp/active-users.csv --confirm --json
# Or if installed globally
bento subscribers import /tmp/active-users.csv --confirm --json#!/bin/bash
# Tag users who haven't been active
# Check if a subscriber has the "inactive" tag
bento subscribers search --email user@example.com --tag inactive --json \
| jq -r '.data[].email' \
| while read email; do
bento subscribers tag --email "$email" --add "needs-reengagement" --confirm
done# In your webhook handler
curl -X POST https://your-server.com/webhook -d '{"email": "user@example.com", "event": "purchase"}'
# Handler script
bento events track \
--email "$WEBHOOK_EMAIL" \
--event "$WEBHOOK_EVENT" \
--details "$WEBHOOK_DETAILS"# Clone the repository
git clone https://github.com/bentonow/bento-cli.git
cd bento-cli
# Install dependencies
bun install
# Run CLI locally
bun run dev
# Run tests
bun test
# Lint and format
bun lint
bun formatThis repository includes a Claude Code skill that provides guidance for using the Bento CLI. The skill teaches Claude about all available commands, safety patterns, and best practices.
To install the bento-cli skill for Claude Code:
# Navigate to your Claude Code skills directory
cd ~/.claude/skills
# Clone the skill (or symlink from your local clone)
git clone https://github.com/bentonow/bento-cli.git bento-cli-repo
ln -s bento-cli-repo/skill bento-cli
# Or copy directly
cp -r /path/to/bento-cli/skill ~/.claude/skills/bento-cliAlternatively, if you're working within the bento-cli repository, the skill is automatically available at skill/SKILL.md.
- Command reference: All CLI commands with options and examples
- Safety-first philosophy: Guidance on
--dry-run,--limit, and--confirmflags - Anti-patterns: Common mistakes to avoid with bulk operations
- Workflows: CI/CD integration, scripting patterns, safe import validation
- Documentation: docs.bentonow.com
- Issues: GitHub Issues
- Community: Discord
MIT License - see LICENSE for details.
Built with love by the Bento team.