# Use Stampwing with Node.js and Next.js

Build and install the local TypeScript SDK, send with a verified Test key, connect a server module, and add templates and signed webhooks.

Author: Stampwing
Canonical: https://www.stampwing.com/guides/stampwing-node-nextjs
Published: 2026-10-05
Reviewed: 2026-10-05
Category: Engineering

Install an actual SDK artifact and adapt a working Test integration to your own server application.

## Short answer

Section link: https://www.stampwing.com/guides/stampwing-node-nextjs#short-answer

Use @postrune/sdk in server-side Node.js code. Its exported client is Postrune. Supply the deployment origin and a scoped key, and persist one idempotency key per business event. The package names are compatibility identifiers, not a different product.

## Before you start

Section link: https://www.stampwing.com/guides/stampwing-node-nextjs#before-you-start

For: JavaScript and TypeScript developers using Node.js 22+ or a compatible Next.js App Router application.

Bring: A Stampwing source checkout for building the SDK, a separate consumer app, and the Test credential from the quickstart.

Scope: Registry publication is not assumed. If you have no checkout or package archive, use the dependency-free HTTP sender instead. Next.js snippets show backend integration, not a ready-made public send endpoint.

## 1. Build a local installable package

Section link: https://www.stampwing.com/guides/stampwing-node-nextjs#build-sdk

From the Stampwing repository root, install dependencies and create the SDK archive. npm pack runs the SDK’s prepack build and prints the archive filename. Copy its absolute path; do not install the private application root as your SDK.

Stampwing repository terminal

```shell
npm ci
npm pack ./packages/sdk
```

Note: The current archive is postrune-sdk-0.2.0.tgz. Use the filename printed by your checkout if its version differs. npm install @postrune/sdk is not a verified public installation path yet.

## 2. Install the archive in your own app

Section link: https://www.stampwing.com/guides/stampwing-node-nextjs#install-consumer

Switch to your application’s directory. If you are creating a new Node project, run npm init -y first. Use a .mjs file for the tutorial’s ES module imports; an existing CommonJS project does not need its package-wide module type changed. Replace the path below with your actual archive path; keep quotes around paths containing spaces.

The dependency listing should contain @postrune/sdk. Keep the archive available to builds or store it in your own controlled artifact registry; a developer’s absolute path is not a portable CI dependency. Test a clean install using the same artifact before deployment.

Consumer application terminal

```shell
npm install "/absolute/path/to/postrune-sdk-0.2.0.tgz"
npm ls @postrune/sdk
```

## 3. Send and inspect a Test message

Section link: https://www.stampwing.com/guides/stampwing-node-nextjs#sdk-test

Copy your private .env.stampwing from the quickstart into the consumer application folder and add .env.stampwing to that application’s .gitignore before copying the secret. The tutorial folder’s ignore rule does not protect a different folder. Run commands from the consumer folder and save this script there as send-test.mjs. The doctor call checks the key without sending and the guard refuses Live credentials or a different project. Keep POSTRUNE_EXPECTED_PROJECT_ID from the quickstart. Use a key with email:send and email:read.

send-test.mjs

```javascript
import { Postrune } from '@postrune/sdk';

const operationKey = process.env.POSTRUNE_OPERATION_KEY;
const apiKey = process.env.POSTRUNE_API_KEY;
const baseUrl = process.env.POSTRUNE_BASE_URL;
const expectedProjectId = process.env.POSTRUNE_EXPECTED_PROJECT_ID?.trim();
if (!operationKey || !apiKey || !baseUrl || !expectedProjectId) {
  throw new Error('Set the four POSTRUNE_ values from the quickstart.');
}
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(expectedProjectId)) {
  throw new Error('Set the expected project UUID from Settings.');
}
const client = new Postrune({ apiKey, baseUrl });
const check = await client.doctor();
if (!check.credential.valid || check.sent !== false || check.credential.environment !== 'test'
    || check.credential.projectId.toLowerCase() !== expectedProjectId.toLowerCase()) {
  throw new Error('This tutorial requires a verified Test credential for the expected project.');
}
for (const permission of ['email:send', 'email:read']) {
  if (!check.credential.permissions.includes(permission)) {
    throw new Error('Missing permission: ' + permission);
  }
}
const sent = await client.send({
  from: 'hello@postrune.test',
  to: 'reader@example.com',
  subject: 'Your Stampwing integration is ready',
  text: 'This is a simulated Test message. No email is delivered.',
}, { idempotencyKey: operationKey });
console.log(sent); // Preserve this UUID even if the following read fails.
try {
  console.log(await client.getEmail(sent.id));
} catch {
  console.error('Send accepted as ' + sent.id + '. Status lookup failed; do not submit another message.');
  process.exitCode = 2;
}
```

## 4. Run it and understand retries

Section link: https://www.stampwing.com/guides/stampwing-node-nextjs#execute

This example deliberately uses the exact quickstart payload. With the same project, Test key, and saved operation key, it reads back the same message UUID instead of creating another email. If you used an older or edited quickstart payload, recover that exact payload first. Do not change the key just to bypass a conflict.

Expect the same { id, status, mode: "demo" } acceptance shape as the HTTP quickstart, followed by current delivery metadata. The SDK’s ordinary send method makes one attempt and has a 15-second deadline. A timeout is unconfirmed, not evidence of failure.

For a genuinely new authorized business event, save its new key and frozen content before the first send. If you choose sendWithRetry, supply that saved key and a bounded retry budget. Never generate a fresh key in each job attempt. PostruneError carries status, code, requestId, issues, and retryAfterMs when available; network failures can occur before a structured response exists.

Consumer application terminal

```shell
node --env-file=.env.stampwing send-test.mjs
```

