---
title: "Quickstart"
description: "Sign up, scaffold a Function, send mail, receive mail, and reply using the managed *.primitive.email domain."
canonical: "https://docs.primitive.dev/docs/quickstart"
last-updated: "2026-05-17T00:00:00.000Z"
---

# 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](https://www.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 -> Credentials**, choose **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=<opaque-base64-secret>
```


## 4. Verify API Access


```
primitive whoami
```

```
curl https://api.primitive.dev/v1/account \
  -H "Authorization: Bearer $PRIMITIVE_API_KEY"
```


## 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
```

```
CODE=$(cat dist/handler.js | jq -Rs .)


curl -X POST https://api.primitive.dev/v1/functions \
  -H "Authorization: Bearer $PRIMITIVE_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"name\":\"my-fn\",\"code\":$CODE}"
```


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
```

```
curl -X PUT https://api.primitive.dev/v1/functions/<function-id>/route \
  -H "Authorization: Bearer $PRIMITIVE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"target":{"kind":"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 you@example.com \
  --body "Hello from Primitive" \
  --wait
```

```
curl -X POST https://api.primitive.dev/v1/send-mail \
  -H "Authorization: Bearer $PRIMITIVE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "hello@your-org.primitive.email",
    "to": "you@example.com",
    "subject": "Hello from Primitive",
    "body_text": "It works.",
    "wait": true
  }'
```


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:

- any address on a domain you have added and verified ownership of (no entitlement needed: verifying a domain lets you send both from it and to it);

- 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 at domains on your account whose ownership is not verified (requires `send_to_confirmed_domains`);

- 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](/docs/sending) 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>
```

```
curl "https://api.primitive.dev/v1/emails?limit=5" \
  -H "Authorization: Bearer $PRIMITIVE_API_KEY"


curl https://api.primitive.dev/v1/emails/<email-id> \
  -H "Authorization: Bearer $PRIMITIVE_API_KEY"
```


## 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."
```

```
curl -X POST https://api.primitive.dev/v1/emails/<inbound-email-id>/reply \
  -H "Authorization: Bearer $PRIMITIVE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"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](/docs/functions): handler shape, deploys, tests, logs, secrets, and limits.

- [Sending Mail](/docs/sending): sends, replies, forwards, idempotency, and gates.

- [Receiving Mail](/docs/receiving): webhooks, retries, replays, and storage.

- [SDKs](/docs/sdks): Node.js, Python, and Go examples.

Maintained by the [primitive.dev team](/about) · [Trust & security](/trust)Last updated: May 17, 2026
