Agent setup
This page is written for a coding agent (Cursor, Claude Code, Codex, and similar) to wire Letter into an application end to end. Follow the steps in order. The goal: install the SDK, authenticate without ever putting a secret in chat, and start reporting users and events so onboarding sequences can fire.
No secrets appear in this document. The API key is provisioned by a browser confirmation during
npx @letterapp/cliand written directly to the project’s environment file. Never paste an API key into a prompt or commit it to source.
1. Install and authenticate (one command)
Run the Letter CLI from the project root:
npx @letterapp/cli
What it does, interactively:
- Prints a short confirmation code and opens your browser to the Letter dashboard (press Enter, or use the printed URL on a headless machine).
- You confirm the code matches and pick the project to connect.
- The CLI receives a freshly minted API key over a secure back channel and
writes
LETTER_API_KEYto.env.local(plus~/.letter/credentials.jsonfor tooling). The key is never printed to the terminal. - It detects your package manager and installs
@letterapp/node.
If @letterapp/node is not installed automatically, add it manually:
npm install @letterapp/node
# or: pnpm add @letterapp/node / yarn add @letterapp/node / bun add @letterapp/node
Confirm LETTER_API_KEY exists in .env.local. Do not echo its value.
2. Create a server-side client module
Letter is a server-side SDK. Create one shared client and import it where you
need it. Example (lib/letter.ts):
import { Letter } from "@letterapp/node";
export const letter = new Letter({
apiKey: process.env.LETTER_API_KEY!,
// Only set baseUrl for self-hosted or local Letter instances. Omit in prod;
// the SDK defaults to https://api.letter.app. The CLI writes LETTER_BASE_URL
// to the env file when it is needed.
...(process.env.LETTER_BASE_URL ? { baseUrl: process.env.LETTER_BASE_URL } : {}),
});
Keep the key in the environment. Never inline the raw key in source.
3. Identify users
Call identify wherever a user signs up or logs in, so Letter knows who they
are. Use a stable userId (your primary key), include email, and pass any
traits you segment on.
await letter.identify({
userId: user.id,
email: user.email,
traits: { name: user.name, plan: user.plan },
});
4. Track key events
Call track on the 2-3 actions that matter most for lifecycle messaging
(activation, first value, upgrade). Start small.
await letter.track({
userId: user.id,
event: "Signed Up",
properties: { source: "web" },
});
Good first events for most products: Signed Up, the core “aha” action, and a
conversion/upgrade event.
5. Group users into accounts (B2B, optional)
If the product is B2B, associate users with their account/workspace:
await letter.group({
userId: user.id,
accountId: workspace.id,
name: workspace.name,
traits: { plan: workspace.plan },
});
6. Flush in serverless and scripts
On long-running servers, the calls above auto-batch in the background. In serverless handlers and one-off scripts there is no background time, so flush before the function returns or the process exits:
await letter.flush();
// or, in a script: await letter.close();
For serverless, you can also construct the client with flushAt: 1 and use the
*Sync methods (identifySync, trackSync).
7. Verify
Trigger a real signup (or call identify once from a script), then confirm the
contact/event landed. The onboarding “waiting” screen lights up automatically on
the first call; you can also use the @letterapp/mcp check_connection tool.
Reference
- Ingestion API (identify / group / track / batch / auth / rate limits): /docs/api
- Node SDK (options, retries, idempotency, serverless mode): /docs/node-sdk
- Bulk-loading an existing user base: /docs/initial-import
Summary for the agent
When done, summarize for the human: which files you created or edited, where you
placed identify/track/group calls, the events you instrumented, and that
LETTER_API_KEY lives in .env.local. Do not commit unless asked, and never
print or commit the API key.