> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sequenzy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# CLI

> Manage your email marketing from the command line with the Sequenzy CLI

# Sequenzy CLI

The Sequenzy CLI lets you manage subscribers, run campaigns, send emails, manage your team, triage inbox replies, operate outbound webhooks, and view analytics directly from your terminal.

It also lets you inspect, supply, and AI-sync localized template variants.

## Installation

```bash theme={null}
# Using npx (no install needed)
npx @sequenzy/cli --help

# Using Bun (no install needed)
bunx @sequenzy/cli --help

# Or install globally
npm install -g @sequenzy/cli
```

Use the scoped package name for one-off runs: `npx @sequenzy/cli ...` or `bunx @sequenzy/cli ...`. The unscoped `sequenzy` package is the TypeScript API library and does not expose a CLI binary.

### Using the CLI with AI agents

If an AI agent will run the CLI for you, also install the Sequenzy skill. It gives your agent a versioned workflow guide for operating the CLI safely:

```bash theme={null}
npx skills add Sequenzy/skills --skill sequenzy-email-marketing
```

Learn more in the [Skills documentation](/concepts/skills).

## Authentication

### Login

```bash theme={null}
sequenzy login
```

This prints a login URL and approval code in the terminal. Open the link in any browser, approve the session, and keep the terminal open until the CLI stores your API key.

This flow also works in headless or agent-driven environments where the CLI cannot launch a browser for you.

### Check login status

```bash theme={null}
sequenzy whoami
```

`whoami` checks local credential state only. To validate the credential that
would actually reach Sequenzy—including a `SEQUENZY_API_KEY` environment
override—and inspect its non-secret identity and permissions, run:

```bash theme={null}
sequenzy account
sequenzy account --json
```

### Logout

```bash theme={null}
sequenzy logout
```

## Commands

Destructive commands (`campaigns delete`, `ab-tests delete`, `ab-tests delete-variant`, `lists delete`, `segments delete`, `tags delete`, `webhooks delete`, and `team cancel-invitation`) ask for confirmation before running. Pass `--yes` (or `-y`) to skip the prompt. When stdin is not interactive - in scripts, CI, or agent-driven runs - `--yes` is required and the command fails without it.

### Subscribers

```bash theme={null}
# List subscribers
sequenzy subscribers list
sequenzy subscribers list --tag vip
sequenzy subscribers list --segment active-users
sequenzy subscribers list --list "Master List" --json

# Add a subscriber
sequenzy subscribers add user@example.com
sequenzy subscribers add user@example.com --first-name John --last-name Doe --tag premium --attr plan=pro
# Matching sequences are enrolled by default.

# Update a subscriber
sequenzy subscribers update user@example.com --first-name Ana --last-name Silva

# Get subscriber details
sequenzy subscribers get user@example.com

# Remove a subscriber
sequenzy subscribers remove user@example.com
sequenzy subscribers remove user@example.com --hard  # permanent delete

# Trigger a custom event for one subscriber
sequenzy subscribers event user@example.com --name saas.purchase --property plan=pro --property amount=99

# Reconcile tags across many existing subscribers
sequenzy subscribers tags add --tag derived-churn-risk --emails-file ./backlog.csv
sequenzy subscribers tags add --tag vip --subscriber-id sub_123 sub_456
sequenzy subscribers tags remove --tag legacy-plan --emails-file ./backlog.csv

# Import full CRM records from CSV or JSON, then inspect progress
sequenzy subscribers import ./copper-export.csv --duplicate-strategy merge --list list_abc123 --opt-in-mode confirmed
sequenzy subscribers import-status import_abc123
```

`subscribers tags add` and `subscribers tags remove` only touch contacts that
already exist: unknown emails, external IDs, and subscriber IDs are reported,
never created. Tag automations stay off unless you pass `--trigger-automations`
on `add`, which is what historical backfills want. The CLI chunks targets into
500-contact API requests and reports combined totals, so a multi-thousand-row
reconciliation is a single command.

Subscriber imports accept columns such as email, full/first/last name, external
ID, phone, status, and tags. Additional CSV columns become typed custom
attributes. Imports run asynchronously; a `completed` import may still contain
row failures, so check `import-status --json`. Use `--opt-in-mode confirmed`
only when the source CRM contains verified consent.

### Lists

```bash theme={null}
# Create a list
sequenzy lists create "Monday Batch 01" --private

# Add subscribers to a list from an array
sequenzy lists add-subscribers list_123 --email one@example.com two@example.com

# Add subscribers to a list from JSON or CSV
sequenzy lists add-subscribers list_123 --emails-json '["one@example.com","two@example.com"]'
sequenzy lists add-subscribers list_123 --emails-file ./batch-001.csv
sequenzy lists import list_123 --emails-file ./batch-001.csv

# Update a list
sequenzy lists update list_123 --name "Weekly Newsletter" --private
sequenzy lists update list_123 --description "Sent every Monday" --no-private

# Remove subscribers from a list
sequenzy lists remove-subscribers list_123 --email one@example.com two@example.com
sequenzy lists remove-subscribers list_123 --emails-file ./churned.csv

# Delete a list
sequenzy lists delete list_123 --yes
```

Use `--private` to omit a list from individual controls on the hosted subscriber
email preferences/unsubscribe page. Use `--no-private` to make its name and
description subscriber-facing there. List privacy does not override a
subscriber's global unsubscribe.

The CLI splits large files into API-safe batches automatically. CSV files can
use an `email`, `e-mail`, `email address`, or `mail` header; if no email header
exists, the first column is used. Use this command instead of looping over
`subscribers add`; standard API rate limits are 100 requests per minute per API
key and 20 requests per second burst.

`lists remove-subscribers` accepts the same email inputs as `add-subscribers`
(`--email`, `--emails-json`, or `--emails-file`) and only removes list
memberships; the subscribers stay in your audience. Emails that do not match a
subscriber are reported as not found. Deleting a list removes all of its
memberships but keeps the subscribers.

### Send Email

Send transactional emails to single recipients:

```bash theme={null}
# Using a template
sequenzy send user@example.com --template welcome --var name=John

# With nested array data for repeat blocks
sequenzy send user@example.com --template order-confirmation --vars-json '{"event":{"items":[{"title":"Pro plan","description":"Monthly subscription"}]}}'

# Using raw HTML
sequenzy send user@example.com --subject "Hello" --html "<h1>Hi!</h1>"

# From a file
sequenzy send user@example.com --subject "Report" --html-file ./email.html
```

`--template` takes a transactional API slug and maps it to the API's `slug`
field. Direct `--html` and `--html-file` content maps to the API's canonical
`body` field and requires `--subject`. Template and direct-content flags cannot
be combined. If the recipient email matches a stored subscriber, saved first and
last names fill omitted name variables automatically. Explicit `--var` or
`--vars-json` values take precedence.

### Statistics

```bash theme={null}
# Overview stats
sequenzy stats
sequenzy stats --period 30d

# Send API and transactional SMTP stats
sequenzy stats --email-type transactional --period 30d

# One saved transactional email (all time, or add --period 30d)
sequenzy stats --transactional password-reset

# Campaign stats
sequenzy stats --campaign camp_abc123
sequenzy stats --campaign camp_abc123 --json

# Sequence stats
sequenzy stats --sequence seq_xyz789
```

