---
title: "Receiving Inbound Email"
canonical: "https://test.abhinandan.one/node-sdk-receiving-email"
markdown_url: "https://test.abhinandan.one/node-sdk-receiving-email.md"
publisher: "Primitive SDKs"
kind: "guide"
content_type: "reference"
category: "Node.js SDK"
description: "primitive.receive() verifies the webhook signature and returns a normalized ReceivedEmail with sender, replyTarget, thread, and raw fields."
keywords: ["primitive.receive", "ReceivedEmail", "@primitivedotdev/sdk/webhook", "normalizeReceivedEmail", "email.raw", "receive inbound webhook"]
last_modified: "2026-08-21T18:22:43.359885+00:00"
published_at: "2026-08-11T18:55:07.831644+00:00"
source_files:
  - "sdk-node/README.md"
  - "sdk-node/src/webhook/index.ts"
sections:
  - {anchor: "receive-from-a-standard-request", title: "Receive from a standard `Request`"}
  - {anchor: "step-install-the-sdk-and-set-your-webhook-secret", title: "Install the SDK and set your webhook secret"}
  - {anchor: "step-call-primitivereceive-with-the-request-and-your-secret", title: "Call primitive.receive with the request and your secret"}
  - {anchor: "step-verify-the-result", title: "Verify the result"}
  - {anchor: "receive-from-a-raw-body-and-headers", title: "Receive from a raw body and headers"}
  - {anchor: "the-receivedemail-shape", title: "The ReceivedEmail shape"}
  - {anchor: "next-steps", title: "Next steps"}
---

> Documentation index: https://test.abhinandan.one/llms.txt

# Receiving Inbound Email

Turn a raw inbound webhook delivery into a normalized ReceivedEmail object with primitive.receive, ready to pass straight into client.reply or client.forward.

`primitive.receive(...)`, the root export of `@primitivedotdev/sdk`, verifies an inbound webhook delivery and returns a `ReceivedEmail`: a normalized object with a stable shape you can act on immediately, instead of the raw [`email.received` event](https://test.abhinandan.one/webhook-events.md). Call it at the top of any handler that receives inbound mail, whether that's a Next.js route, an Express endpoint, or a Primitive Function.

## Receive from a standard `Request`

Pass the `Request` straight to `primitive.receive` when your framework hands you a Fetch API `Request` object, as Next.js App Router and Cloudflare Workers do. This overload is async.

### 1. Install the SDK and set your webhook secret

```bash
npm install @primitivedotdev/sdk
export PRIMITIVE_WEBHOOK_SECRET=whsec_...
```

### 2. Call primitive.receive with the request and your secret

```ts
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 });
}
```

### 3. Verify the result

`primitive.receive(...)` reads the request body, verifies the HMAC-SHA256 signature against your account secret, and resolves to a `ReceivedEmail`. It rejects a delivery whose timestamp is more than 300 seconds off your clock, and a tampered or expired delivery throws instead of returning. See [Node.js SDK Errors](https://test.abhinandan.one/node-sdk-errors.md) for the error types and what triggers each one.

## Receive from a raw body and headers

Pass `{ body, headers, secret }` when your framework doesn't expose a standard `Request` object, for example a plain Node HTTP handler or Express with `express.raw({ type: "application/json" })`.

```ts
import primitive from "@primitivedotdev/sdk";

const email = primitive.receive({
  body: req.body, // string or Buffer, exact bytes as received
  headers: req.headers,
  secret: process.env.PRIMITIVE_WEBHOOK_SECRET!,
});
```

This overload is synchronous and returns a `ReceivedEmail` directly, not a `Promise`. `body` must be the exact bytes of the HTTP request before any JSON parsing; a body that has been parsed and re-serialized will fail signature verification on insignificant whitespace alone.

> **Tip:** Only need to verify a signature without normalizing the payload? Use `verifyWebhookSignature` from `@primitivedotdev/sdk/webhook` directly. See [Webhook Signature Verification](https://test.abhinandan.one/node-sdk-webhook-signing.md).

## The ReceivedEmail shape

`ReceivedEmail` is the flat, normalized shape `primitive.receive(...)` returns, with the sender, recipient, subject, body, and threading fields promoted to the top level:

```ts
email.sender.address;
email.sender.name;

email.receivedBy;
email.receivedByAll;

email.replyTarget.address;
email.replySubject;
email.forwardSubject;

email.subject;
email.text;

email.thread.messageId;
email.thread.references;

email.raw;
```

| Field | Description |
| --- | --- |
| `sender.address`, `sender.name` | The From address, parsed leniently for display. Falls back to the SMTP envelope sender when the header can't be parsed, so it is not a safe authorization anchor. See [Verifying Inbound Email Authenticity](https://test.abhinandan.one/node-sdk-email-authenticity.md). |
| `receivedBy` | The recipient address this email was received on. |
| `receivedByAll` | Every recipient address on the delivery. |
| `replyTarget.address` | The Reply-To address when the inbound email carried one, otherwise the sender. Fully sender-controlled, so never authorize on it. |
| `replySubject` | The `Re: <parent>` subject `client.reply` uses. |
| `forwardSubject` | The `Fwd: <parent>` subject `client.forward` uses. |
| `subject` | The original inbound subject line. |
| `text` | The plain-text body, when present. |
| `thread.messageId`, `thread.references` | Threading headers used to derive `In-Reply-To` and `References` on replies. |
| `raw` | The full, schema-validated [`email.received` event](https://test.abhinandan.one/webhook-events.md) this object was normalized from. |

Use `email.raw` whenever you need something outside the normalized shape: the original headers, SPF/DKIM/DMARC results, attachment metadata, or the raw MIME download URL.

> **Note:** Hand `email` straight to `client.reply(email, ...)` or `client.forward(email, ...)`; see [Sending, Replying, and Forwarding Email](https://test.abhinandan.one/node-sdk-sending-email.md) for both. Recipients, subject, and threading headers on a reply are derived server-side from the inbound row the email's id points to, not recomputed client-side.
