Signed Download Tokens
Generate short-lived, HMAC-signed tokens that scope a download URL to one email's raw MIME bytes or attachment bundle, and verify them before serving the content.
Signed download tokens let you mint a short-lived, HMAC-signed token that scopes access to one email's raw bytes or attachment bundle, so you can hand out a fetchable link without exposing your webhook secret. Use them when a browser or downstream service needs to retrieve that content later from a route you serve.
Both helpers are exported from the webhook subpath of @primitivedotdev/sdk, the SDK entry point that also carries signature verification:
import {
generateDownloadToken,
verifyDownloadToken,
} from "@primitivedotdev/sdk/webhook";
generateDownloadToken takes a GenerateDownloadTokenOptions object and verifyDownloadToken takes VerifyDownloadTokenOptions and returns a VerifyDownloadTokenResult. All three types are exported alongside the functions, so your editor shows the exact required fields; the SDK is the authoritative reference for them.
import type {
GenerateDownloadTokenOptions,
VerifyDownloadTokenOptions,
VerifyDownloadTokenResult,
} from "@primitivedotdev/sdk/webhook";
Checking whether an inbound email's raw content arrived inline or must be fetched from Primitive's own download URL is a different job. See Parsing Raw Email (.eml) for isRawIncluded and decodeRawEmail. Download tokens are for URLs you mint and serve yourself.
Mint and verify a token#
Generate the token in the code that already holds the email and the secret, then verify it in the route that serves the bytes.
- 1
Generate the token where you have the email and the secret#
// server-side: any module that already has the inbound email in hand import { generateDownloadToken } from "@primitivedotdev/sdk/webhook"; const token = generateDownloadToken({ emailId: email.id, secret: process.env.PRIMITIVE_WEBHOOK_SECRET!, });Fill in the remaining fields from
GenerateDownloadTokenOptions; your editor lists them and marks which are required. - 2
Embed the token in a URL your own route serves#
const downloadUrl = `https://yourapp.example.com/downloads/${email.id}?token=${token}`;Hand this URL to whatever needs the content later. The token, not the path, is what gates access.
- 3
Verify before serving any bytes#
// app/downloads/[emailId]/route.ts (Next.js route handler) import { verifyDownloadToken } from "@primitivedotdev/sdk/webhook"; export const runtime = "nodejs"; export async function GET( req: Request, { params }: { params: { emailId: string } }, ) { const token = new URL(req.url).searchParams.get("token") ?? ""; const result = verifyDownloadToken({ token, emailId: params.emailId, secret: process.env.PRIMITIVE_WEBHOOK_SECRET!, }); // Inspect `result` (a VerifyDownloadTokenResult) and reject before you // read anything off disk or call Primitive's download endpoint. // ... stream the raw bytes or attachment bundle here ... }
Verify on every request. Do not cache a "verified once" flag for a token past its lifetime, and do not skip the email-id check to serve a multi-email download page. A token is scoped to a single email; mint one per email.
Both functions take the secret directly, so they work outside a receive() call, for example in a standalone download route that never sees the original inbound webhook request. Use the same PRIMITIVE_WEBHOOK_SECRET you use for webhook signature verification.
Next steps#
Decode raw MIME bytes and extract attachments once you've verified access.
Webhook Signature VerificationVerify inbound webhook deliveries with the same PRIMITIVE_WEBHOOK_SECRET.
Bundling AttachmentsBuild the content-addressed attachment bundle a token can scope access to.
Receiving Inbound EmailNormalize the inbound webhook into a ReceivedEmail before minting a token for it.
Was this page helpful?