Posthawk CLI

The Posthawk CLI ships inside the same npm package as the Node.js SDK. A single global install gives you commands to scaffold projects, preview React Email templates with hot reload, send mail from the terminal, and manage your whole account — domains, contacts, suppressions, webhooks, broadcasts, newsletters, scheduled emails, and validation. There is also an interactive REPL (run bare `posthawk` in a TTY) and an interactive send wizard.

SDKnpm i -g posthawk

Install once globally. You get the `posthawk` binary plus all the SDK types. The CLI works on macOS, Linux, and Windows with Node.js 18 or newer.

npm i -g posthawk
npm i -g posthawk

# verify install
posthawk --version
SDKposthawk login

Store your API key locally in ~/.posthawk/config.json (file mode 0600). The CLI verifies the key against /v1/domains before saving. You can also set POSTHAWK_API_KEY as an env var to override the stored key.

posthawk login
# Interactive login (masked prompt)
posthawk login

# Or via environment variable
export POSTHAWK_API_KEY=ck_live_...

# Check the active key
posthawk whoami

# Remove the stored key
posthawk logout
SDKposthawk init [dir]

Scaffold a new project with a React Email starter template, package.json, .env.local, and .gitignore. Run it in an existing directory to add the files without overwriting anything.

posthawk init [dir]
# New folder
posthawk init my-emails
cd my-emails
npm install

# Or scaffold into the current folder
posthawk init .

# Output:
#   created  emails/welcome.tsx
#   created  .env.local
#   created  .gitignore
#   created  package.json
#   ✓ Project ready.
SDKposthawk preview <file.tsx>

Start a local dev server with hot reload for a React Email template. Edit the file and the rendered preview updates instantly via SSE. The preview server has an HTML view and a Plain Text view so you can check both variants of the email.

Parameters

filestringrequired

Path to a .tsx/.jsx email template with a default export

--portnumberoptional

Override the default port (7321)

--propsjsonoptional

JSON string passed as props to the component

posthawk preview <file.tsx>
# Start the preview server
posthawk preview emails/welcome.tsx

# Custom port
posthawk preview emails/welcome.tsx --port 8000

# Pass props to the component
posthawk preview emails/welcome.tsx \
  --props '{"name":"Alex","actionUrl":"https://example.com"}'

# Output:
#   ▸ starting dev server at http://localhost:7321
#   ✓ ready in 182ms
#     watching for changes · press Ctrl+C to stop
SDKposthawk send [file.tsx] --to <email> --from <email>

Send an email from the terminal. Supply exactly one body source: a React Email template file (compiled + rendered to HTML/text), inline --html, inline --text, or a --template-id. The file positional is OPTIONAL. In a TTY, running `posthawk send` with no file and no --html/--text/--template-id launches an interactive wizard (pick a verified domain, recipient, subject, body type, and confirm).

Parameters

filestringoptional

Path to a .tsx/.jsx email template. Optional — omit to use --html/--text/--template-id or the interactive wizard.

--tostringrequired

Recipient(s), comma-separated

--fromstringrequired

Sender (must be from a verified domain)

--subjectstringoptional