Sequence stats include a Current Enrollments section with live active and
waiting enrollment-run counts grouped by current node. These live counts are
not constrained by `--period`, `--start`, or `--end`; use `--json` for the
complete `enrollmentCounts.byCurrentNode` array.

Use `sequenzy email-sends list --search "Welcome" --status opened` to discover
recent deliveries by subject/title, recipient, status, type, or source. Then use
`sequenzy email-sends get <emailSendId>` to inspect one delivery's open/click
timestamps and complete event timeline.

Add `--all` to retrieve every matching page. Export RFC 4180 CSV to stdout with
`--csv`, or write a complete file with
`sequenzy email-sends list --all --csv email-sends.csv`. List and detail output
include dashboard URLs, and the command prints a concrete follow-up using the
first returned delivery ID.

Transactional template listing supports the dashboard filters and metrics:

```bash theme={null}
sequenzy transactional list --search "password reset" --status active
sequenzy transactional list --sort open-rate --order desc
```

Campaign and saved transactional-email stats include a Clicked Links section with the top
destination URLs, click counts, and each link's share of all link clicks;
saved transactional stats also show complaint, reply, bounce-type, and
human-versus-machine engagement breakdowns. Add `--json` to receive the
structured fields.

### Campaigns

```bash theme={null}
# List campaigns
sequenzy campaigns list
sequenzy campaigns list --status draft

# Get campaign details
sequenzy campaigns get camp_abc123

# Inspect rejected campaigns and their reviewer feedback
sequenzy campaigns list --status rejected

# Update a draft campaign reply-to
sequenzy campaigns update camp_abc123 --reply-to support@example.com

# Create a draft campaign
sequenzy campaigns create "Launch" --prompt "Announce the new dashboard"
sequenzy campaigns create "Launch" --subject "Big News!" --preheader-text "A quick launch preview" --html-file ./launch.html
sequenzy campaigns create "Launch" --subject "Big News!" --blocks-file ./launch-blocks.json

# Create a draft with its audience already saved
sequenzy campaigns create "Launch" --subject "Big News!" --html-file ./launch.html --segment seg_abc123
sequenzy campaigns create "Launch" --subject "Big News!" --html-file ./launch.html --target-lists-json '{"type":"lists","listIds":["list_abc123"]}'

# Update campaign subject or preheader
sequenzy campaigns update camp_abc123 --subject "Updated subject"
sequenzy campaigns update camp_abc123 --preheader-text "Updated preview"

# Retarget or clear a draft's saved audience
sequenzy campaigns update camp_abc123 --segment seg_abc123
sequenzy campaigns update camp_abc123 --clear-target-lists

# Schedule a campaign
sequenzy campaigns schedule camp_abc123 --at "2026-06-01T14:00:00Z"
sequenzy campaigns schedule camp_abc123 --at "2026-06-01T14:00:00Z" --segment seg_abc123

# Schedule a recurring campaign (repeats weekly or monthly)
sequenzy campaigns schedule camp_abc123 --at "2026-06-01T14:00:00Z" --repeat monthly

# Return a scheduled campaign to an editable draft
sequenzy campaigns unschedule camp_abc123

# Send test email
sequenzy campaigns test camp_abc123 --to me@example.com

# Render a campaign to email-ready HTML (read-only, nothing is sent)
sequenzy campaigns render camp_abc123 --out preview.html
sequenzy campaigns render camp_abc123 --subscriber sub_abc123 --out -

# Cancel a scheduled, paused, or sending campaign
sequenzy campaigns cancel camp_abc123

# Pause and resume delivery
sequenzy campaigns pause camp_abc123
sequenzy campaigns resume camp_abc123
sequenzy campaigns resume camp_abc123 --spread-over-hours 6

# Duplicate a campaign as a new draft
sequenzy campaigns duplicate camp_abc123
sequenzy campaigns duplicate camp_abc123 --mode ab_test
sequenzy campaigns duplicate camp_abc123 --mode variant --variant-id var_b

# Resend a sent campaign to people who didn't open it
sequenzy campaigns resend-to-non-openers camp_abc123

# Delete a campaign
sequenzy campaigns delete camp_abc123 --yes
```

Rejected campaign list and detail output include the reviewer feedback when it
was provided. The same value is available as `rejectionComment` with `--json`.

<Note>
  Scheduling requires a future ISO timestamp and a verified sending domain.
</Note>

With `--repeat weekly` or `--repeat monthly`, the campaign becomes a recurring template: each run is duplicated and sent automatically at the cadence, re-evaluating list or segment membership every time. Use `campaigns unschedule` to stop the series and return the template to an editable draft, or re-schedule without `--repeat` to keep a one-shot send.

`campaigns create` accepts `--prompt` to generate the campaign email, including subject, preview text, and blocks. Use `--preview-text` or `--preheader-text` to set the inbox preview text yourself; with `--prompt`, that explicit value overrides the generated preview. `campaigns create` and `campaigns update` also accept raw HTML or Sequenzy block JSON via `--blocks-json` or `--blocks-file`. Use block input when you need editor-compatible content, conditional blocks, repeat blocks, or [Poll and NPS survey blocks](/concepts/polls). Campaign stats render answer distributions and NPS scores automatically; add `--json` to receive the complete top-level `polls` array.

`campaigns create` can also save the audience on the draft: pass `--segment <id>` to target one saved segment, or `--target-lists-json`/`--target-lists-file` for any targeting shape, such as `{"type":"all"}`, `{"type":"lists","listIds":["list_abc123"]}`, `{"type":"segment","segmentId":"seg_abc123"}`, or `{"type":"rules","include":[{"type":"lists","listIds":["list_abc123"]}],"exclude":[{"type":"segments","segmentIds":["seg_abc123"]}]}`. `--segment` and the target list flags are mutually exclusive. `campaigns update` accepts the same three flags to retarget a draft, plus `--clear-target-lists` to remove saved targeting again, and `campaigns schedule` accepts them to choose the audience as you schedule; scheduling without them reuses the saved targeting, or falls back to all active subscribers when none was saved. `campaigns create`, `campaigns update`, and `campaigns get` all print the saved audience on the `Audience` line and return it as `targetLists` with `--json`.

`campaigns unschedule` removes a scheduled send and any recurrence, then returns the campaign to `draft` so it can be edited and scheduled again. `campaigns cancel` stops scheduled, paused, waiting-approval, and actively sending campaigns immediately. It skips the confirmation prompt on purpose so you can stop a bad send fast, and it cannot be undone. Only sending campaigns can be paused, and only paused campaigns can be resumed; pass `--spread-over-hours` (1-72) to spread the remaining delivery over a longer window. Sending, scheduled, and paused campaigns must be cancelled before they can be deleted.

`campaigns duplicate` defaults to `--mode campaign`, which copies the campaign and its email. Use `--mode ab_test` to also copy the campaign's A/B test with all variants, or `--mode variant` with `--variant-id` to copy a single variant's content as the new campaign email.

