---
name: letter
description: Connect an app to Letter (product email automation) from the command line. Runs a secure, interactive device login that provisions the API key in the browser - never in the chat - writes it to .env.local, and installs the Node SDK. Also checks connection status.
homepage: https://letter.app
metadata: {"clawdbot":{"emoji":"✉️","requires":{"bins":["letter"]}}}
---

## Install the Letter CLI if it doesn't exist

```bash
npm install -g @letterapp/cli
# or run ad hoc with: npx @letterapp/cli
```

npm: https://www.npmjs.com/package/@letterapp/cli
github: https://github.com/vincenzor/letter-cli
website: https://letter.app

---

| Property | Value |
|----------|-------|
| **name** | letter |
| **description** | CLI to connect an app to Letter and verify the integration |
| **allowed-tools** | Bash(letter:*), Bash(npx @letterapp/cli:*) |

---

## What the CLI does

Two layers, set up by one login:

1. **Connect an app** (the common task) — wire Letter into the current project
   so it can send events. This is what the workflow below covers.
2. **Operate the workspace** — drive the same things the dashboard does
   (projects, contacts, segments, sequences, broadcasts, templates, domains,
   members) from the terminal. See "Operating the workspace" below.

### Two credentials, picked automatically

`letter login` mints both credentials at once and stores them. You never pass a
token by hand and never need to choose which one - each command grabs the right
credential on its own:

| Purpose | Format | Where it lives | Used by |
| --- | --- | --- | --- |
| Send events (SDK / ingestion) | `lt_live_*` | `.env.local` as `LETTER_API_KEY` | your app, `letter status` |
| Run the CLI / Management API | `lt_pat_*` | `~/.letter/credentials.json` | every resource command |

(The dashboard calls the `lt_live_*` key a "Project token" and the `lt_pat_*`
token an "API key". You rarely touch either value directly.)

## Core Workflow

The job is to wire Letter into the current project. Do NOT ask the user to paste
an API key. The CLI provisions it securely in the browser.

1. **Connect** — run the device login from the project root.
2. **Integrate** — add `identify` / `track` calls using the installed SDK.
3. **Verify** — confirm data has landed.

```bash
# 1. Connect (opens the browser for the user to confirm a code).
#    --yes skips the "press Enter" wait so the command runs unattended;
#    the user still must approve in the browser.
npx @letterapp/cli --yes

# 2. The CLI writes LETTER_API_KEY to .env.local and installs @letterapp/node.
#    Add server-side calls where users sign up / act:
#      letter.identify({ userId, email })
#      letter.track({ userId, event: "signed_up" })

# 3. Verify the pipe.
letter status
```

---

## Essential Commands

```bash
# Connect this project (interactive device login; default command)
npx @letterapp/cli
letter login
letter init

# Non-interactive prompt (agent/CI): don't wait for Enter. Browser approval
# is still required - the key is never returned to the terminal or chat.
letter login --yes

# Headless / remote: print the URL instead of opening a browser
letter login --no-open

# Skip installing the SDK
letter login --no-install

# Target a specific API origin (defaults to https://api.letter.app)
letter login --base-url https://api.letter.app

# Connection status
letter status                  # has the project received contacts/events?
letter auth status             # is this machine connected, and to which project?
letter auth logout             # remove the stored credential

# Config
letter config set base-url https://api.letter.app
letter config get
letter config reset
```

---

## Operating the workspace (Management API)

After login, the workspace PAT is stored, so resource commands work with no
extra auth. Every group has `list` / `get` / `create` / `update` / `delete`
plus verb subcommands; run `letter <group> --help` for exact flags. All support
`--json`. Project-scoped groups default to the connected project and take
`--project <slug>` to target another.

```bash
letter me                              # who am I, which workspace, which role
letter projects list --json
letter contacts list --limit 20
letter contacts suppress user_alice
letter segments preview --filter @paid.json
letter sequences draft seq_123 --graph @graph.json --trigger @trigger.json \
  --expected-revision 7
letter sequences publish seq_123
letter broadcasts preflight bc_42
letter broadcasts schedule bc_42                       # send now
letter broadcasts schedule bc_42 --scheduled-at 2026-06-01T09:00:00Z
letter domains create --domain mail.acme.com
letter domains verify dom_1
letter sender-identity --from-email hi@mail.acme.com --from-name "Acme"
```

Groups: `projects`, `members`, `invitations`, `api-keys` (your `lt_pat_*`
tokens), `contacts`, `accounts`, `events`, `suppressions`, `segments`,
`sequences`, `broadcasts`, `templates`, `domains`, `project-tokens` (project
`lt_live_*` keys), `sender-identity`, `sending-mode`, `me`. JSON flags
(`--filter`, `--graph`, `--trigger`, `--audience`, `--design`, `--mapping`)
accept inline JSON or `@path` to read from a file. Full reference:
https://letter.app/docs/cli and https://letter.app/docs/management-api.

---

## Output Modes

- **Default** — human-friendly colored output.
- `--json` — raw JSON for scripting:

```bash
letter --json auth status
letter --json status | jq '.events'
```

---

## Common Gotchas

1. **Never ask for or paste an API key.** The whole point of `letter login` is
   that the key is provisioned in the browser and written to `.env.local`
   directly. There is an `--api-key` flag, but it is for CI only - do not use it
   in an interactive or chat context.
2. **Browser approval is required.** Even with `--yes`, a human must approve the
   code in the browser. On a headless box use `--no-open` and hand the user the
   printed URL.
3. **Run from the project root.** The CLI writes `.env.local` and installs the
   SDK into the current working directory's project.
4. **Credential locations.** After login the project ingestion key
   (`lt_live_*`) lives in `.env.local` as `LETTER_API_KEY`, and the workspace
   PAT (`lt_pat_*`, used by management commands and `@letterapp/mcp`) lives in
   `~/.letter/credentials.json`. Both are secrets; keep them out of source
   control and never echo their values.
5. **Local/self-host.** Pass `--base-url` (or `letter config set base-url`) so
   the device flow and SDK reach the right instance.
6. **Verifying.** `letter status` reports contact/event counts. Zero counts mean
   the SDK calls haven't fired yet, not that login failed - check `letter auth
   status` to confirm the connection itself.
7. **Language-aware setup.** The CLI detects the stack and adapts. Node, Python,
   and Ruby have official SDKs: it installs the right package (`@letterapp/node`
   via npm/pnpm/yarn/bun, `letterapp` via pip/poetry/uv/pipenv, or `letterapp`
   via bundler/gem) and prints SDK instructions. Go, PHP, and anything else get
   HTTP API instructions instead: call `POST /v1/identify` and `POST /v1/track`
   (Bearer `LETTER_API_KEY`, `Content-Type: application/json`) directly.
8. **`npx` vs global.** If you ran setup with `npx @letterapp/cli` (no global
   install), the bare `letter` command isn't on PATH - prefix every command with
   `npx @letterapp/cli` (e.g. `npx @letterapp/cli status`). The CLI's own printed
   hints already adapt to how it was launched. To get the bare `letter` command,
   `npm install -g @letterapp/cli`.

---

## Quick Reference

```bash
npx @letterapp/cli            # Connect (device login)
letter login --yes            # Connect unattended (browser approval still needed)
letter login --no-open        # Print URL instead of opening a browser
letter status                 # Has data landed?
letter auth status            # Is this machine connected?
letter auth logout            # Remove stored credential
letter me                     # Identity/workspace/role behind the PAT
letter <group> --help         # Management commands (projects, contacts, ...)
letter config set base-url <url>
letter --json status          # JSON output
```
