Express
Send transactional emails from your Express.js API using the Veil Mail SDK.
Prerequisites
- Express 4 or later
- Node.js 18+
- A Veil Mail API key
- A verified domain
1. Install the SDK
npm install @resonia/veilmail-sdk2. Configure the Client
Create a shared client instance. Store your API key in an environment variable — never commit it to source control.
.env
VEILMAIL_API_KEY=veil_live_xxxxx
VEILMAIL_WEBHOOK_SECRET=whsec_xxxxxsrc/lib/veilmail.ts
import { VeilMail } from '@resonia/veilmail-sdk';
export const veilmail = new VeilMail({
apiKey: process.env.VEILMAIL_API_KEY,
});3. Send Your First Email
Add an endpoint that sends a transactional email:
src/index.ts
import express from 'express';
import { veilmail } from './lib/veilmail';
const app = express();
app.use(express.json());
app.post('/api/send-welcome', async (req, res) => {
try {
const { email, name } = req.body;
const result = await veilmail.emails.send({
from: 'hello@yourdomain.com',
to: email,
subject: `Welcome, ${name}!`,
html: `<h1>Welcome</h1><p>Hi ${name}, thanks for joining.</p>`,
});
res.json({ emailId: result.id });
} catch (error) {
console.error('Failed to send email:', error);
res.status(500).json({ error: 'Failed to send email' });
}
});
app.listen(3000, () => console.log('Server running on port 3000'));4. Send with a Template
Use templates to manage email content outside your code:
src/routes/billing.ts
app.post('/api/send-invoice', async (req, res) => {
const { email, invoiceId, amount } = req.body;
const result = await veilmail.emails.send({
from: 'billing@yourdomain.com',
to: email,
subject: `Invoice #${invoiceId}`,
templateId: 'template_xxxxx',
templateData: {
invoiceId,
amount: `$${amount}`,
dueDate: new Date(Date.now() + 30 * 86400000).toLocaleDateString(),
},
});
res.json({ emailId: result.id });
});5. Handle Webhooks
Receive real-time event notifications. Use express.raw() to get the raw body for signature verification.
src/webhooks.ts
import crypto from 'crypto';
import express from 'express';
function verifySignature(payload: string, signature: string, secret: string): boolean {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Important: use express.raw() so the body isn't parsed as JSON
app.post(
'/webhooks/veilmail',
express.raw({ type: 'application/json' }),
(req, res) => {
const signature = req.headers['x-veilmail-signature'] as string;
const payload = req.body.toString();
if (!signature || !verifySignature(payload, signature, process.env.VEILMAIL_WEBHOOK_SECRET!)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(payload);
switch (event.type) {
case 'email.delivered':
console.log('Delivered:', event.data.emailId);
break;
case 'email.bounced':
console.log('Bounced:', event.data.emailId);
// Remove from mailing list or flag user
break;
case 'email.complained':
console.log('Complaint:', event.data.emailId);
// Unsubscribe the user
break;
case 'subscriber.unsubscribed':
console.log('Unsubscribed:', event.data.subscriberId);
break;
}
res.status(200).send('OK');
}
);Important: The webhook route must use express.raw() instead of express.json() so the body is available as a raw Buffer for signature verification.
6. Manage Subscribers
Add and manage subscribers from your API:
src/routes/subscribers.ts
// Add a subscriber when a user signs up
app.post('/api/subscribe', async (req, res) => {
const { email, firstName, lastName } = req.body;
const subscriber = await veilmail.audiences
.subscribers('audience_xxxxx')
.add({
email,
firstName,
lastName,
metadata: { source: 'website' },
});
res.json({ subscriberId: subscriber.id });
});
// Unsubscribe
app.post('/api/unsubscribe', async (req, res) => {
const { subscriberId } = req.body;
await veilmail.audiences
.subscribers('audience_xxxxx')
.remove(subscriberId);
res.json({ success: true });
});7. Error Handling
The SDK throws typed errors you can catch and handle:
src/routes/email.ts
import { VeilMail, VeilMailError, RateLimitError } from '@resonia/veilmail-sdk';
app.post('/api/send', async (req, res) => {
try {
const result = await veilmail.emails.send({
from: 'hello@yourdomain.com',
to: req.body.email,
subject: 'Hello',
html: '<p>Hi!</p>',
});
res.json({ id: result.id });
} catch (error) {
if (error instanceof RateLimitError) {
res.status(429).json({ error: 'Rate limited, try again later' });
} else if (error instanceof VeilMailError) {
res.status(error.statusCode).json({ error: error.message });
} else {
res.status(500).json({ error: 'Internal error' });
}
}
});