`campaigns render`, `sequences render`, and `templates render` return the exact email-safe HTML that would be sent, so you can embed a true visual preview in your own dashboard or email builder. The render applies your email theme and font, company branding and footer, localization, block visibility conditions, product recommendation blocks, and merge tags.

Personalize with `--subscriber <id>` for a stored subscriber, or `--email you@example.com` plus optional `--first-name`/`--last-name` for an ad-hoc contact. Add merge variables with repeatable `--var key=value` flags, or `--vars-json` for values that need to be numbers, booleans, or nested objects. Only a stored subscriber can resolve subscriber-scoped content such as block conditions, product recommendations, and a working unsubscribe link. With no contact at all, the email renders for a sample contact and merge tags resolve to empty values.

Rendering itself needs only a read scope on the campaign, sequence, or template. `--subscriber` puts that subscriber's details into the HTML, so it additionally requires the `subscribers:read` scope.

Use `--out <file>` to write the HTML to disk, or `--out -` to stream the raw document to stdout for piping. Without `--out`, the command prints a summary (subject, preview text, locale, personalization, tracking, byte size). `--json` returns the full payload including `html`.

Links stay clean by default. Pass `--tracking` to apply your company's auto-UTM decoration as a real send would. Per-send click redirects and the open pixel are added at send time against a real email send record, so they never appear in a render.

`campaigns resend-to-non-openers` creates a draft that targets the same audience as a sent campaign plus a "didn't open this campaign" rule, so only people who haven't opened it yet receive the resend. It's available 6 hours after the campaign finishes sending (to give opens time to register), and the response includes an estimate of how many subscribers haven't opened the original. The draft is not sent automatically - review it, then schedule or send it.

### Landing Pages

```bash theme={null}
# List landing pages
sequenzy landing-pages list
sequenzy landing-pages list --json

# Create a draft from a built-in template
sequenzy landing-pages create "Product Waitlist" --template waitlist
sequenzy landing-pages create "Launch Page" --template launch --slug launch

# Get or edit a page
sequenzy landing-pages get lp_abc123
sequenzy landing-pages update lp_abc123 --name "Updated Waitlist" --slug waitlist

# Publish or unpublish
sequenzy landing-pages publish lp_abc123
sequenzy landing-pages unpublish lp_abc123

# Connect or verify a custom landing page domain
sequenzy landing-pages connect-domain pages.example.com
sequenzy landing-pages update-domain-settings --verify

# Delete a page
sequenzy landing-pages delete lp_abc123
```

`landing-pages create`, `update`, `publish`, and `unpublish` can accept full builder JSON with `--content-json` or `--content-file`. Built-in template keys include `from-scratch`, `waitlist`, `lead-magnet`, `launch`, `demo-request`, `webinar`, `newsletter`, `product-hunt`, `pricing-offer`, `agency-lead-gen`, and `feature-announcement`.

Custom landing page domains need a CNAME record pointing to `pages.sequenzydns.com`; run `update-domain-settings --verify` after DNS propagates.

### A/B Tests

```bash theme={null}
# List A/B tests, optionally scoped to a sequence
sequenzy ab-tests list
sequenzy ab-tests list --sequence seq_abc123

# Get variants and localization status
sequenzy ab-tests get ab_abc123

# Update campaign or sequence settings
sequenzy ab-tests update ab_abc123 --test-percentage 30 --duration-minutes 120 --winner-criteria open_rate
sequenzy ab-tests update ab_abc123 --test-type content --winner-criteria open_rate --winner-threshold 150 --confirm-live-change

# Get aggregate and per-variant stats
sequenzy ab-tests stats ab_abc123 --period 30d

# Update a draft variant
sequenzy ab-tests update-variant ab_abc123 var_b --subject "Better subject"
sequenzy ab-tests update-variant ab_abc123 var_b --html-file ./variant-b.html
sequenzy ab-tests update-variant ab_abc123 var_b --blocks-file ./variant-b-blocks.json

# Create an A/B test for a draft campaign
sequenzy ab-tests create camp_abc123
sequenzy ab-tests create camp_abc123 --test-percentage 30 --duration-minutes 120 --winner-criteria click_rate
sequenzy ab-tests create camp_abc123 --variants-json '[{"subject":"Alternative subject"}]'
sequenzy ab-tests create --automation-node node_abc123 --test-type content --winner-threshold 100 --variants-json '[{"subject":"Variant B"}]'

# Add a variant to a draft campaign or sequence A/B test
sequenzy ab-tests add-variant ab_abc123 --subject "Alternative subject"
sequenzy ab-tests add-variant ab_abc123 --subject "Alternative subject" --blocks-file ./variant.json

# Remove a variant from a draft campaign or sequence A/B test
sequenzy ab-tests delete-variant ab_abc123 var_b --yes

# Delete a campaign A/B test
sequenzy ab-tests delete ab_abc123 --yes
```

Use `get` first to inspect the effective `settings` object and discover variant
IDs. Campaign settings are editable only in draft. Sequence settings can be
updated later with `--confirm-live-change` when the test is active or has
recorded activity.

`ab-tests create` accepts either a campaign ID or `--automation-node`. Campaigns must be in draft or rejected status; an automation node must be an email step and is converted to a dashboard A/B-test node. Add `--confirm-live-change` when converting a node in an active sequence. Control variant A is created automatically from the current email; sequence variants receive independent email templates. Extra variants come from `--variants-json` or `--variants-file` (each entry takes `subject`, optional `previewText`, and optional `blocks`). Campaign tests use audience percentage and duration; sequence tests use `--test-type`, `--winner-threshold`, and `--winner-criteria`. An explicit sequence winner criterion overrides the test-type default.

Variants can only be added or removed while the test is in draft status, variant A is the control and cannot be deleted, and running tests cannot be deleted. When a sequence test's parent sequence is active, `add-variant` and `delete-variant` also require `--confirm-live-change` because they immediately change the live rotation.

### Sequences

