Volter World

Test transactional email locally

Send a transactional email with Resend's real Node SDK, inspect its content locally and resume the same message after stopping the World. This example needs Node 22.6 or newer, npm and registry access during installation. It needs no Resend account, real API key or platform login. No email leaves the World or reaches a real mailbox.

Start in an empty directory. Save the following files there.

Install and select the twin

package.json
{
  "name": "local-email-example",
  "private": true,
  "type": "module",
  "dependencies": { "resend": "6.6.0" },
  "devDependencies": {
    "@volter/world": "3.0.69",
    "@volter/twin-resend": "1.0.1"
  }
}
.env.example
RESEND_API_KEY=
npm install
npx volter world init --name local-email-example --twins resend --source resend=@volter/twin-resend

Review the selected vendor and .volter/world.json: the service should name @volter/twin-resend version 1.0.1. The World supplies a throwaway API key. Commit the config and lockfile to keep that choice explicit. Choose a twin explains how to compare a release's supported operations and exercised coverage.

Send with the unchanged SDK

The twin's default account owns [email protected]. Like Resend's testing domain, resend.dev sends only to the account owner. Use that synthetic recipient for this example. An app using its own sender domain needs a verified domain in the World; the release's README describes its local DNS setup.

send-email.mjs
import assert from 'node:assert/strict';
import { writeFileSync } from 'node:fs';
import { Resend } from 'resend';

assert.ok(process.env.VOLTER_WORLD, 'run this script through the World');
const resend = new Resend(process.env.RESEND_API_KEY);
const email = {
  from: 'Example app <[email protected]>',
  to: ['[email protected]'],
  subject: 'Your local sign-in code',
  text: 'Your synthetic sign-in code is 123456.',
  html: '<p>Your synthetic sign-in code is <strong>123456</strong>.</p>'
};
const sent = await resend.emails.send(email);
assert.equal(sent.error, null);
assert.ok(sent.data.id);
const saved = await resend.emails.get(sent.data.id);
assert.equal(saved.error, null);
assert.equal(saved.data.id, sent.data.id);
assert.equal(saved.data.subject, email.subject);
assert.equal(saved.data.text, email.text);
assert.deepEqual(saved.data.to, email.to);
writeFileSync('last-email.json', JSON.stringify({ id: sent.data.id }) + '\n');
console.log('email sent and read back locally');
npx volter world up
npx volter world clock set 2026-01-15T12:00:00Z
npx volter world run -- node send-email.mjs
email sent and read back locally

The SDK uses its ordinary destination and the World's credential. Injection routes it to the twin. The returned ID names a stored email; a scenario handler does not invent this write.

Inspect the local inbox

The twin models delivery five seconds after sending. Advance its clock rather than sleeping. /_twin/mail is a twin-specific inbox endpoint, not a Resend production API.

inspect-email.mjs
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { Resend } from 'resend';

assert.ok(process.env.VOLTER_WORLD, 'run this script through the World');
const { id } = JSON.parse(readFileSync('last-email.json', 'utf8'));
const resend = new Resend(process.env.RESEND_API_KEY);
const saved = await resend.emails.get(id);
assert.equal(saved.error, null);
assert.equal(saved.data.last_event, 'delivered');
const response = await fetch(`${process.env.RESEND_TWIN_URL}/_twin/mail?to=owner%40world.test`);
assert.equal(response.status, 200);
const inbox = await response.json();
const email = inbox.mail.find((message) => message.id === id);
assert.ok(email, 'the delivered message must be in the local inbox');
assert.equal(email.subject, 'Your local sign-in code');
assert.match(email.text, /123456/);
assert.match(email.html, /<strong>123456<\/strong>/);
console.log('local inbox contains the delivered email and sign-in code');
npx volter world clock advance 5s
npx volter world run -- node inspect-email.mjs
npx volter world log

The inbox assertions check stored content and simulated delivery. They do not measure real deliverability, spam filtering or email-client rendering. An existing app can use the same inbox endpoint to retrieve its sign-in link or code during a local workflow.

Stop and resume the same email

npx volter world down
npx volter world up
npx volter world run -- node inspect-email.mjs
npx volter world down
local inbox contains the delivered email and sign-in code
Stopped local-email-example

down stops compute and retains the message. A later up resumes its state. Keep last-email.json while reproducing this example; it records which message to inspect. For a fresh run, reset deliberately, freeze the clock, send again and inspect the newly returned ID.

For failures such as a rate limit, use scenario handlers. They author refusals and stateless behavior; successful email sends still create real local records. The catalog release describes its other supported operations, local setup endpoints and gaps.

View Markdown source

On this page