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 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, 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 (requires send_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 _agents record (requires send_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).