Sign in to fill these examples with your own keys. Sign in Start free trial

Send emails with the CLI

Install the postshiba command, sign in, and send from your terminal.

What you'll build

A receipt email sent from your terminal with postshiba, then a few account commands you can reuse in scripts.

Before you start

  • An API key with Full access. See Create an API key. Support keys cannot send.
  • A verified sending domain. See Add and verify a domain. from must be an address on it.
  • A cluster that is ready to send (sending_ready is true). See Clusters.
  • Your team id. It is on the API keys page.
  • Node.js 20 or later.

Install

install.sh
1npm install -g @postshiba/cli

For a one-off run without installing:

npx.sh
1npx @postshiba/cli

On a terminal, postshiba with no arguments opens a menu: send an email, add a sending domain, inspect clusters and inboxes, check your setup, or install the agent skill. Missing fields become prompts, with a spinner and a table for the result.

Sign in

Run postshiba login. It asks for the API key, checks it, then prints Signed in as your email. It then asks for your team id, and offers a default cluster if you have one. The key is never printed.

login.sh
1postshiba login

Or set environment variables and skip the config file:

env.sh
1export POSTSHIBA_API_KEY=YOUR_API_KEY 2export POSTSHIBA_TEAM_ID=KjkAJW 3export POSTSHIBA_CLUSTER_ID=NmQpXr

Each invocation reads a flag first, then the matching env var, then ~/.config/postshiba/config.json. A missing key is a usage error that names postshiba login and POSTSHIBA_API_KEY. A team-scoped command without a team id names --team. postshiba whoami reprints the signed-in user. postshiba logout deletes the config file.

Send your first email

On a terminal, postshiba send prompts for anything missing. from suggests hello@ on a verified domain. to is comma-separated. Then pick a body: plain text, an HTML file, or a published template. Confirm the preview, and the success line includes the message id.

To skip the prompts, pass the flags:

send.sh
1postshiba send \ 2 --from hello@mail.example.com \ 3 --to you@example.com \ 4 --subject "Your receipt" \ 5 --text "Thanks for your order." \ 6 --arg order_id=ord_123

The message then shows under Events in the dashboard. from and at least one --to are required. Repeat --to for more recipients. Pass --cluster NmQpXr to pin a cluster. Without it, the CLI uses the default from login or POSTSHIBA_CLUSTER_ID, or lets the API pick a sending-ready cluster.

With --json, success prints the API body:

response.json
1{ 2 "queued": true, 3 "message_id": "abc@capsule.test", 4 "from": "hello@mail.example.com", 5 "to": ["you@example.com"], 6 "subject": "Your receipt", 7 "unique_args": {"order_id": "ord_123"} 8}

Templates and sandbox tests

Pass --template with the alias or public id and --var key=value for each Liquid variable. Leave out --text and --html.

template.sh
1postshiba send \ 2 --from hello@mail.example.com \ 3 --to you@example.com \ 4 --template welcome \ 5 --var name=Ada \ 6 --cluster NmQpXr

--sandbox runs every send check without delivering. It requires --cluster and posts to the cluster send path.

sandbox.sh
1postshiba send \ 2 --from hello@mail.example.com \ 3 --to you@example.com \ 4 --subject "Your receipt" \ 5 --text "Thanks for your order." \ 6 --cluster NmQpXr \ 7 --sandbox

--idempotency-key also uses that cluster path. See Sending.

Manage your account from the terminal

Commands follow postshiba <resource> <action>. Positional ids follow the path. Rows that create or update take --data as JSON, @file.json, or - for stdin.

Resource Example
clusters postshiba clusters get NmQpXr
sending-domains postshiba sending-domains create --data '{"name":"mail.example.com"}'
messages postshiba messages get PqRzMn GxTyVu
events postshiba events list NmQpXr
smtp-credentials postshiba smtp-credentials create NmQpXr --data @cred.json
firewall postshiba firewall add-entry --data @entry.json

Also network, tenants, inboxes, webhooks, templates, and suppressions. postshiba sending-domains make-primary HsVtYk promotes a domain. postshiba messages download-attachment PqRzMn GxTyVu 1 --output photo.png writes the file. postshiba help clusters lists the actions for a resource.

doctor.sh
1postshiba doctor

doctor checks that the key is valid, a team id is set, a cluster is sending_ready, and a sending domain is verified. Each failure prints the command that fixes it.

Scripts and agents

Interactive mode needs a TTY, no --json, and CI unset. Otherwise the CLI is plain: no prompts, and a successful call prints the API JSON with 2-space indent. --no-input forces that. A missing flag is then a usage error.

Flag Effect
--json Print API JSON. Never prompt.
--no-input Never prompt. Missing input exits 2.
--yes Skip the confirm on delete, suspend, release, and unassign. Plain mode requires it for those actions.
Exit Meaning
0 Success.
1 The API returned an error.
2 Usage error: unknown command, missing id, missing --data, or bad JSON. The message points at postshiba help <resource>.

The CLI ships an agent skill that teaches an assistant to pass --json, read credentials from the environment, run doctor --json first, and use --sandbox for tests. --yes only when you asked for a destructive action.

skills.sh
1postshiba skills install

That writes .cursor/skills/postshiba-cli/ in this project. Pass --global to install under your home directory, or --target claude or --target agents for those layouts. You can also run npx skills add postshiba/postshiba-cli. The same skill is in the PostShiba Cursor plugin.

Troubleshooting

A non-2xx response prints Error: <message> on stderr and, when present, (field: <field>). Then the process exits 1.

Status error What the CLI does
401 Invalid token or no user signed in Prints the error. The API key is missing, mistyped, or deleted. Copy it again from API keys, or run postshiba login.
403 forbidden, domain_unverified, or kyc_required Prints the error. Use a Full access key and a verified from. While your team is pending production approval, send only to team members, live inbox addresses, and inbox forward-to addresses.
422 invalid Prints the error and field when the API names one, such as a missing to, an unpublished template, or a rejected attachment.
429 throttled Prints the error, then The cluster hit its hourly send limit. Wait until the next hour before retrying. Do not retry in that window. See What happens at your cap.

Every other status is on Errors.

Next steps