Sign in Create account
Guides

postal CLI

Send emails and events, update contacts and check domains from the terminal, with JSON output for scripts and AI agents.

#Install

The postal CLI comes in the postal-so package and needs Node 18 or newer:

Terminal
npm install --global postal-so
postal --version

# or, without installing
npx postal-so --help

Use it to test a key, send a test email, trigger an event or check domains without writing code, and in scripts and AI agents (with --json).

#Authentication

postal login asks for the key (what you type is not shown), checks it with the API and saves it to ~/.config/postal/config.json with 0600 permissions (only your user can read it). Outside a terminal, it reads the key from standard input. postal whoami shows the account and the key in use, and postal logout deletes the saved key.

Terminal
postal login                       # asks for the key and saves it to ~/.config/postal/config.json
echo "$POSTAL_API_KEY" | postal login   # without a terminal
postal whoami                      # plan, quotas and the key in use (masked)
postal logout

The POSTAL_API_KEY environment variable takes precedence over the file: in CI and on servers, use only the variable. A sending-only key is enough for emails send, events send and emails get (only for the emails it sent); the other commands need full access.

#Commands

CommandWhat it does
postal login, logout, whoamiSaves, deletes and shows the key in use.
postal emails sendSends an email with --html, --text or --template.
postal emails get <id>Shows the status of an email.
postal events sendSends an event that starts automations.
postal contacts upsertCreates or updates a contact by --external-id or --email.
postal contacts get <contact>Shows a contact by email, ID or external_id:<id>.
postal domains listLists the domains and their verification status.
postal domains verify <id>Requests the DNS verification of a domain.

postal --help lists the commands and postal <command> --help shows the options and examples of each one (the help text is in Portuguese).

#Send an email

The values of --html and --text accept @file to read the content from a file. Repeat --to, --cc and --bcc (or separate them with commas) for several addresses. With --idempotency-key, repeating the command does not send the email twice.

Terminal
postal emails send \
  --from "Acme Store <[email protected]>" \
  --to [email protected] \
  --subject "Welcome!" \
  --html "<p>We are glad to have you here.</p>"

postal emails send --from [email protected] --to [email protected] \
  --subject "Your report" --html @report.html --text @report.txt

# with a published template
postal emails send --to [email protected] --template welcome --var NAME=Jane

#Events and contacts

events send accepts --email or --external-id to say who did it, --props with the event data and --contact-props to update the contact before the automations run. contacts upsert looks up by --external-id and then by --email; pass the email to create a new contact.

Terminal
postal events send --email [email protected] --name signup --props '{"plan":"free"}'

postal contacts upsert --external-id user_42 --email [email protected] \
  --first-name Jane --props '{"plan":"pro"}'

postal domains list

#Output for scripts and agents

Add --json to print the API response as JSON. On error, the JSON {"error": {"statusCode", "name", "message"}} goes to stderr.

Terminal
postal domains list --json | jq '.data[] | select(.status != "verified") | .name'

postal emails send --from [email protected] --to [email protected] \
  --subject "Test" --text "It worked" --json
# {"id": "019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b"}
Exit codeMeaning
0Success.
1API error (such as validation_error) or network error.
2Incorrect usage: unknown option, missing value or invalid JSON.

POSTAL_API_URL changes the API URL (default https://api.postal.so), which is useful for tests.