The official Node.js and TypeScript SDK for Veil Mail — secure transactional and marketing email with automatic PII protection, CASL compliance, and a developer-first API.
Veil Mail is a drop-in alternative to Resend, SendGrid, Mailgun, and Postmark. The SDK mirrors the Resend client shape, so most migrations are a package swap and a constructor change.
// Before — Resend
import { Resend } from 'resend'
const resend = new Resend('re_xxxxx')
await resend.emails.send({ from, to, subject, html })
// After — Veil Mail
import { VeilMail } from '@resonia/veilmail-sdk'
const client = new VeilMail('veil_live_xxxxx')
await client.emails.send({ from, to, subject, html })Migration guides: from Resend · from SendGrid · from Mailgun · from Postmark
npm install @resonia/veilmail-sdk
# or
pnpm add @resonia/veilmail-sdk
# or
yarn add @resonia/veilmail-sdkimport { VeilMail } from '@resonia/veilmail-sdk';
const client = new VeilMail('veil_live_xxxxx');
// Send an email
const email = await client.emails.send({
from: 'hello@example.com',
to: 'user@example.com',
subject: 'Welcome!',
html: '<h1>Welcome to our platform!</h1>',
});
console.log(email.id); // email_xxxxx- Full TypeScript support - Complete type definitions for all API operations
- Automatic PII protection - Emails are scanned for sensitive data before sending
- Zero dependencies - Uses native
fetch(Node.js 18+) - Comprehensive error handling - Typed errors for different scenarios
// Send an email
await client.emails.send({
from: 'hello@example.com',
to: 'user@example.com',
subject: 'Hello!',
html: '<h1>Welcome!</h1>',
});
// Send with a template
await client.emails.send({
from: 'hello@example.com',
to: 'user@example.com',
templateId: 'template_xxxxx',
variables: { firstName: 'John' },
});
// List emails
const { data, hasMore } = await client.emails.list({ limit: 20 });
// Get a single email
const email = await client.emails.get('email_xxxxx');
// Cancel a scheduled email
await client.emails.cancel('email_xxxxx');// Add a domain
const domain = await client.domains.create({ domain: 'mail.example.com' });
console.log(domain.dnsRecords); // DNS records to configure
// Verify domain DNS
await client.domains.verify('domain_xxxxx');
// List domains
const { data } = await client.domains.list();
// Delete a domain
await client.domains.delete('domain_xxxxx');// Create a template
const template = await client.templates.create({
name: 'Welcome Email',
subject: 'Welcome, {{firstName}}!',
html: '<h1>Hello {{firstName}}!</h1>',
variables: [{ name: 'firstName', type: 'string', required: true }],
});
// List templates
const { data } = await client.templates.list();
// Update a template
await client.templates.update('template_xxxxx', {
subject: 'Updated Subject',
});
// Delete a template
await client.templates.delete('template_xxxxx');// Create an audience
const audience = await client.audiences.create({
name: 'Newsletter Subscribers',
description: 'Users who signed up for updates',
});
// Add a subscriber
const subscriber = await client.audiences.subscribers('audience_xxxxx').add({
email: 'user@example.com',
firstName: 'John',
lastName: 'Doe',
});
// Add a subscriber with double opt-in (starts as pending)
const pending = await client.audiences.subscribers('audience_xxxxx').add({
email: 'user@example.com',
firstName: 'John',
doubleOptIn: true, // Status will be 'pending' until confirmed
});
// Confirm a pending subscriber (after they click confirmation link)
const confirmed = await client.audiences.subscribers('audience_xxxxx').confirm('subscriber_xxxxx');
// confirmed.status === 'active', confirmed.confirmedAt is set
// List subscribers (filter by status: 'pending', 'active', 'unsubscribed', etc.)
const { data } = await client.audiences.subscribers('audience_xxxxx').list({
status: 'active',
});
// Remove a subscriber
await client.audiences.subscribers('audience_xxxxx').remove('subscriber_xxxxx');// Create a campaign
const campaign = await client.campaigns.create({
name: 'December Newsletter',
subject: 'Our December Updates',
from: 'news@example.com',
audienceId: 'audience_xxxxx',
templateId: 'template_xxxxx',
});
// Schedule for later
await client.campaigns.schedule('campaign_xxxxx', {
scheduledAt: '2024-12-25T09:00:00Z',
});
// Or send immediately
await client.campaigns.send('campaign_xxxxx');
// Pause a sending campaign
await client.campaigns.pause('campaign_xxxxx');
// Resume a paused campaign
await client.campaigns.resume('campaign_xxxxx');
// Cancel a campaign
await client.campaigns.cancel('campaign_xxxxx');// Create a webhook
const webhook = await client.webhooks.create({
url: 'https://example.com/webhooks/veilmail',
events: ['email.delivered', 'email.bounced'],
});
console.log(webhook.secret); // Use this to verify signatures
// Test a webhook
const result = await client.webhooks.test('webhook_xxxxx');
// Rotate signing secret
await client.webhooks.rotateSecret('webhook_xxxxx');Veil Mail has built-in email compliance features for marketing emails.
Set the type field to classify your emails:
// Marketing email — automatically gets compliance features
await client.emails.send({
from: 'news@yourdomain.com',
to: 'subscriber@example.com',
subject: 'Monthly Newsletter',
html: '<h1>Updates</h1>',
type: 'marketing', // Adds unsubscribe headers, footer, enforces physical address
});
// Transactional email (default) — no compliance overhead
await client.emails.send({
from: 'orders@yourdomain.com',
to: 'customer@example.com',
subject: 'Order #1234 Confirmed',
html: '<h1>Order confirmed</h1>',
// type defaults to 'transactional'
});When type: 'marketing' is set:
- List-Unsubscribe headers are added automatically (RFC 2369 + RFC 8058)
- Unsubscribe footer is appended to the HTML with your physical address
- Physical address is required (configure in dashboard Settings)
- Suppression check blocks sends to bounced/unsubscribed/complained addresses
All emails (transactional and marketing) are checked against the suppression list. If a recipient is suppressed, the API returns a 422 with error code recipient_suppressed:
try {
await client.emails.send({ ... });
} catch (error) {
if (error instanceof VeilMailError && error.code === 'recipient_suppressed') {
console.log('Suppressed:', error.details.suppressedRecipients);
}
}The SDK provides typed errors for different scenarios:
import {
VeilMail,
VeilMailError,
AuthenticationError,
ValidationError,
PiiDetectedError,
RateLimitError,
} from '@resonia/veilmail-sdk';
try {
await client.emails.send({ ... });
} catch (error) {
if (error instanceof PiiDetectedError) {
console.log('PII detected:', error.piiTypes);
} else if (error instanceof RateLimitError) {
console.log('Rate limited, retry after:', error.retryAfter);
} else if (error instanceof ValidationError) {
console.log('Validation error:', error.message);
} else if (error instanceof AuthenticationError) {
console.log('Invalid API key');
} else if (error instanceof VeilMailError) {
console.log('API error:', error.code, error.message);
}
}const client = new VeilMail({
apiKey: 'veil_live_xxxxx',
baseUrl: 'https://api.veilmail.xyz', // Optional
timeout: 30000, // Optional, in milliseconds
});- Node.js 18+ (uses native
fetch)
When using with Next.js 16 and Turbopack, add the SDK to transpilePackages in your next.config.ts:
const nextConfig = {
transpilePackages: ['@resonia/veilmail-sdk'],
// ... other config
};If linking the SDK locally in a monorepo, use file: protocol instead of link: for better Turbopack compatibility:
{
"dependencies": {
"@resonia/veilmail-sdk": "file:../path/to/sdk"
}
}Veil Mail's SDK and REST API are designed to be a drop-in replacement for the major transactional email providers. See the full side-by-side migration guides:
- Migrating from Resend — near-identical SDK shape. Package swap + constructor change.
- Migrating from SendGrid — replace
@sendgrid/mail, map dynamic templates, update webhook verification. - Migrating from Mailgun — swap the form-data API for a JSON REST client. Routes and SMTP relay supported.
- Migrating from Postmark — map message streams to the
typefield. Inbound email supported.
Veil Mail throws typed errors instead of Resend's { data, error } return pattern:
// Resend pattern
const { data, error } = await resend.emails.send({ ... });
if (error) { /* handle */ }
// Veil Mail pattern
try {
const email = await client.emails.send({ ... });
} catch (error) {
// Handle typed errors — AuthenticationError, ValidationError,
// PiiDetectedError, RateLimitError, VeilMailError
}Webhook event names map almost one-to-one. The only rename is email.delivery_delayed → email.deferred. Use the X-VeilMail-Signature header for HMAC-SHA256 signature verification.
- Full documentation
- SDK reference
- Email API reference
- Authentication
- Webhooks
- React Email adapter
- MCP server for Claude/Cursor
- Framework guides: Next.js · Express · Fastify · Laravel · Rails
Package not found after publishing? npm registry propagation can take a few minutes. Wait 2-5 minutes and try again.
Turbopack build errors?
Make sure you've added @resonia/veilmail-sdk to transpilePackages in your Next.js config.
MIT - See LICENSE for details.