```bash theme={null}
# List sequences
sequenzy sequences list

# Get sequence details
sequenzy sequences get seq_abc123

# Send one saved email step to internal reviewers
sequenzy sequences test seq_abc123 --node node_email_2 \
  --to reviewer-one@example.com reviewer-two@example.com

# Render one saved email step to email-ready HTML (read-only)
sequenzy sequences render seq_abc123 --node node_email_2 --out preview.html

# Create the same blank trigger-to-completion draft as the dashboard
sequenzy sequences create cancellation-feedback

# Filter the dashboard sequence list
sequenzy sequences list --status active --search cancellation --label Lifecycle --limit 25

# Create an AI-generated sequence with custom spacing and a plain, text-first style
sequenzy sequences create onboarding --trigger contact_added \
  --goal "Guide new users to activation" \
  --email-count 4 \
  --duration-days 10 \
  --email-style plain

# Create a purchase sequence scoped to one product (event property filters)
sequenzy sequences create deliver-ebook --trigger event_received \
  --event-name ecommerce.order_placed \
  --property-filters-json '[{"path":"lineItems[].providerProductId","operator":"equals","value":"prod_123"}]' \
  --steps-file ./steps.json

# Create a dynamic discount sequence (code generated per subscriber at runtime)
sequenzy sequences create win-back --trigger tag_added \
  --tag-name cancelled \
  --steps-json '[{"type":"create_discount","discount":{"provider":"shopify","discountType":"percent","percentOff":20,"appliesToAllPlans":true,"maxRedemptions":1,"codePrefix":"WINBACK"}},{"subject":"Come back with {{discount.code}}","html":"<p>Use {{discount.code}} for 20% off.</p>"}]'

# Create a sequence that copies purchase-event data onto the subscriber
sequenzy sequences create purchase-profile --trigger event_received \
  --event-name saas.purchase \
  --steps-json '[{"type":"update_subscriber","nodeType":"action_update_attributes","config":{"customAttributeUpdates":[{"name":"plan","value":"{{event.plan}}","valueType":"text"},{"name":"mrr","value":"{{event.amount}}","valueType":"number"}]}}]'

# Update sequence metadata and existing email steps
sequenzy sequences update seq_abc123 --name "Updated onboarding"
sequenzy sequences update seq_abc123 --steps-file ./sequence-updates.json
sequenzy sequences update seq_abc123 \
  --emails-json '[{"nodeId":"node_founder_note","emailPreset":"minimal"}]'
sequenzy sequences update seq_abc123 \
  --subscriber-update-steps-json '[{"nodeId":"node_update","config":{"customAttributeUpdates":[{"name":"active","value":"{{event.active}}","valueType":"boolean"}]}}]'
sequenzy sequences update seq_abc123 \
  --stop-condition-json '{"type":"has_tag","value":"customer"}'

# Atomically replace the trigger
sequenzy sequences update seq_abc123 --trigger inbound_webhook \
  --event-name summit.cancellation_feedback \
  --custom-integration-file ./heysummit-webhook.json \
  --confirm-live-change

# BCC team inboxes on every email the sequence sends
sequenzy sequences update seq_abc123 --bcc-emails "cs-team@yourcompany.com, founder@yourcompany.com"
sequenzy sequences update seq_abc123 --clear-bcc-emails

# Attach a file to an email step (fetched from the URL at send time; [] removes)
sequenzy sequences update seq_abc123 \
  --emails-json '[{"nodeId":"node_email","attachments":[{"filename":"guide.pdf","path":"https://example.com/files/guide.pdf"}]}]'

# Resolve a unique attachment from the event that enrolled the subscriber
sequenzy sequences update seq_abc123 \
  --emails-json '[{"nodeId":"node_file","attachments":[{"filename":"file-{{event.order_id}}.pdf","path":"{{event.file_url}}"}]}]'

# Insert new linear steps after an existing node
sequenzy sequences update seq_abc123 \
  --insert-steps-json '{"afterNodeId":"node_email","steps":[{"delay":{"days":1},"subject":"Migration check-in","html":"<p>Need help migrating?</p>"}]}'

# Insert an if/else branch after an existing node
sequenzy sequences update seq_abc123 \
  --branch-file ./branch.json

# Wait for a reply, then continue or time out after five days
sequenzy sequences update seq_abc123 \
  --insert-steps-json '{"afterNodeId":"node_email_1","steps":[{"nodeType":"logic_wait_for_event","config":{"label":"Wait for reply","eventName":"email.replied","timeoutDays":5,"timeoutAction":"continue"}}]}'

# Route replies to completion and everyone else to the existing follow-up
sequenzy sequences update seq_abc123 \
  --branch-json '{"afterNodeId":"node_email_1","branches":[{"id":"replied","conditionType":"event_received","eventName":"email.replied","activityScope":"this_sequence","targetNodeId":"node_complete"}],"elseTargetNodeId":"node_email_2"}'

# Enable/disable
sequenzy sequences enable seq_abc123
sequenzy sequences disable seq_abc123
sequenzy sequences pause-enrollments seq_abc123
sequenzy sequences resume-enrollments seq_abc123
```

`sequences list`, `sequences get`, and `sequences update` print the stored
status together with the resolved run state, because `active` alone is
ambiguous: a sequence with paused enrollment still emails its current recipients
but takes nobody new. The four run states you will see are `active (running)`,
`active (new enrollments paused, existing recipients continue)`,
`paused (recipients frozen, nobody new enters)`, and `draft`/`archived`
(`not running`). `enable`, `disable`, `pause-enrollments`, and
`resume-enrollments` print the resulting state on a `State:` line. With `--json`
the same information is available as `effectiveStatus`,
`acceptsNewEnrollments`, `processesExistingEnrollments`, and
`effectiveStatusSummary`.

```bash theme={null}

# Sequence-list actions
sequenzy sequences duplicate seq_abc123 --name "Welcome Series v2"
sequenzy sequences archive seq_abc123
sequenzy sequences unarchive seq_abc123

# Persisted dashboard conversion goals
sequenzy sequences goals create seq_abc123 Reactivated \
  --trigger-type event --trigger-event-name account.reactivated \
  --attribution-window-hours 168
sequenzy sequences goals create seq_abc123 Upgraded \
  --trigger-event-name plan.upgraded --inactive
sequenzy sequences goals list seq_abc123

# Configure and rotate an inbound-webhook sequence endpoint
sequenzy sequences webhook configure seq_abc123 \
  --field-mapping-file ./field-mapping.json \
  --sample-payload-file ./sample-payload.json
sequenzy sequences webhook get seq_abc123
sequenzy sequences webhook rotate seq_abc123

# Manually enroll subscribers in a sequence
sequenzy sequences enroll seq_abc123 --email one@example.com two@example.com
sequenzy sequences enroll seq_abc123 --subscriber-ids sub_123 sub_456 --target-node-id node_123
sequenzy sequences enroll seq_abc123 --emails-file ./vips.csv
sequenzy sequences enroll seq_abc123 --emails-json '["one@example.com"]' --target-node-id node_123

# Dry-run cancellation for enrollments with matching entry event values
sequenzy sequences cancel-enrollments seq_abc123 --field-path order.id --field-values ord_123,ord_456

# Preview, then drain, every active or waiting enrollment in a sequence
sequenzy sequences cancel-enrollments seq_abc123 --all
sequenzy sequences cancel-enrollments seq_abc123 --all --apply --reason "Lifecycle cutover"

# Cancel a specific batch of enrollments
sequenzy sequences cancel-enrollments seq_abc123 --subscriber-ids sub_123 sub_456 --apply

# Apply cancellation for one subscriber enrollment
sequenzy sequences cancel-enrollments seq_abc123 --subscriber-id sub_abc123 --apply
```

`sequences cancel-enrollments` takes exactly one target: `--all`, `--subscriber-id`, `--subscriber-ids`, or `--field-values`. Use `--all` to fully stop a live sequence, including segment-triggered contacts that share no entry field value - pausing enrollment only blocks new entrants, it does not remove contacts already mid-flight. Without `--apply` the command reports matches and changes nothing. With `--apply` it keeps calling the API until every match is drained, printing running progress, because each request cancels at most 1,000 enrollments.

