Volter World

Customer onboarding with Stripe, email and SQL

Create a Stripe customer, save the application's signup row in PostgreSQL, and request a welcome email with Resend. Inspect each stored outcome through the real SDKs. The browser form and command-line workflow use the same application code.

You need Node 22.6+, npm and registry access during installation. The browser page also prepares public Volter brand assets before entering the World. No platform account or real vendor credential is needed. PostgreSQL is a World-managed application database; its records are application state, separate from SaaS history.

Obtain the application

Copy this starter into a new folder:

$ npx @volter/world@3.0.158 example customer-onboarding --dir onboarding
$ cd onboarding
$ npm install

The command copies source; it neither installs dependencies nor starts a World. Its manifest pins the CLI, core, runtime, console, twins and SDKs. The installed starter creates a lockfile during installation. The repository's package manifest and lockfile identify the source example's dependency cohort. Keep your own lockfile with the application when sharing it.

Read onboarding.mjs for the application and run.mjs for the command entry. Empty keys in .env.example tell init what the app reads; init supplies throwaway credentials. Do not add real keys.

$ ./node_modules/.bin/volter world init --name onboarding --twins stripe,resend

Review Stripe and Resend's exact sources in .volter/world.json. The pg dependency and DATABASE_URL also request a managed PostgreSQL service. Keep it: this app needs SQL. SaaS selection does not exclude a declared native database. world covers describes the configured database as part of this workflow.

Create and read an account

$ ./node_modules/.bin/volter world up
$ ./node_modules/.bin/volter world clock set 2026-01-15T12:00:00Z
$ ./node_modules/.bin/volter world run -- node run.mjs create Ada owner@world.test
$ ./node_modules/.bin/volter world clock advance 5s
$ ./node_modules/.bin/volter world run -- node run.mjs read
$ ./node_modules/.bin/volter world log

Read the customer returned by Stripe, the signup row returned by SQL and the email returned by Resend. completion: complete means delivery was observed, not merely that a send request was accepted. Opaque IDs can differ between fresh Worlds; customer identity, stored fields and delivery status are the useful outcomes.

Observe two different failures

A refusing recipient's mail server creates a bounce after the send was accepted:

$ ./node_modules/.bin/volter world run -- node run.mjs bounce
$ ./node_modules/.bin/volter world run -- node run.mjs create Grace owner@world.test
$ ./node_modules/.bin/volter world clock advance 5s
$ ./node_modules/.bin/volter world run -- node run.mjs read

Grace's customer and SQL row remain. Her email exists and has last_event: bounced; the application reports partial-email-bounced. The bounce command uses Resend's declared synthetic recipient setting, not a successful-send handler. Restore delivery before later signups:

$ ./node_modules/.bin/volter world run -- node run.mjs deliver

An unverified sender is refused before an email is accepted:

$ ./node_modules/.bin/volter world run -- node run.mjs refused
$ ./node_modules/.bin/volter world run -- node run.mjs retry
$ ./node_modules/.bin/volter world clock advance 5s
$ ./node_modules/.bin/volter world run -- node run.mjs read

The first command creates Lin's customer and SQL row, then records the email refusal. The second resumes that pending welcome on the same customer. It refuses to resend an email that already has an accepted ID. These application receipts do not guarantee exactly-once delivery after an ambiguous network failure; resolve acceptance before retrying an uncertain send.

Use the browser form

Prepare its fonts and logo outside the World:

$ node prepare-ui.mjs

Then start the app through the World:

$ ./node_modules/.bin/volter world run -- node server.mjs

Open the printed app URL in your browser. Submit a signup, advance the World clock in another terminal and reload. The page shows the completion state and full stored outcome. Its server registers its app URL with the World after listening; consumers can discover it with world app-url.

To open the World dashboard, stop the app with Ctrl-C first, then switch the owned compute to the viewer:

$ ./node_modules/.bin/volter world down
$ ./node_modules/.bin/volter world view --no-open

The viewer prints the dashboard URL and keeps running. Start world run -- node server.mjs in a second terminal if you want the application alongside it. Open Stripe under Records, then select the customer to inspect its name, email and stored ID. Open Resend to inspect the accepted email and delivery event. Board opens the vendor pages declared by the installed twin; its root workspace may cover settings rather than customers. Provisioning remains in full history even when hidden from the business-activity view. Read the application's SQL completion record with run.mjs read or its browser page.

Stop app consumers before stopping the viewer. down retains vendor and declared application state. Resume explains continuing it; demo a workflow uses this same application for a repeatable baseline, refusal and retry. Reset only your disposable example World; it discards that branch's work.

Source files are also in the CLI starter. This page describes the source workflow; it does not inherit execution evidence from another example or an older release.

View Markdown source

On this page