## 5. Keep the Next.js integration on the server

Section link: https://www.stampwing.com/guides/stampwing-node-nextjs#nextjs-server

In Next.js, put POSTRUNE_API_KEY, POSTRUNE_BASE_URL, and POSTRUNE_EXPECTED_PROJECT_ID in your private .env.local or deployment secret settings and restart the app after changing them. The standalone Node script uses --env-file; Next.js loads .env.local itself. Install server-only in the consumer app with npm install server-only if needed.

Save the following helper in src/lib/stampwing.ts (or lib/stampwing.ts if your app has no src directory). Call it from an authenticated server action, route handler, or durable worker only after your app authorizes the business event. The caller must supply a recipient from trusted application data and an operation key saved with that event.

src/lib/stampwing.ts — Test integration helper

```typescript
import 'server-only';
import { Postrune } from '@postrune/sdk';

export async function sendWelcomeTest(event: {
  recipient: string; savedOperationKey: string;
}) {
  const apiKey = process.env.POSTRUNE_API_KEY;
  const baseUrl = process.env.POSTRUNE_BASE_URL;
  const expectedProjectId = process.env.POSTRUNE_EXPECTED_PROJECT_ID?.trim();
  if (!apiKey || !baseUrl || !expectedProjectId) throw new Error('Stampwing is not configured.');
  const client = new Postrune({ apiKey, baseUrl });
  const check = await client.doctor();
  if (!check.credential.valid || check.sent !== false || check.credential.environment !== 'test'
      || check.credential.projectId.toLowerCase() !== expectedProjectId.toLowerCase()
      || !check.credential.permissions.includes('email:send')) {
    throw new Error('Use a Test sending credential for the expected project.');
  }
  return client.send({
    from: 'hello@postrune.test', to: event.recipient,
    subject: 'Welcome to your app',
    text: 'Your Test integration is connected. Delivery is simulated.',
  }, { idempotencyKey: event.savedOperationKey });
}
```

Note: Do not expose this as an unauthenticated send-any-email API. Keep authorization, validation, CSRF protection where applicable, and application rate limiting around the caller. In App Router code, await request.json() and asynchronous params; use the Node.js runtime. The helper intentionally refuses Live keys.

## 6. Use a published template

Section link: https://www.stampwing.com/guides/stampwing-node-nextjs#templates

1. Create a template in the same project and Test environment, declare its variables, and preview it using synthetic values.
2. Publish a version and copy its version UUID. Draft IDs and version IDs are different; a send pins the published version.
3. Give the sending key email:send and templates:use. Keep email:read if you also read progress.
4. Replace raw subject/text/html with templateVersionId and variables. A request cannot mix the two forms.

After creating client and checking Test scope as above

```javascript
const templateVersionId = process.env.POSTRUNE_TEMPLATE_VERSION;
if (!templateVersionId) throw new Error('Set the published POSTRUNE_TEMPLATE_VERSION UUID.');
const result = await client.send({
  from: 'hello@postrune.test', to: 'reader@example.com',
  templateVersionId,
  variables: { name: 'Ada' },
}, { idempotencyKey: 'template-tutorial:welcome:001' });
```

Note: The variables must match your template schema. This example assumes a declared name variable. An existing version remains immutable when you publish a newer one; store the chosen version with the business event.

## 7. Verify webhook bytes before applying changes

Section link: https://www.stampwing.com/guides/stampwing-node-nextjs#webhooks

Create an HTTPS endpoint in Webhooks when your installation supports it. Store its signing secret on your server separately from the API key. Verify the original request text before parsing or handling the event. A JSON parser that runs first can change the bytes and break signature verification.

Return a non-2xx response if verification or durable storage fails. Expect duplicate and out-of-order events. A webhook replay repeats a notification; it does not resend the email. Your local loopback listener is not reachable from a hosted service. For an account-free exercise, use the signed local receiver tutorial.

Handler fragment — request and durable storage come from your application

```typescript
import { verifyWebhook } from '@postrune/sdk';

const secret = process.env.POSTRUNE_WEBHOOK_SECRET;
if (!secret) throw new Error('Webhook verification is not configured.');
const rawBody = await request.text();
const event = await verifyWebhook(rawBody, request.headers, secret);
// Persist the verified event identity and intended work atomically.
// Only then acknowledge the request with a 2xx response.
// Apply business effects asynchronously and deduplicate by event identity.
```

[Build and test a durable webhook receiver](https://www.stampwing.com/guides/reliable-email-webhooks)

## 8. Use HTTP or another language

Section link: https://www.stampwing.com/guides/stampwing-node-nextjs#other-languages

HTTP works from any backend language: the request body, Bearer header, idempotency rules, and response contract are the same. The repository also contains Python, Go, PHP, Ruby, Java, .NET, Rust, and Elixir source clients. Read docs/integrations/native-sdks.md and the relevant packages/*-sdk/README.md for that language’s build commands. Those source clients are not proof of registry publication.

[Read the HTTP fields and permissions](https://www.stampwing.com/guides/stampwing-api-reference)

## Sources

Section link: https://www.stampwing.com/guides/stampwing-node-nextjs#sources

- [Stampwing API contract (implemented interfaces; deployment gates still apply)](https://www.stampwing.com/reference/openapi.json)

Developer documentation: https://www.stampwing.com/docs

Next step: [Prepare for live sending](https://www.stampwing.com/guides/stampwing-go-live)

## Related guides

- https://www.stampwing.com/guides/stampwing-quickstart
- https://www.stampwing.com/guides/stampwing-api-reference
- https://www.stampwing.com/guides/reliable-email-webhooks

Editorial policy: https://www.stampwing.com/resources/editorial
