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:
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.
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
| Command | What it does |
|---|---|
postal login, logout, whoami | Saves, deletes and shows the key in use. |
postal emails send | Sends an email with --html, --text or --template. |
postal emails get <id> | Shows the status of an email. |
postal events send | Sends an event that starts automations. |
postal contacts upsert | Creates 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 list | Lists 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.
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.
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.
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 code | Meaning |
|---|---|
0 | Success. |
1 | API error (such as validation_error) or network error. |
2 | Incorrect 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.