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. 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#
npm install @primitivedotdev/sdk export PRIMITIVE_WEBHOOK_SECRET=whsec_... - 2
Call primitive.receive with the request and your secret#
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 aReceivedEmail. 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 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" }).
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.
Only need to verify a signature without normalizing the payload? Use verifyWebhookSignature from @primitivedotdev/sdk/webhook directly. See Webhook Signature Verification.
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:
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. |
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 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.
Hand email straight to client.reply(email, ...) or client.forward(email, ...); see Sending, Replying, and Forwarding Email 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.
Next steps#
Reply to or forward the ReceivedEmail you just normalized.
Verifying Inbound Email AuthenticityDecide whether to trust the sender before acting on the email.
Webhook Signature VerificationVerify the Primitive-Signature header manually when you don't have a standard Request.
Node.js SDK ErrorsLook up WebhookVerificationError, WebhookPayloadError, and WebhookValidationError codes.
Was this page helpful?