Send your first email
Publish a template, generate your client, test in the virtual inbox, and send through your provider.
BEFORE YOU START
You’ll need a Papier workspace and a server-side TypeScript project. These scripts use Node.js 22.23.4 or later and an ESM project with "type": "module" in package.json. The examples use project acme and template welcome-email; substitute your own slugs.
Prepare a welcome template#
In your Papier workspace, create an email template with the slug welcome-email. Add a personalized greeting such as Welcome, {{ name }}! in the template’s HTML or text content. Set this data schema and publish a version of the template.
{
"type": "object",
"properties": {
"name": { "type": "string" }
},
"required": ["name"],
"additionalProperties": false
}The schema makes name required. The generated client uses it for TypeScript autocomplete, and the SDK validates the data at runtime.
Connect and generate your client#
Run these commands from your application directory. During init, select your organization, project, and template versions. sync resolves schemas and generates the client.
npm install @usepapier/client @usepapier/cli
npm install --save-dev tsx
npx @usepapier/cli login
npx @usepapier/cli init
npx @usepapier/cli syncAdd a Papier API key to your local .env file. Keep it on the server and out of version control.
PAPIER_API_KEY=your_papier_api_keywelcome-email becomes papier.emails.welcomeEmail. If that property is missing, check the project and template selection in .papier/papier.json, then run sync again.
Test with the virtual inbox#
Start with virtualInboxAdapter. It renders the template and records the test send through the SDK’s logging flow without delivering an email to the recipient.
import { createPapier } from '@usepapier/client';
import { virtualInboxAdapter } from '@usepapier/client/base';
const apiKey = process.env.PAPIER_API_KEY;
if (!apiKey) throw new Error('Set PAPIER_API_KEY on the server.');
const papier = createPapier({
project: 'acme',
apiKey,
environment: 'development',
emailConfig: {
from: { name: 'Acme', email: 'hello@example.com' },
adapters: [virtualInboxAdapter],
},
});
const result = await papier.emails.welcomeEmail.send({
to: 'alex@example.com',
subject: 'Welcome aboard, Alex',
data: { name: 'Alex' },
});
console.log(result.success, result.adapter, result.id);
// true, 'virtual-inbox', a generated test IDnpx tsx --env-file=.env test-email.tsYou should see true, virtual-inbox, and a test ID. Open your workspace’s virtual inbox and developer logs to inspect the test. The SDK still needs access to your published template; a missing log can also mean its logging request failed.
Send through a provider#
Follow Configure email providers to save providers.ts and client.ts beside your script. That guide connects Resend or SendGrid, adds a verified sender, and registers the adapter in emailConfig.adapters.
Then save and run this script with your own recipient address. The SDK fetches the template, validates your data, renders HTML, and calls the configured adapter.
import { papier } from './client.js';
try {
const result = await papier.emails.welcomeEmail.send({
to: 'alex@example.com', // Replace with your own recipient.
subject: 'Welcome aboard, Alex',
data: { name: 'Alex' },
});
console.log('Accepted by:', result.adapter, result.id);
} catch (error) {
console.error(error instanceof Error ? error.message : 'Email failed');
process.exitCode = 1;
}npx tsx --env-file=.env send-email.tsA successful result means the adapter accepted the send. Use your provider’s delivery events to confirm that the message reached the recipient.
Handle failures and send later#
The try/catch above covers schema errors, template-fetch errors, and provider failures. Correct invalid data before retrying. If no adapter is registered, the SDK throws a “no email adapters configured” error.
For a queue or outbox workflow, call build() first. It validates and returns a serializable payload; your application owns persistence and scheduling. This minimal example sends the payload immediately so you can try both methods.
import { papier } from './client.js';
const payload = await papier.emails.welcomeEmail.build({
to: 'alex@example.com',
subject: 'Welcome aboard, Alex',
data: { name: 'Alex' },
});
// build() validates the data. It does not send an email or render HTML.
// Persist payload in your own outbox or queue if you need to send it later.
const result = await papier.send(payload);
console.log('Accepted by:', result.adapter, result.id);Use a worker configured for the same project to send a stored payload. The payload contains template metadata and data, rather than a snapshot of rendered HTML; rendering and template resolution happen when you send.