Quickstart
Install a Primitive SDK or the CLI, set your API key, and receive, reply to, and send your first email in minutes.
Primitive is an inbound and outbound email API. This page gets you from a fresh install to a working receive-and-reply loop, using whichever SDK matches your stack, or the CLI if you'd rather work from the terminal.
You need a Primitive API key (prim_...) from your dashboard, and, if you're receiving inbound mail, a webhook secret (whsec_...) from the same account.
Building with an AI coding agent? Point it at the Agent Guide instead, it's a denser, single-page reference built for that workflow.
- 1
Install#
Pick your language.
Requires Node.js 22 or newer.
npm install @primitivedotdev/sdkRequires Python 3.10 or newer.
pip install primitivedotdevRequires Go 1.25 or newer.
go get github.com/primitivedotdev/sdks/sdk-go@latestnpm install -g primitive # or, no install: npx primitive@latest <command>The CLI is a separate package from the Node SDK,
@primitivedotdev/sdkno longer ships aprimitivebin. Use the CLI for terminal/CI workflows; use an SDK when embedding Primitive in application code. - 2
Set your API key#
Get a key from your dashboard and export it as an environment variable. Every example on this page reads it from there.
export PRIMITIVE_API_KEY=prim_testIf you're receiving inbound mail, also export your webhook secret (used to verify the
Primitive-Signatureheader):export PRIMITIVE_WEBHOOK_SECRET=whsec_...TipUsing the CLI instead? Skip the env var and authenticate interactively with
primitive login, then confirm withprimitive whoami. See Authentication: login, signup, logout, whoami for the full sign-in flow. - 3
Receive and reply#
This is the core loop every SDK is built around: normalize an inbound webhook into a
ReceivedEmail, then reply to it. See the Inbound and Outbound Email Model for what's on that object and how reply threading works.A Next.js route handler that receives inbound mail and replies:
import primitive from "@primitivedotdev/sdk"; export const runtime = "nodejs"; export const maxDuration = 300; const client = primitive.client({ apiKey: process.env.PRIMITIVE_API_KEY!, }); export async function POST(req: Request) { const email = await primitive.receive(req, { secret: process.env.PRIMITIVE_WEBHOOK_SECRET!, }); await client.reply(email, "Thank you for your email."); return Response.json({ ok: true }); }primitive.receive(...)reads the request body, verifies the HMAC-SHA256 signature against your account secret, and returns a normalized ReceivedEmail.client.reply(email, ...)derives threading and theRe:subject from the parent message server-side, you never set them yourself.import primitive client = primitive.client(api_key="prim_test") def webhook_handler(body: bytes, headers: dict[str, str]) -> dict[str, object]: email = primitive.receive( body=body, headers=headers, secret="whsec_...", ) client.reply(email, "Thank you for your email.") return {"ok": True}package main import ( "context" "log" primitive "github.com/primitivedotdev/sdks/sdk-go" ) func handle(ctx context.Context, body []byte, headers map[string]string) { email, err := primitive.Receive(primitive.HandleWebhookOptions{ Body: body, Headers: headers, Secret: "whsec_...", }) if err != nil { log.Printf("invalid webhook: %v", err) return } client, err := primitive.NewClient("prim_test") if err != nil { log.Fatal(err) } _, err = client.Reply(ctx, email, primitive.ReplyParams{BodyText: "Thank you for your email."}) if err != nil { log.Printf("reply failed: %v", err) } }The CLI's task-oriented commands cover the same send/reply flow without any code:
primitive emails latest --limit 5 primitive reply --id <inbound-email-id> --body "Thanks!"See Sending, Replying, and Searching Email from the CLI for the full command set.
Expected result: the handler returns
{"ok": true}(Node) or its language equivalent, and the sender receives a reply threaded under the original message (sameReferences, subject prefixedRe:). - 4
Send a new email#
Outbound mail that isn't a reply uses
sendinstead. By defaultsendreturns as soon as Primitive accepts the message; passwait: trueto use wait mode and get the terminal SMTP delivery status before your handler returns.import primitive from "@primitivedotdev/sdk"; const client = primitive.client({ apiKey: process.env.PRIMITIVE_API_KEY!, }); const result = await client.send({ from: "Support <support@example.com>", to: "alice@example.com", subject: "Hello", bodyText: "Hi there", wait: true, waitTimeoutMs: 5000, }); console.log(result.id, result.status, result.queueId, result.deliveryStatus);import primitive client = primitive.client(api_key="prim_test") result = client.send( from_email="Support <support@example.com>", to="alice@example.com", subject="Hello", body_text="Hi there", wait=True, wait_timeout_ms=5000, ) print(result.id, result.status, result.queue_id, result.delivery_status)package main import ( "context" "log" "os" "time" primitive "github.com/primitivedotdev/sdks/sdk-go" ) func main() { client, err := primitive.NewClient(os.Getenv("PRIMITIVE_API_KEY")) if err != nil { log.Fatal(err) } ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() wait := true result, err := client.Send(ctx, primitive.SendParams{ From: "Support <support@example.com>", To: "alice@example.com", Subject: "Hello", BodyText: "Hi there", Wait: &wait, WaitTimeoutMs: 5000, }) if err != nil { log.Fatal(err) } log.Println(result.ID, result.Status, result.DeliveryStatus) }primitive send --to alice@example.com --body "Hello!" --waitVerify: check
result.status/result.idin the response, or runprimitive emails latest(CLI) / your inbox provider's sent-mail view to confirm delivery.
Escape hatches#
Framework has no standard Request object? Use the lower-level receive({ body, headers, secret }) form (Node) or the equivalent handle_webhook (Python) / primitive.Receive (Go) call directly, see Receiving Inbound Email.
Need the full generated HTTP API (Memories, semantic search, account management)? Reach for the generated API client instead of the high-level send/reply/forward surface.
Already picked Python or Go for your stack? All three SDKs implement the identical inbound/outbound model, pick by language, not by capability gap. See Python SDK Quickstart or Go SDK Quickstart for the language-specific deep dive.
Next steps#
Learn the normalized email object, wait-mode delivery statuses, and the reply/forward threading rules used above.
Node.js SDK QuickstartGo deeper on the Node SDK: subpath exports, request options, and the full receive-and-reply route handler.
Webhook Events OverviewUnderstand the shared webhook contract, signature verification, event types, and forward compatibility.
Agent GuideA dense single-page reference for AI coding agents integrating Primitive across any language.
Was this page helpful?