Send basic transactional emails with PostShiba
Verify a domain and send your first production message.
This guide creates a shared cluster, verifies mail.example.com, and queues one receipt. You will see sending_ready, sending_allowed, and queued along the way.
Before you start
- A Full access key from API keys for the setup steps: cluster, domain, and webhook. Run them from your shell, not your app.
- A Send only key for the app that sends the receipt in step 4.
- A domain whose DNS you can edit. A subdomain keeps root MX free. See Subdomain or root domain?.
- A payment method on the team. Cluster create returns
403{error: payment_required}when billing is on and there is no card.
1. Set the shell
Run those exports in the current shell. If you saved the block as setup.sh, load it with source setup.sh so later commands inherit the variables.
2. Create a cluster
A cluster is a named sending environment. Customer clusters are shared-IP. size is the node size. plan is the monthly capacity. Omit plan and create sets nano.
A pending team can create one cluster. A second cluster returns 403 {error: kyc_required} until the team is approved. Create also issues a default SMTP credential. The password is not in this response.
Save the cluster id. The full object also includes emails_per_hour, emails_per_day, smtp_endpoint, http_endpoint, classification, and sending_ips. If sending_ready is still false, GET the cluster again.
If the response is 403 kyc_required, the team is still pending. While the team is pending, you send only to member emails, live inbox addresses, and inbox forward-to addresses, and anything else returns 403 kyc_required. See Does PostShiba require production approval?. If a later send returns 403 cluster_not_ready, the cluster is not ready yet. See Errors.
3. Add a sending domain
Create queues a DNS check. sending_allowed stays false until DKIM is verified and return-path is verified (or you imported a key with dkim_manual).
Publish the DKIM CNAME and return-path CNAME exactly as returned. Then ask PostShiba to check DNS.
Read the domain again. Do not sleep for a fixed time.
Wait until sending_allowed is true. See Add and verify a domain for the records and Verified domains for the statuses. If sending_allowed stays false, check last_verification_error.
4. Send a receipt
Install the Node client with npm install @postshiba/node. emails.send maps to POST /api/v1/emails. POST /api/v1/emails does not accept Idempotency-Key. We call POST /api/v1/teams/:team_id/clusters/:cluster_id/sends with Idempotency-Key when we want the first 201 replayed for 24 hours. A reused key with a different body returns 409 idempotency_conflict.
from is optional. A blank From is replaced with hello@ the first sending-ready domain.
Use your Send only key here, not the Full access key from the shell. It can send and nothing else, so a leaked app key cannot change your domains or webhooks. Keep it on your server, never in browser or mobile code.
A successful request returns 201 with "queued": true.
If inject queued nobody, the response is 502 {error: inject_failed}. Other send errors are on Errors and Sending.
REST does not need the SMTP password in application code. Create another credential only when you send over SMTP. See SMTP credentials.
5. Watch delivery
Create one webhook endpoint for delivery events. Omit cluster_id and tenant_id to receive the whole team.
Store secret now. Lists omit it.
The POST is a SendGrid Event Webhook array. Unique args you set at send time are flattened onto each event and also appear as unique_args. Verify X-Capsule-Signature before you trust a body. See Verify webhook requests and Event types.
You now have a shared cluster, a verified mail.example.com, a queued receipt, and a signed delivery webhook.
When to move to dedicated IPs
Receipts and password resets can stay on the cluster's shared IPs until you have steady volume. Shared IPs are the better default below about 50,000 messages a month or when traffic is spiky; dedicated IPs need that volume to keep a reputation. When you are ready, put dedicated IPs in a transactional pool and opt those messages in with "X-Capsule-Pool": "receipts" on the headers object. Keep marketing on a separate bulk cluster. A new dedicated IP warms up for 14 days; overflow keeps receipts flowing on shared IPs meanwhile. See Set up IP pools and warm up dedicated IPs.