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 @primitivedotdev/cli
primitive login
primitive whoami

If you cannot install globally, run commands with npx @primitivedotdev/cli@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 @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 whoami

5. 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 and auto-registers an inbound endpoint for the organization. Save the returned 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 you@example.com \
  --body "Hello from Primitive" \
  --wait

The --wait flag blocks until Primitive has a delivery result from the receiving side.

8. Understand First-Send Gates

New accounts can send to:

  • addresses on *.primitive.email;
  • addresses on domains you have verified;
  • email addresses that belong to members of your Primitive organization;
  • external addresses that have already sent you authenticated mail, when replying to known addresses is allowed;
  • 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 and fix.action fields. See Sending Mail for the full model.

9. Receive an Email

Send a test message to any address at your managed subdomain, for example hi@your-org.primitive.email.

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.