`sequences enroll` accepts subscriber IDs with `--subscriber-ids`, the same email inputs as `lists add-subscribers` (`--email`, `--emails-json`, or `--emails-file`), or both. Each API request is capped at 500 total targets across emails and subscriber IDs. Only active subscribers are enrolled: unknown emails are reported as not found, and inactive, unavailable, or already enrolled subscribers are skipped. Enrollment starts at the first step after the trigger unless you pass `--target-node-id`, and the sequence must be accepting entrants (enabled and not paused for enrollment).

`sequences test` sends only the selected saved email step. Use `sequences get`
to find its `nodeId`, then pass one to ten internal addresses after `--to`.
The JSON response includes one durable `emailSendId` per recipient for delivery
inspection.

For `sequences update`, use `sequences get` first to find `nodeId` and `emailId` values. Metadata flags include `--description`, repeatable `--label` (or `--clear-labels` to remove every label), `--user-cancellable`/`--not-user-cancellable`, and sequence or per-step sender/reply identity. Trigger replacement uses `--trigger` plus its typed trigger flags and is atomic; on an active sequence it requires `--confirm-live-change`. `--steps-file` and `--emails-file` edit existing email steps by `nodeId`, `emailId`, or array order; items can set transactional mode and CC/BCC recipients. An email item can set `emailPreset` to `minimal` or `branded` for native Sequenzy blocks, including emails that contain supported custom HTML blocks, using the same format transformation as the dashboard. An email stored entirely as one standalone raw HTML block does not support `emailPreset`, and `emailPreset` cannot be combined with `html` or `htmlContent`. `--insert-steps-file` inserts new linear steps with an optional `afterNodeId`, including delay, tag/list, outbound webhook, condition, discount, subscriber-update, SMS, and `logic_wait_for_event` nodes with typed config. Tag, list, wait, condition, and webhook steps put their node fields in `config`: `action_add_tag`/`action_remove_tag` take `tagName` (or `tagId` when you have the real tag ID), `action_add_to_list`/`action_remove_from_list` take `listId`, `logic_wait_for_event` takes `eventName` with `timeoutDays`/`timeoutAction`, `logic_condition` takes `conditionType` plus its resource field, and `action_webhook` takes `url` with optional `method`/`headers`. SMS, discount, and delay steps keep using their step-level fields. Use `--branch-file` for a new if/else node; each path can provide new `steps`, an existing `targetNodeId`, or both, while `elseTargetNodeId` wires the fallback. Do not combine branch insertion, linear insertion, or a graph edit in the same command.

When the target sequence is active, structural edits such as `--insert-steps-*`, `--branch-*`, or `--graph-edit-*` require `--confirm-structural-change` after you confirm the live-flow impact. Content-only `--steps-*` and `--emails-*` edits do not require that flag.

Deleting a step with `--graph-edit-*` immediately moves parked recipients to
its unique surviving successor, or completes them when no successor remains.
The command reports both affected counts in human output and under
`sequence.migratedRecipientCount` / `sequence.completedRecipientCount` with
`--json`.

### Templates

```bash theme={null}
# List templates
sequenzy templates list
sequenzy templates list --company comp_abc123
sequenzy templates list --json

# Get template
sequenzy templates get tmpl_abc123
sequenzy templates get tmpl_abc123 --company comp_abc123 --json

# Render a template to email-ready HTML
sequenzy templates render tmpl_abc123 --out preview.html

# Create or update with HTML
sequenzy templates create welcome --subject "Welcome" --html-file ./welcome.html
sequenzy templates update tmpl_abc123 --subject "Updated" --html-file ./welcome-v2.html
sequenzy templates update tmpl_abc123 --preview-text "Everything you need to get started"

# Create or update with Sequenzy blocks
sequenzy templates create welcome --subject "Welcome" --blocks-file ./welcome-blocks.json
sequenzy templates update tmpl_abc123 --blocks-file ./welcome-v2-blocks.json

# Store a caller-supplied localized variant
sequenzy templates localizations set tmpl_abc123 es --subject "Bienvenido" --html-file ./welcome-es.html

# Queue AI translation for selected locales, or omit --locale to sync all
sequenzy templates localizations sync tmpl_abc123 --locale es fr
sequenzy templates localizations sync tmpl_abc123
```

`templates list` includes localization sync status by locale. `templates get`
includes the full localized variants and the effective company localization
config. `templates localizations set` requires one enabled non-primary locale,
a subject, and exactly one HTML or blocks source. `templates localizations
sync` works even when automatic on-save localization is disabled.

When you pass blocks JSON, each block can include a `conditions` array for recipient-specific content. Use `field: "variable"` to branch on a value passed in the send's `variables` or an automation `event` payload:

```json theme={null}
[
  {
    "id": "intro",
    "type": "text",
    "content": "<p>Hello there</p>",
    "variant": "paragraph"
  },
  {
    "id": "pro-offer",
    "type": "text",
    "content": "<p>Your Pro upgrade is ready.</p>",
    "variant": "paragraph",
    "conditions": [
      { "id": "c1", "field": "variable", "operator": "is", "value": "plan:pro" }
    ]
  }
]
```

In a `variable` condition, the text before the colon in `value` is a merge-tag path (without `{{ }}`), including nested paths like `order.total` or `event.plan`, and the text after it is the comparison value. `field` can also be `attribute` (same `name:value` form) or `email` / `firstName` / `lastName`. Operators: `is`, `is_not`, `contains`, `not_contains`, `gt`, `gte`, `lt`, `lte`, `is_empty`, `is_not_empty`. For if/else, add a `conditional-group` block with `ifBranch.children` and `elseBranch.children`.

### Companies

```bash theme={null}
sequenzy companies list
sequenzy companies get comp_abc123
sequenzy companies get comp_abc123 --json
sequenzy companies update comp_abc123 --primary-color "#0ea5e9"
sequenzy companies update comp_abc123 --company-context-file ./product.md --tone-voice "clear, direct, warm"
sequenzy companies update comp_abc123 --email-theme-json '{"colors":{"mutedText":"#4b5563"},"typography":{"baseFontSize":16}}'

# Sending identity defaults
sequenzy companies update comp_abc123 --from-email hello@mail.example.com --from-name "Example"
sequenzy companies update comp_abc123 --from-name "Example"
sequenzy companies update comp_abc123 --sender-profile-id sender_abc123 --from-name "Example News"
```

`companies get` includes product info, brand colors, AI writing context, the account-wide From and Reply-To defaults, and the effective email localization settings for that company. `companies update` edits the product info that AI uses for generated emails, including `primaryColor`, `companyContext`, `toneVoice`, value props, testimonials, and related brand fields. `--email-theme-json` / `--email-theme-file` edit the company's default email theme (`presetId`, `colors`, `typography`, `layout`); updates are partial, and passing `null` resets the theme to the platform default.

`--from-email` / `--reply-to` set the account-wide sending defaults, creating the profile when one does not exist yet (a From address needs a verified sending domain). `--from-name` and `--reply-to-name` on their own rename the display name of the current default profile without changing its address, so campaigns and sequences pinned to that profile pick up the new name.

One address can carry several display names, so pass `--sender-profile-id` (or `--reply-profile-id`) to select exactly which profile becomes the default and which one `--from-name` renames. Run `sequenzy sender-profiles list` to see the IDs. Renaming with `--from-email` on an address that has several display names is rejected, since there is no way to tell which identity you meant.

