Quickstart
This path gets a new account from zero to sending, receiving, and replying without touching DNS. It uses the managed *.primitive.email subdomain and the primitive CLI.
1. Create an Account
Sign up at primitive.dev. Your organization receives a managed subdomain like brave-otter.primitive.email.
The managed subdomain has MX and outbound records published automatically. You can receive inbound mail and send from addresses on that subdomain immediately.
2. Install And Login With The CLI
The CLI uses OAuth, so you do not need to create or paste an API key for terminal use.
brew install primitivedotdev/tap/primitive # or: npm install -g primitive primitive login primitive whoami
If you cannot install globally, run commands with npx primitive@latest.
Homebrew installs the required Node.js runtime automatically; npm and npx require Node.js 22 or newer. The CLI package and runtime SDK are separate: use primitive (also published as the scoped @primitivedotdev/cli) for the primitive binary, and use @primitivedotdev/sdk inside application code.
3. Create Optional cURL Credentials
If you use the cURL examples in this guide, open Settings -> API keys and create an API key. Store it as PRIMITIVE_API_KEY. CLI-only users can skip this.
For webhook verification, open Settings -> Webhooks and copy the signing secret as PRIMITIVE_WEBHOOK_SECRET.
export PRIMITIVE_API_KEY=prim_... export PRIMITIVE_WEBHOOK_SECRET=whsec_...
4. Verify API Access
primitive whoami5. Scaffold a Function
Functions are the fastest receive path because Primitive hosts the handler and wires it to inbound mail.
primitive functions:init my-fn cd my-fn npm install npm run build
This step is local project generation. There is no REST endpoint for scaffolding files on your machine; use REST/curl at deploy time.
The scaffold contains handler.ts, build.mjs, package.json, tsconfig.json, and a README. It imports the runtime client correctly and uses the bundler settings expected by Primitive Functions.
6. Deploy the Function
primitive functions:deploy \ --name my-fn \ --file ./dist/handler.js
Deploy creates the Function, but inbound mail does not reach it until you bind a route. Save the returned Function id, then register the inbound endpoint explicitly:
primitive functions:route-set --id <function-id> --fallback--fallback binds the Function to any active domain without a scoped binding of its own, which covers your managed subdomain. Keep the Function id for later redeploys.
7. Send an Email
Use the CLI first because it auto-resolves a usable from address on your managed domain.
primitive send \ --to [email protected] \ --body "Hello from Primitive" \ --wait
The --wait flag blocks until Primitive has a delivery result from the receiving side.
8. Understand First-Send Gates
Each recipient is checked against a set of entitlement gates. Out of the box, new accounts can send to:
- external addresses that have already sent you authenticated mail (replying to known addresses is granted by default).
Other recipient gates exist but require entitlements that are not granted to a fresh account by default:
- addresses on
*.primitive.email(requiressend_to_primitive_managed_domains); - addresses on domains you have verified (requires
send_to_confirmed_domains; verifying a domain lets you send from it, but sending to it needs this gate); - email addresses that belong to members of your Primitive organization (requires
send_to_org_member_emails); - addresses on domains that have opted in to receive from agents via a DNS
_agentsrecord (requiressend_to_opted_in_domains); - any domain, only after support enables broader outbound sending for your organization.
If a first send to Gmail, Outlook, Fastmail, or another external address returns 403 recipient_not_allowed, inspect the returned gates[] array — each gate carries its own fix object (read gates[].fix.action), though some denials have no automatic fix. See Sending Mail for the full model.
9. Receive an Email
Send a test message to any address at your managed subdomain, for example [email protected].
Primitive receives it through MX, parses it, stores it, signs the event, and invokes your Function. You can also read it from the CLI:
primitive emails:latest --limit 5 primitive emails:get-email --id <email-id>
10. Reply
Reply to an inbound message with threading derived from the original email:
primitive sending:reply-to-email \ --id <inbound-email-id> \ --body-text "Got it."
In SDK code, use client.reply(email, ...) in Node.js or Python and client.Reply(...) in Go. Primitive derives the recipient, Re: subject, In-Reply-To, and References headers.
Go Deeper
- Functions: handler shape, deploys, tests, logs, secrets, and limits.
- Sending Mail: sends, replies, forwards, idempotency, and gates.
- Receiving Mail: webhooks, retries, replays, and storage.
- SDKs: Node.js, Python, and Go examples.
- Collecting Payments: charge and pay USDC with x402 (invite-only soft launch).