Subject line (required for inline --html/--text sends; overrides a template's default subject)

--ccstringoptional

CC recipients, comma-separated

--bccstringoptional

BCC recipients, comma-separated

--textstringoptional

Inline plain-text body (use instead of a template file)

--htmlstringoptional

Inline HTML body (use instead of a template file)

--template-idstringoptional

Send using a stored Posthawk template by id

--reply-tostringoptional

Reply-to address

--scheduled-forstringoptional

ISO 8601 datetime to schedule the send

--timezonestringoptional

IANA timezone for --scheduled-for

--idempotency-keystringoptional

Idempotency-Key header for safe retries

--propsjsonoptional

JSON string passed as props to the template component

--dry-runflagoptional

Compile and render without sending

posthawk send [file.tsx] --to <email> --from <email>
# Template file send
posthawk send emails/welcome.tsx \
  --to alex@acme.io \
  --from hello@yourdomain.dev

# Inline HTML send (no file)
posthawk send \
  --to alex@acme.io \
  --from hello@yourdomain.dev \
  --subject "Quick note" \
  --html "<h1>Hello!</h1>"

# Send a stored template, scheduled for later
posthawk send \
  --template-id welcome-template \
  --to alex@acme.io \
  --from hello@yourdomain.dev \
  --scheduled-for 2026-06-20T14:00:00Z \
  --timezone America/New_York \
  --idempotency-key 550e8400-e29b-41d4-a716-446655440000

# Interactive wizard (TTY, no file/body flags)
posthawk send

# Dry-run: compile and render without hitting the API
posthawk send emails/welcome.tsx \
  --to test@example.com \
  --from hi@example.dev \
  --dry-run
SDKexport default Component; export const subject

The CLI expects each template to default-export a React component. Optionally, you can export a `subject` string or function — the CLI uses it as the default subject when --subject is not passed. The `subject` function receives the same props as the component.

export default Component; export const subject
// emails/welcome.tsx
import { Body, Container, Heading, Html, Text } from '@react-email/components';

interface WelcomeEmailProps {
  name?: string;
}

// Optional subject export — receives the same props as the component
export const subject = ({ name }: WelcomeEmailProps) =>
  `Welcome to Posthawk${name ? `, ${name}` : ''}!`;

// Default export is the component
export default function WelcomeEmail({ name = 'there' }: WelcomeEmailProps) {
  return (
    <Html>
      <Body>
        <Container>
          <Heading>Hi {name},</Heading>
          <Text>Welcome to Posthawk!</Text>
        </Container>
      </Body>
    </Html>
  );
}
SDKposthawk validate <email> · posthawk status <jobId>

Validate a single email address, or check the delivery status of a previously sent email by its job id. Both take a single required positional argument and no flags.

posthawk validate <email> · posthawk status <jobId>
# Validate an address
posthawk validate user@example.com

# Check a send's status
posthawk status abc-123-def
SDKposthawk domains [list|get|add|verify|remove|receiving|webhook]

Manage sending domains. Bare `posthawk domains` defaults to list. Subcommands cover add/verify/remove, inbound receiving toggles, and webhook config.

posthawk domains [list|get|add|verify|remove|receiving|webhook]
# List (default) — shows verification status + region
posthawk domains
posthawk domains list

# Get one domain's DNS records + state
posthawk domains get <id>

# Add a domain (optional --region: us-east-1 | eu-north-1)
posthawk domains add mail.example.com --region us-east-1

# Verify after DNS propagates
posthawk domains verify <id>

# Remove a domain
posthawk domains remove <id>

# Inbound receiving
posthawk domains receiving enable <id>
posthawk domains receiving disable <id>

# Inbound webhook
posthawk domains webhook set <id> --url https://yourapp.com/inbound
posthawk domains webhook test <id>
SDKposthawk scheduled [list|get|cancel|reschedule|send-now]

Manage scheduled emails. Bare `posthawk scheduled` defaults to list. reschedule takes an --at ISO datetime.

posthawk scheduled [list|get|cancel|reschedule|send-now]
# List (default) — filterable + paginated
posthawk scheduled
posthawk scheduled list --status scheduled --limit 20 --offset 0

# Get one
posthawk scheduled get <id>

# Cancel
posthawk scheduled cancel <id>

# Reschedule to a new time
posthawk scheduled reschedule <id> --at 2026-06-20T14:00:00Z

# Send immediately
posthawk scheduled send-now <id>
SDKposthawk contacts [list|get|create|update|delete|import]

Manage workspace contacts from the terminal. Bare `posthawk contacts` defaults to list. import reads a JSON file (an array, or an object with a "contacts" array).

posthawk contacts [list|get|create|update|delete|import]
# List (default) — optional filters
posthawk contacts
posthawk contacts list --tag newsletter --search jane --page 1

# Get one
posthawk contacts get <id>

# Create
posthawk contacts create --email jane@example.com --name "Jane" --tags newsletter,beta

# Update (at least one field)
posthawk contacts update <id> --tags newsletter,premium
posthawk contacts update <id> --unsubscribed true

# Delete
posthawk contacts delete <id>

# Bulk import from a JSON file
posthawk contacts import contacts.json
SDKposthawk suppressions [list|add|remove]

Manage the suppression list. Bare `posthawk suppressions` defaults to list. add takes an email positional and an optional --notes.

posthawk suppressions [list|add|remove]
# List (default)
posthawk suppressions
posthawk suppressions list

# Add (reason "manual")
posthawk suppressions add user@example.com --notes "Requested removal"

# Remove by id
posthawk suppressions remove <id>
SDKposthawk webhooks [list|create|update|delete|test]

Manage webhook endpoints. Bare `posthawk webhooks` defaults to list. --events is a comma-separated list (send, delivery, bounce, complaint, reject, delivery_delay, open, click).

posthawk webhooks [list|create|update|delete|test]
# List (default)
posthawk webhooks

# Create
posthawk webhooks create \
  --url https://yourapp.com/webhooks/posthawk \
  --events delivery,bounce,complaint \
  --description "Production webhook"

# Update (at least one field)
posthawk webhooks update <id> --events delivery,bounce,complaint,open,click
posthawk webhooks update <id> --enabled false

# Test (sends a synthetic event)
posthawk webhooks test <id>

# Delete
posthawk webhooks delete <id>
SDKposthawk broadcasts [list|get|create|send|cancel]

Manage one-off broadcasts. Bare `posthawk broadcasts` defaults to list. create uses --type all|tag with an optional --tag segment.

posthawk broadcasts [list|get|create|send|cancel]
# List (default) — optional --status + --page
posthawk broadcasts
posthawk broadcasts list --status draft --page 1

# Get one
posthawk broadcasts get <id>

# Create
posthawk broadcasts create \
  --name "March Update" \
  --from hello@yourdomain.com \
  --from-name "Acme" \
  --subject "What shipped in March" \
  --html "<h1>This month at Acme</h1>..." \
  --type tag --tag newsletter

# Send / cancel
posthawk broadcasts send <id>
posthawk broadcasts cancel <id>
SDKposthawk newsletters [list|get|issues send]

Inspect newsletters and send an issue. Bare `posthawk newsletters` defaults to list. Only `issues send` is supported under the issues subgroup (create/update issues via the dashboard or SDK).

posthawk newsletters [list|get|issues send]
# List (default)
posthawk newsletters
posthawk newsletters list --limit 20 --offset 0

# Get one
posthawk newsletters get <id>

# Send an issue (both ids positional)
posthawk newsletters issues send <newsletterId> <issueId>
SDKposthawk watch <file.tsx> --to <email> --from <email>

Watch a React Email template and re-send on every save — handy for iterating against a real inbox. Requires a file positional plus --to and --from.

Parameters

filestringrequired

Path to a .tsx/.jsx email template

--tostringrequired

Recipient(s), comma-separated

--fromstringrequired

Sender (verified domain)

--subjectstringoptional

Subject line

--ccstringoptional

CC recipients

--bccstringoptional

BCC recipients

--propsjsonoptional

JSON props for the component

posthawk watch <file.tsx> --to <email> --from <email>
posthawk watch emails/welcome.tsx \
  --to me@example.com \
  --from hello@yourdomain.dev \
  --props '{"name":"Alex"}'
SDKposthawk logs [--follow] [--limit <n>]

Show your local CLI send history (recorded on this machine). --follow (alias -f) tails new entries; --limit caps how many rows are shown.

posthawk logs [--follow] [--limit <n>]
posthawk logs --limit 50
posthawk logs --follow
SDKposthawk doctor · upgrade · completion <shell>

Maintenance commands. doctor runs health checks (CLI version vs npm, auth, API reachability, project peer deps). upgrade runs `npm install -g posthawk@latest`. completion prints a shell completion script for bash, zsh, or fish.

posthawk doctor · upgrade · completion <shell>
# Diagnose your setup
posthawk doctor

# Upgrade the CLI to the latest release
posthawk upgrade

# Print a completion script
posthawk completion zsh   # or: bash | fish
SDKposthawk

Run `posthawk` with no arguments in a TTY to drop into an interactive REPL. The prompt accepts any CLI command (a leading `posthawk` token is stripped if you type it). Tab-completion is available. Built-ins: exit / quit / :q to leave, clear / cls to clear the screen.

posthawk
$ posthawk
posthawkdomains list
posthawksend --to me@example.com --from hi@yourdomain.dev --subject Hi --text "Hello"
posthawkexit
SDKPOSTHAWK_API_KEY / POSTHAWK_BASE_URL

All CLI commands respect the same environment variables as the SDK. Env vars take precedence over the stored config file.

Parameters

POSTHAWK_API_KEYstringoptional

Overrides the stored API key — essential for CI/CD

POSTHAWK_BASE_URLstringoptional

Overrides the API URL — use this for self-hosted instances

NO_COLORflagoptional

Disables colored terminal output

POSTHAWK_API_KEY / POSTHAWK_BASE_URL
# Run in CI without interactive login
export POSTHAWK_API_KEY=ck_live_...

posthawk send emails/release-notes.tsx \
  --to announcements@acme.io \
  --from release-bot@acme.dev \
  --props "{\"version\":\"${GITHUB_REF_NAME}\"}"

# Self-hosted instance
export POSTHAWK_BASE_URL=https://api.yourdomain.com
posthawk whoami
SDKGitHub Actions workflow

Ship release notes automatically when a git tag is pushed. The CLI works in any CI environment that has Node.js 18+ and can install global npm packages.

GitHub Actions workflow
# .github/workflows/release-notes.yml
name: Send Release Notes
on:
  push:
    tags: ['v*']

jobs:
  send:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'

      - run: npm ci
      - run: npm i -g posthawk

      - name: Send release notes
        env:
          POSTHAWK_API_KEY: ${{ secrets.POSTHAWK_API_KEY }}
        run: |
          posthawk send emails/release-notes.tsx \
            --to changelog@acme.io \
            --from release-bot@acme.dev \
            --subject "Acme ${{ github.ref_name }} shipped" \
            --props "{\"version\":\"${{ github.ref_name }}\"}"
SDKesbuild + React Email

The CLI compiles your .tsx file at runtime using esbuild, then dynamic-imports the result to render with @react-email/render. Compiled output is cached in node_modules/.posthawk-cache/ so subsequent runs start faster. React and @react-email/* are kept external during compilation so they resolve from your project's own node_modules — meaning you always get the exact same React Email version your templates were authored against.

esbuild + React Email
# What the CLI does when you run 'posthawk send welcome.tsx':
#
#   1. esbuild bundles welcome.tsx → ESM with react/@react-email/*
#      left as external imports
#   2. Writes the output to node_modules/.posthawk-cache/<hash>.mjs
#   3. Dynamic-imports that file with a cache-busting ?t=timestamp
#   4. Pulls the default export as the component
#   5. Calls render() from @react-email/render to get HTML + text
#   6. Sends the rendered payload via the SDK
#
# First compile: ~500ms cold. Subsequent compiles: ~200ms warm.