### Segments

```bash theme={null}
# List saved segments
sequenzy segments list

# Preview how many subscribers are in a segment
sequenzy segments count seg_abc123

# Create a Stripe-product segment
sequenzy segments create --name "Bought Pro" --stripe-product prod_pro

# Create a threshold-based Stripe-product segment
sequenzy segments create --name "3+ Pro Payments" \
  --stripe-product prod_pro \
  --purchase-operator at-least \
  --payments 3

# Create a purchased-product segment (Shopify, WooCommerce, or Commerce API orders)
sequenzy segments create --name "Starter Kit Buyers" --commerce-product api:prod-starter-kit

# Create a repeat-buyer segment by order count
sequenzy segments create --name "Repeat Kit Buyers" \
  --commerce-product api:prod-starter-kit \
  --purchase-operator at-least \
  --orders 2

# Create a segment that matches any filter instead of all filters
sequenzy segments create --name "VIP Or Gmail" \
  --match any \
  --filter-json '[{"field":"tag","operator":"contains","value":"vip"},{"field":"email","operator":"contains","value":"@gmail.com"}]'

# Create a segment from raw filter JSON
sequenzy segments create --name "Custom Filter" \
  --filter-json '[{"field":"stripeProduct","operator":"is","value":"prod_pro"}]'

# Combine campaign-specific engagement filters (bounced A but not B)
sequenzy segments create --name "Bounced A Not B" \
  --filter-json '[{"field":"emailBounced","operator":"is","value":"campaign:cmp_abc"},{"field":"emailBounced","operator":"is_not","value":"campaign:cmp_xyz"}]'

# Create a nested segment with an event filter
sequenzy segments create --name "Active Non Buyers" \
  --filter-json '{"root":{"kind":"group","joinOperator":"and","children":[{"kind":"filter","field":"attribute","operator":"lte","value":"last_login_days_ago:90"},{"kind":"group","joinOperator":"or","children":[{"kind":"filter","field":"event","operator":"is_not","value":"saas.purchase:30d"},{"kind":"filter","field":"segment","operator":"is_not","value":"seg_paying_customers"}]}]}}'

# Update a segment's name, filters, or join operator
sequenzy segments update seg_abc123 --name "Churn Risk"
sequenzy segments update seg_abc123 --filters-json '[{"field":"tag","operator":"contains","value":"vip"}]'
sequenzy segments update seg_abc123 --join-operator or

# Delete a segment
sequenzy segments delete seg_abc123 --yes
```

For Stripe product segments, use the Stripe product ID, not the product name. The CLI maps:

* `bought` -> `field: "stripeProduct", operator: "is", value: "prod_xxx"`
* `didnt-buy` -> `field: "stripeProduct", operator: "is_not", value: "prod_xxx"`
* `at-least` -> `field: "stripeProduct", operator: "at_least", value: "prod_xxx:3"`
* `less-than` -> `field: "stripeProduct", operator: "less_than_count", value: "prod_xxx:3"`

For purchased-product segments (`--commerce-product`), use `provider:productId` where the provider is `shopify`, `woocommerce`, or `api` and the product ID is the one your orders carry in their line items. Product IDs are provider-scoped (Shopify product 42 and WooCommerce product 42 are different products); a bare ID without a provider prefix matches the ID on any provider. The CLI maps:

* `bought` -> `field: "commerceProduct", operator: "is", value: "shopify:42"`
* `didnt-buy` -> `field: "commerceProduct", operator: "is_not", value: "shopify:42"`
* `at-least` -> `field: "commerceProduct", operator: "at_least", value: "shopify:42:2"` (use `--orders`)
* `less-than` -> `field: "commerceProduct", operator: "less_than_count", value: "shopify:42:2"` (use `--orders`)

For purchased-collection segments (`--commerce-collection`), pass a collection ID or handle (for example `skincare`). The filter matches anyone whose orders contain any product currently in that collection, so it is the practical way to segment a large catalog without listing products one by one. You can prefix a provider (`shopify:skincare`) to scope the lookup. The CLI maps:

* `bought` -> `field: "commerceCollection", operator: "is", value: "skincare"`
* `didnt-buy` -> `field: "commerceCollection", operator: "is_not", value: "skincare"`
* `at-least` -> `field: "commerceCollection", operator: "at_least", value: "skincare:2"` (use `--orders`)
* `less-than` -> `field: "commerceCollection", operator: "less_than_count", value: "skincare:2"` (use `--orders`)

Collection membership comes from your synced catalog as it stands right now, not as it stood when the order was placed - moving a product out of a collection removes its past buyers from the segment. Shopify collections and WooCommerce categories are synced automatically; products pushed through the Commerce API do not carry collections yet, so this filter does not match them.

For trial timing, pass `--filter-json` directly:

* `{"field":"stripeTrialProduct","operator":"is","value":"prod_xxx:is_canceled"}`
* `{"field":"stripeTrialProduct","operator":"gte","value":"prod_xxx:start_at:7 days ago"}`
* `{"field":"stripeTrialProduct","operator":"is","value":"prod_xxx:end_at:2026-05-26"}`

Engagement filters (`emailSent`, `emailOpened`, `emailClicked`, `emailBounced`, `emailComplained`) accept a rolling time window (`7d`, `30d`, `90d`, `180d`, `all`), a specific sent campaign (`campaign:<campaign_id>`), or - with the `at_least` / `less_than_count` operators - a count with a time window (`count:timeRange`, like `10:30d` or `10:all`). For example, `{"field":"emailClicked","operator":"at_least","value":"10:all"}` matches subscribers who clicked 10 or more times ever. Combine two campaign-specific filters with the default `--match all` to express rules like "bounced campaign A but not campaign B". Look up campaign IDs with `sequenzy campaigns list`.

`--filter-json` accepts the same filter shape used by the API and MCP. Pass a JSON array for legacy flat filters, or an object with a nested `root` group for v2 nested logic. If you omit filter or group `id` values, the CLI fills them in automatically.

Event filters use `field: "event"` with values like `saas.purchase:30d`, `saas.purchase:all`, or `saas.purchase:5:30d` for count-based operators. Segment filters use `field: "segment"` with the saved segment ID as the value.

By default, `segments create` matches **all** filters with `AND` logic. Pass `--match any` to save the segment with `OR` logic instead.

`segments count` returns the number of **active subscribers** who currently match the segment. In the dashboard you may also see a broader matched-contact count, but campaigns only send to active subscribers.

`segments update` accepts the same filter shapes as `segments create` via `--filters-json` or `--filters-file` (a flat array or an object with a nested `root` group). Provided filters replace the segment's existing rules, and missing filter IDs are filled in automatically. `lists import` is an alias for `lists add-subscribers`.

### Audience Syncs

Push segments to Meta custom audiences for Facebook and Instagram retargeting. Requires the Meta Ads integration to be connected in the dashboard (Settings → Integrations).

