{"schema_version":"1.0","publisher":"Primitive SDKs","canonical_url":"https://test.abhinandan.one/quickstart-369b6942","markdown_url":"https://test.abhinandan.one/quickstart-369b6942.md","article":{"id":"fbea3525-a37d-403d-b620-d500947e1940","article_slug":"quickstart-369b6942","parent_article_slug":null,"parent_article_title":null,"kind":"quickstart","published_at":"2026-08-11T18:18:13.022006+00:00","keywords":["primitive.receive","primitive.client","PRIMITIVE_API_KEY","PRIMITIVE_WEBHOOK_SECRET","npm install @primitivedotdev/sdk","npm install -g primitive"],"meta_description":"Install @primitivedotdev/sdk, primitivedotdev, sdk-go, or the primitive CLI, set an API key, then receive and reply to an inbound email.","og_image_url":null,"source_file_paths":["README.md","sdk-node/README.md","sdk-python/README.md","sdk-go/README.md","cli-node/README.md"],"recording_id":null,"replayable":false,"task_name":"Quickstart","category":"Getting Started","summary":null,"description":"Install a Primitive SDK or the CLI, set your API key, and receive, reply to, and send your first email in minutes.","content_kind":"repo_page","content_markdown":"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.\n\nYou need a Primitive API key (`prim_...`) from your dashboard, and, if you're receiving inbound mail, a webhook secret (`whsec_...`) from the same account.\n\n<Tip>\n\nBuilding with an AI coding agent? Point it at the [Agent Guide](agent-guide) instead, it's a denser, single-page reference built for that workflow.\n\n</Tip>\n\n<Steps>\n\n<Step title=\"Install\">\n\nPick your language.\n\n<Tabs>\n\n<Tab title=\"Node.js\">\n\nRequires Node.js 22 or newer.\n\n```bash\nnpm install @primitivedotdev/sdk\n```\n\n</Tab>\n\n<Tab title=\"Python\">\n\nRequires Python 3.10 or newer.\n\n```bash\npip install primitivedotdev\n```\n\n</Tab>\n\n<Tab title=\"Go\">\n\nRequires Go 1.25 or newer.\n\n```bash\ngo get github.com/primitivedotdev/sdks/sdk-go@latest\n```\n\n</Tab>\n\n<Tab title=\"CLI\">\n\n```bash\nnpm install -g primitive\n# or, no install:\nnpx primitive@latest <command>\n```\n\nThe CLI is a separate package from the Node SDK, `@primitivedotdev/sdk` no longer ships a `primitive` bin. Use the CLI for terminal/CI workflows; use an SDK when embedding Primitive in application code.\n\n</Tab>\n\n</Tabs>\n\n</Step>\n\n<Step title=\"Set your API key\">\n\nGet a key from your dashboard and export it as an environment variable. Every example on this page reads it from there.\n\n```bash\nexport PRIMITIVE_API_KEY=prim_test\n```\n\nIf you're receiving inbound mail, also export your webhook secret (used to verify the `Primitive-Signature` header):\n\n```bash\nexport PRIMITIVE_WEBHOOK_SECRET=whsec_...\n```\n\n<Tip>\n\nUsing the CLI instead? Skip the env var and authenticate interactively with `primitive login`, then confirm with `primitive whoami`. See [Authentication: login, signup, logout, whoami](cli-authentication) for the full sign-in flow.\n\n</Tip>\n\n</Step>\n\n<Step title=\"Receive and reply\">\n\nThis 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](email-model) for what's on that object and how reply threading works.\n\n<Tabs>\n\n<Tab title=\"Node.js\">\n\nA Next.js route handler that receives inbound mail and replies:\n\n```ts\nimport primitive from \"@primitivedotdev/sdk\";\n\nexport const runtime = \"nodejs\";\nexport const maxDuration = 300;\n\nconst client = primitive.client({\n  apiKey: process.env.PRIMITIVE_API_KEY!,\n});\n\nexport async function POST(req: Request) {\n  const email = await primitive.receive(req, {\n    secret: process.env.PRIMITIVE_WEBHOOK_SECRET!,\n  });\n\n  await client.reply(email, \"Thank you for your email.\");\n\n  return Response.json({ ok: true });\n}\n```\n\n`primitive.receive(...)` reads the request body, verifies the HMAC-SHA256 signature against your account secret, and returns a normalized [ReceivedEmail](email-model). `client.reply(email, ...)` derives threading and the `Re:` subject from the parent message server-side, you never set them yourself.\n\n</Tab>\n\n<Tab title=\"Python\">\n\n```python\nimport primitive\n\nclient = primitive.client(api_key=\"prim_test\")\n\n\ndef webhook_handler(body: bytes, headers: dict[str, str]) -> dict[str, object]:\n    email = primitive.receive(\n        body=body,\n        headers=headers,\n        secret=\"whsec_...\",\n    )\n\n    client.reply(email, \"Thank you for your email.\")\n    return {\"ok\": True}\n```\n\n</Tab>\n\n<Tab title=\"Go\">\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"log\"\n\n\tprimitive \"github.com/primitivedotdev/sdks/sdk-go\"\n)\n\nfunc handle(ctx context.Context, body []byte, headers map[string]string) {\n\temail, err := primitive.Receive(primitive.HandleWebhookOptions{\n\t\tBody:    body,\n\t\tHeaders: headers,\n\t\tSecret:  \"whsec_...\",\n\t})\n\tif err != nil {\n\t\tlog.Printf(\"invalid webhook: %v\", err)\n\t\treturn\n\t}\n\n\tclient, err := primitive.NewClient(\"prim_test\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t_, err = client.Reply(ctx, email, primitive.ReplyParams{BodyText: \"Thank you for your email.\"})\n\tif err != nil {\n\t\tlog.Printf(\"reply failed: %v\", err)\n\t}\n}\n```\n\n</Tab>\n\n<Tab title=\"CLI\">\n\nThe CLI's task-oriented commands cover the same send/reply flow without any code:\n\n```bash\nprimitive emails latest --limit 5\nprimitive reply --id <inbound-email-id> --body \"Thanks!\"\n```\n\nSee [Sending, Replying, and Searching Email from the CLI](cli-email-commands) for the full command set.\n\n</Tab>\n\n</Tabs>\n\n**Expected result:** the handler returns `{\"ok\": true}` (Node) or its language equivalent, and the sender receives a reply threaded under the original message (same `References`, subject prefixed `Re:`).\n\n</Step>\n\n<Step title=\"Send a new email\">\n\nOutbound mail that isn't a reply uses `send` instead. By default `send` returns as soon as Primitive accepts the message; pass `wait: true` to use [wait mode](email-model) and get the terminal SMTP delivery status before your handler returns.\n\n<Tabs>\n\n<Tab title=\"Node.js\">\n\n```ts\nimport primitive from \"@primitivedotdev/sdk\";\n\nconst client = primitive.client({\n  apiKey: process.env.PRIMITIVE_API_KEY!,\n});\n\nconst result = await client.send({\n  from: \"Support <support@example.com>\",\n  to: \"alice@example.com\",\n  subject: \"Hello\",\n  bodyText: \"Hi there\",\n  wait: true,\n  waitTimeoutMs: 5000,\n});\n\nconsole.log(result.id, result.status, result.queueId, result.deliveryStatus);\n```\n\n</Tab>\n\n<Tab title=\"Python\">\n\n```python\nimport primitive\n\nclient = primitive.client(api_key=\"prim_test\")\n\nresult = client.send(\n    from_email=\"Support <support@example.com>\",\n    to=\"alice@example.com\",\n    subject=\"Hello\",\n    body_text=\"Hi there\",\n    wait=True,\n    wait_timeout_ms=5000,\n)\n\nprint(result.id, result.status, result.queue_id, result.delivery_status)\n```\n\n</Tab>\n\n<Tab title=\"Go\">\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"os\"\n\t\"time\"\n\n\tprimitive \"github.com/primitivedotdev/sdks/sdk-go\"\n)\n\nfunc main() {\nclient, err := primitive.NewClient(os.Getenv(\"PRIMITIVE_API_KEY\"))\nif err != nil {\n\tlog.Fatal(err)\n}\n\nctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)\ndefer cancel()\nwait := true\n\nresult, err := client.Send(ctx, primitive.SendParams{\n\tFrom:          \"Support <support@example.com>\",\n\tTo:            \"alice@example.com\",\n\tSubject:       \"Hello\",\n\tBodyText:      \"Hi there\",\n\tWait:          &wait,\n\tWaitTimeoutMs: 5000,\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nlog.Println(result.ID, result.Status, result.DeliveryStatus)\n}\n```\n\n</Tab>\n\n<Tab title=\"CLI\">\n\n```bash\nprimitive send --to alice@example.com --body \"Hello!\" --wait\n```\n\n</Tab>\n\n</Tabs>\n\n**Verify:** check `result.status` / `result.id` in the response, or run `primitive emails latest` (CLI) / your inbox provider's sent-mail view to confirm delivery.\n\n</Step>\n\n</Steps>\n\n## Escape hatches\n\n<Tip>\n\nFramework 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](node-sdk-receiving-email).\n\n</Tip>\n\n<Tip>\n\nNeed the full generated HTTP API (Memories, semantic search, account management)? Reach for the [generated API client](python-generated-api-client) instead of the high-level `send`/`reply`/`forward` surface.\n\n</Tip>\n\n<Tip>\n\nAlready 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](python-sdk-quickstart) or [Go SDK Quickstart](go-sdk-quickstart) for the language-specific deep dive.\n\n</Tip>\n\n## Next steps\n\n<CardGroup cols={2}>\n\n<Card title=\"Inbound and Outbound Email Model\" href=\"email-model\">\n\nLearn the normalized email object, wait-mode delivery statuses, and the reply/forward threading rules used above.\n\n</Card>\n\n<Card title=\"Node.js SDK Quickstart\" href=\"node-sdk-quickstart\">\n\nGo deeper on the Node SDK: subpath exports, request options, and the full receive-and-reply route handler.\n\n</Card>\n\n<Card title=\"Webhook Events Overview\" href=\"webhook-events\">\n\nUnderstand the shared webhook contract, signature verification, event types, and forward compatibility.\n\n</Card>\n\n<Card title=\"Agent Guide\" href=\"agent-guide\">\n\nA dense single-page reference for AI coding agents integrating Primitive across any language.\n\n</Card>\n\n</CardGroup>","canonical_base_url":"https://test.abhinandan.one","seo_indexing_enabled":true,"last_modified":"2026-08-21T18:22:43.359885+00:00","video_url":null,"voiceover_url":null,"tools_used":[],"demonstrated_by":[],"steps":[],"related_links":[],"intro":null,"prerequisites":[],"verification":[],"troubleshooting":[],"suggest_edit_url":"https://github.com/abhi-browzer/primitive-sdks/edit/main/docs/quickstart.md","raise_issue_url":"https://github.com/abhi-browzer/primitive-sdks/issues/new?title=Docs+feedback%3A+Quickstart&body=Page%3A+https%3A%2F%2Ftest.abhinandan.one%2Fquickstart-369b6942","page_feedback_enabled":true,"verified_ref":null,"verified_at":"2026-08-11T18:38:45.205849+00:00"}}