```bash theme={null}
# List audience syncs with schedule and status
sequenzy audience-syncs list

# List the connected Meta ad accounts
sequenzy audience-syncs ad-accounts

# Create a sync from a ready-made segment template
sequenzy audience-syncs create --template recent-buyers \
  --ad-account act_1234567890 \
  --name "Sequenzy - Recent buyers"

# Create a sync from an existing segment, refreshed hourly
sequenzy audience-syncs create --segment seg_abc123 \
  --ad-account act_1234567890 \
  --name "Sequenzy - VIPs" \
  --frequency hourly

# Pause, resume, or change the schedule
sequenzy audience-syncs update sync_abc123 --active false
sequenzy audience-syncs update sync_abc123 --frequency weekly

# Trigger an immediate run
sequenzy audience-syncs sync sync_abc123

# Remove a sync (the Meta audience itself is kept)
sequenzy audience-syncs delete sync_abc123
```

`--template` accepts ready-made segment IDs like `zero-ltv`, `no-purchase-1y`, `recent-buyers`, `high-spenders-ecom`, `non-buyers`, and `engaged`; the segment is created automatically on first use. Audiences are add-only: subscribers who leave the segment stay in the Meta audience.

### Tags

```bash theme={null}
# List tag definitions (bare `sequenzy tags` also lists)
sequenzy tags list

# Create a tag
sequenzy tags create vip
sequenzy tags create "VIP Customer" --color purple

# Update a tag's color
sequenzy tags update tag_abc123 --color red

# Delete a tag
sequenzy tags delete tag_abc123 --yes
```

Tag names are normalized to lowercase with hyphens, so `VIP Customer` becomes `vip-customer`. Colors include values like `gray` (the default), `red`, `green`, `blue`, and `purple`. System tags cannot be updated or deleted, and tags used by sequences cannot be deleted until those sequences stop referencing them. Deleting a tag removes it from every subscriber.

### Shopify

```bash theme={null}
# Show the store's browse-abandonment, cart-abandonment, and price-drop automation settings
sequenzy shopify settings get
sequenzy shopify settings get --json

# Turn features on or off
sequenzy shopify settings update --price-drop off
sequenzy shopify settings update --browse-abandonment on

# Fine-tune behavior (partial update - omitted flags keep their values)
sequenzy shopify settings update --browse-delay-hours 4 --browse-cooldown-hours 48
sequenzy shopify settings update --cart-abandonment on --cart-delay-hours 2
sequenzy shopify settings update --price-drop-min-percent 10 --price-drop-lookback-days 14

# Reset a section to the platform defaults
sequenzy shopify settings update --reset-price-drop
```

### Sync Rules

```bash theme={null}
# Show the effective sync rules (new workspaces start empty)
sequenzy sync-rules get
sequenzy sync-rules get --json

# Replace the full rule set from a file
sequenzy sync-rules update --rules-file ./sync-rules.json

# Replace it inline
sequenzy sync-rules update --rules-json '[{"triggerEvent":"ecommerce.order_placed","actions":{"addTags":["vinyl-collector"],"removeTags":[]},"conditions":{"purchasedProduct":{"tags":["Vinyl"]}}}]'

# Opt into the inherited SaaS/ecommerce platform preset
sequenzy sync-rules update --reset
```

Sync rules map events to automatic tag changes. `update` replaces the full rule set - fetch with `get --json`, edit, then send it back. Send an empty array to disable rules. `--reset` is an explicit opt-in to the legacy SaaS/ecommerce platform preset, not a neutral reset. Rules can carry conditions: required or excluded subscriber tags, and a product match for commerce events that tags buyers by product tag, collection, product type, or vendor. See [Sync Rules](/concepts/sync-rules) for the full model.

### Team

```bash theme={null}
# List team members and pending invitations
sequenzy team list

# Invite a team member
sequenzy team invite teammate@example.com --role admin
sequenzy team invite editor@example.com --role restricted
sequenzy team invite finance@example.com --role viewer --billing-access

# Cancel a pending invitation
sequenzy team cancel-invitation inv_abc123 --yes
```

Existing Sequenzy users are added to the team immediately; everyone else receives an email invitation. Inviting and cancelling invitations requires owner or admin access, `restricted` members can open direct campaign links only, and `--billing-access` can only be granted by the company owner. Use `team list` to find invitation IDs.

### Inbox

```bash theme={null}
# List conversations
sequenzy inbox list
sequenzy inbox list --status open --unread
sequenzy inbox list --search "refund" --limit 50

# Get a conversation with its full message history
sequenzy inbox get conv_abc123

# Reply to a conversation
sequenzy inbox reply conv_abc123 --text "Thanks for reaching out!"
sequenzy inbox reply conv_abc123 --html-file ./reply.html --subject "Re: your question"

# Add an internal note instead of emailing the subscriber
sequenzy inbox reply conv_abc123 --text "Customer asked for a refund" --note

# Triage
sequenzy inbox close conv_abc123
sequenzy inbox reopen conv_abc123
sequenzy inbox mark-read conv_abc123
```

The inbox collects subscriber replies to campaigns, sequences, and transactional email. Provide a reply body with `--text` and/or `--html-file`. Outbound replies are emailed to the subscriber and reopen closed conversations; messages sent with `--note` are internal-only and never reach the subscriber.

### Webhooks

```bash theme={null}
# List webhook endpoints
sequenzy webhooks list

# Create a webhook endpoint
sequenzy webhooks create --name CI --url https://example.com/hook --event email.bounced
sequenzy webhooks create --name "All events" --url https://example.com/hook

# Update a webhook endpoint
sequenzy webhooks update wh_abc123 --url https://example.com/new-hook
sequenzy webhooks update wh_abc123 --event email.bounced email.complained
sequenzy webhooks update wh_abc123 --disable

# Delete a webhook endpoint
sequenzy webhooks delete wh_abc123 --yes

# Test and inspect deliveries
sequenzy webhooks test wh_abc123
sequenzy webhooks deliveries wh_abc123 --limit 50
sequenzy webhooks replay wh_abc123 del_456
```

Webhook payloads are signed. `webhooks create` prints the signing secret **once** at creation time; it cannot be retrieved later, so store it immediately to verify webhook signatures. Valid `--event` values cover `email.*`, `subscriber.*`, and `sequence.*` event types; omit `--event` to subscribe to the default event set.

On `webhooks update`, `--event` replaces the existing event list, and changing the URL or re-enabling with `--enable` resets the failure circuit breaker. Use `webhooks deliveries` to find delivery IDs before running `webhooks replay`.

### Products

```bash theme={null}
# List synced products (Stripe, Shopify, WooCommerce, api)
sequenzy products list --provider stripe

# Queue a Stripe product catalog sync
sequenzy products sync

# Create or update a product (Commerce API, keyed by your productId)
# and attach its deliverable file in the same command
sequenzy products upsert my-ebook --title "The Ebook" --price-cents 1900 --currency USD --file ./ebook.pdf

# Bulk upsert products from a JSON file (up to 100)
sequenzy products upsert --products-file ./products.json

# Delete a product pushed via the Commerce API
sequenzy products delete my-ebook

# Upload a local file and attach it as the product's deliverable
sequenzy products attach-file prod_abc --file ./guide.pdf

# Attach an externally hosted file instead
sequenzy products attach-file prod_abc --url https://example.com/template.zip --name template.zip

# Remove the attached file
sequenzy products detach-file prod_abc
```

`attach-file --file` (and `upsert --file`) uploads the file to Sequenzy storage (PDF, ePub, ZIP, images, audio, video, or text, up to 100MB) and attaches it in one step. `attach-file` and `detach-file` accept the product ID from `products list` or, for Commerce API products, your own `productId` (e.g. `sequenzy products attach-file my-ebook --file ./ebook.pdf`). After attaching, purchases of the product enrich the `saas.purchase` event with `download.url` and `download.name`, so purchase sequences can deliver the file with `{{event.download.url}}`. See [Digital Product Delivery](/concepts/digital-products).

`upsert` writes to the platform-agnostic [Commerce API](/concepts/custom-commerce) catalog: products are keyed by your own `productId`, and the same IDs are matched against order line items for purchase sequences and product filters.

### Account

```bash theme={null}
sequenzy account
sequenzy account --json
sequenzy websites
```

### Audit

```bash theme={null}
# Connected integrations and their sync health
sequenzy integrations list
sequenzy integrations list --all --json

# Sender (From) and reply-to profiles, with sending readiness
sequenzy sender-profiles list

# Open/click tracking, attribution window, and tracking domain
sequenzy tracking settings

# Who a scheduled campaign will actually reach
sequenzy campaigns audience camp_123
```

These commands are read-only. `integrations list` never returns credentials,
access tokens, or webhook secrets. `sender-profiles list` reports `canSend` per
address so you can spot a From address sitting on an unverified domain.
`campaigns audience` resolves list and segment names, flags references to
deleted records, and recomputes the recipient count at read time; it warns when
targeting is unset, because scheduling then sends to every active subscriber.

`sequenzy account` makes a live request with the active credential and prints
the key name, ID, non-secret prefix, type, effective preset, full-access state,
scope receipt, missing marketing read scopes, and API Keys settings URL. Compare
the name and prefix with Settings after replacing a key. Only `scopes: null`
means all current and future permissions; an explicit selection of every
current scope is reported as a fixed custom selection. This command requires
`account:read`; if that scope is missing, open API Keys settings from the
dashboard directly.

### API Keys

```bash theme={null}
# List company API key metadata without returning secrets
sequenzy api-keys list --company comp_123

# Create a company API key (full access by default)
sequenzy api-keys create --name "CI deploy key" --company comp_123

# Create a key restricted to a permission preset
sequenzy api-keys create --preset transactional_sender --name "Order emails"

# Create a key with explicit permission scopes
sequenzy api-keys create --scopes "subscribers:read,subscribers:write,events:write"

# Permanently revoke an unused key by its exact ID
sequenzy api-keys revoke key_abc123 --company comp_123 --yes
```

Supported presets: `full_access`, `read_only`, `agent_safe`, `ai_drafting`, `data_ingest_safe`, `data_ingest_automations`, `transactional_sender`, `marketing_sender`. Pass `--scopes` with a comma-separated list to override the preset with custom permissions. The creation output includes the effective preset, full-access state, and permission receipt returned by the API. The plain key is only returned on creation, so save it immediately.

`api-keys list` returns IDs, names, non-secret prefixes, permission receipts,
usage timestamps, and a current-key marker. It never returns plain keys or
stored hashes. Run it before `api-keys revoke`; `api-keys delete` is a
compatibility alias for the same permanent operation. Without `--yes`, the CLI
asks for confirmation.

### AI Generation

```bash theme={null}
# Generate email
sequenzy generate email "Welcome email for new SaaS trial users"
sequenzy generate email "Product launch announcement" --style branded --tone professional

# Deprecated compatibility alias (persists a disabled sequence draft)
sequenzy generate sequence "Onboarding for SaaS trial users"

# Generate subject lines
sequenzy generate subjects "Black Friday sale" --count 5

# Generate SMS message variants
sequenzy generate sms "Cart reminder with a free-shipping hook" --count 3
```

Generated email content is returned as draft content for review. The deprecated
`generate sequence` command persists a disabled draft and prints a migration
warning; prefer `sequences create <name> --trigger <trigger> --goal <goal>`.
Generated emails and sequence steps include the company's configured logo and
footer, and campaigns created from prompts inherit the company font. Sequence
generation supports up to 10 emails. Generated SMS messages are plain text with
encoding and segment counts - use one as the `text` of an SMS sequence step.

### SMS

```bash theme={null}
# Show SMS add-on status, credits, and phone numbers
sequenzy sms settings
sequenzy sms settings --json

# Send a real test SMS (charges credits, max 5/hour)
sequenzy sms send-test +15550100123 --text "Test from Sequenzy"
```

SMS sequence steps only send when the add-on is enabled, the plan is eligible, and a verified number exists - `sms settings` reports all of that (the JSON output includes a `readyToSend` flag). Add SMS steps to sequences with `type: "sms"` and a plain-text `text` field in `sequences create --steps-*` or `sequences update --insert-steps-*`, and edit existing SMS steps with `sequences update --sms-steps-json '[{"nodeId":"node_sms","text":"New message"}]'`.

### Feedback

```bash theme={null}
# Report a missing capability
sequenzy feedback "No command to bulk-delete campaigns by label" --category missing_capability

# Report a bug with context
sequenzy feedback "Segment count and list output disagree for seg_123" --category bug --context "Auditing a re-engagement segment"
```

Sends product feedback about Sequenzy straight to the team. Categories: `missing_capability`, `bug`, `docs`, `ux`, `praise`, `other` (default). If you are an AI agent using this CLI, send feedback whenever your user wanted something the CLI does not support - it directly shapes what gets built next.

## Environment Variables

| Variable           | Description      | Default                    |
| ------------------ | ---------------- | -------------------------- |
| `SEQUENZY_API_URL` | API server URL   | `https://api.sequenzy.com` |
| `SEQUENZY_APP_URL` | App URL for auth | `https://sequenzy.com`     |

## Config Location

The CLI stores configuration in:

* **macOS/Linux**: `~/.config/sequenzy/config.json`
* **Windows**: `%APPDATA%\sequenzy\config.json`

## Scripting

The CLI is designed to work well in scripts:

```bash theme={null}
#!/bin/bash

# Import subscribers from CSV into a list
sequenzy lists import list_123 --emails-file subscribers.csv

# Check if logged in
if ! sequenzy whoami > /dev/null 2>&1; then
  echo "Not logged in"
  exit 1
fi
```

## Exit Codes

| Code | Meaning |
| ---- | ------- |
| 0    | Success |
| 1    | Error   |

## Troubleshooting

CLI failures now include a short human explanation, a suggested next step, and a direct docs link so agents and scripts can recover faster.

### "could not determine executable to run for package sequenzy"

Use the CLI package explicitly:

```bash theme={null}
bunx @sequenzy/cli login
```

`bunx sequenzy login` installs the unscoped TypeScript API library package, which does not include the `sequenzy` executable.

### "Not logged in"

Run `sequenzy login` to authenticate.

### "API error: 401"

Your session may have expired. Run `sequenzy login` again.

### Browser doesn't open

`sequenzy login` always prints the approval link. Open the URL shown in the terminal to complete authentication.
