@truncus/email
v0.2.0
Published
Node.js SDK for Truncus Email API
Maintainers
Readme
@truncus/email
Node.js SDK for Truncus — transactional email infrastructure built on AWS SES with deliverability built-in.
Installation
npm install @truncus/email
# or
pnpm add @truncus/email
# or
yarn add @truncus/emailQuick Start
import { Truncus } from '@truncus/email';
const truncus = new Truncus({ apiKey: 'tr_live_...' });
const result = await truncus.sendEmail({
to: '[email protected]',
from: '[email protected]',
subject: 'Welcome!',
html: '<h1>Hello World</h1>',
});
console.log(result.id); // Email ID
console.log(result.status); // 'sent'Configuration
const truncus = new Truncus({
apiKey: 'tr_live_...', // Required
baseUrl: 'https://truncus.co', // Optional, defaults to production
timeout: 30000, // Optional, request timeout in ms (default 30s)
});API Reference
Emails
sendEmail(options)
Send a transactional email.
const result = await truncus.sendEmail({
// Required
to: '[email protected]',
from: '[email protected]', // Must match a verified domain
subject: 'Welcome!',
html: '<h1>Hello World</h1>',
// Optional
text: 'Hello World', // Plain-text fallback
cc: ['[email protected]'],
bcc: ['[email protected]'],
templateId: 'tmpl_...', // Use a saved template instead of html
variables: { name: 'John' }, // Template variables ({{name}})
metadata: { userId: '123' }, // Custom metadata (stored, not sent)
idempotencyKey: 'order-123', // Prevent duplicate sends (auto-generated if omitted)
tenantId: 'tenant_abc', // Multi-tenant suppression isolation
sendAt: '2026-03-10T09:00:00Z', // Schedule for future delivery (ISO 8601, must be future)
sandbox: true, // Skip SES — test without sending
trackOpens: true, // 1×1 tracking pixel (default: true)
trackClicks: true, // Rewrite links through click proxy (default: true)
attachments: [
{
filename: 'invoice.pdf',
content: '<base64-encoded-content>',
content_type: 'application/pdf',
},
],
});
// result.status === 'sent' — sent immediately
// result.status === 'scheduled' — queued for sendAt time
// result.sendAt — ISO 8601 delivery time (scheduled emails)
// result.sandbox — true if sandbox mode was activegetEmail(emailId)
Retrieve details for a specific email, including open and click tracking stats.
const email = await truncus.getEmail('email_id');
console.log(email.status); // 'delivered'
console.log(email.openCount); // 3 (total opens, including repeat opens)
console.log(email.openedAt); // '2026-03-04T10:23:00Z' (first open)
console.log(email.clickCount);// 1cancelEmail(emailId)
Cancel a scheduled email before it is dispatched. Only works on emails with status === 'scheduled'.
const result = await truncus.cancelEmail('email_id');
console.log(result.status); // 'cancelled'Events
listEvents(options?)
List email events. Includes 'opened' and 'clicked' event types.
// All recent events
const { events } = await truncus.listEvents();
// Events for a specific email
const { events } = await truncus.listEvents({ emailId: 'email_id' });
// Filter by type
const { events } = await truncus.listEvents({ type: 'opened', limit: 50 });
const { events } = await truncus.listEvents({ type: 'clicked', limit: 50 });
const { events } = await truncus.listEvents({ type: 'delivered' });Event types: queued | sent | delivered | bounce | complaint | failed | retry_scheduled | retry_attempted | opened | clicked
Domains
createDomain(options)
Add a sending domain. Returns DNS records to configure.
const domain = await truncus.createDomain({ domain: 'mail.yourdomain.com' });
// Configure these DNS records at your registrar:
console.log(domain.dnsRecords);
// [
// { type: 'TXT', name: '_truncus.mail.yourdomain.com', value: 'truncus-verification=...' },
// { type: 'TXT', name: 'mail.yourdomain.com', value: 'v=spf1 include:amazonses.com ~all' },
// { type: 'CNAME', name: '...._domainkey.mail.yourdomain.com', value: '....dkim.amazonses.com' },
// { type: 'TXT', name: '_dmarc.mail.yourdomain.com', value: 'v=DMARC1; p=quarantine; ...' },
// ]verifyDomain(domainId)
Trigger DNS verification after configuring records.
const result = await truncus.verifyDomain('domain_id');
console.log(result.verified); // true when DNS is correctlistDomains()
List all domains with sending stats.
const { domains } = await truncus.listDomains();
domains.forEach((d) => {
console.log(d.domain, d.status, d.sentToday);
});Suppression
checkSuppression(email, tenantId?)
Check whether an address is on the suppression list before sending.
const status = await truncus.checkSuppression('[email protected]');
console.log(status.suppressed); // true / false
console.log(status.reason); // 'bounce' | 'complaint' | nullFor multi-tenant apps, scope the check to a specific tenant:
const status = await truncus.checkSuppression('[email protected]', 'tenant_abc');Stats
getStats()
Aggregate sending stats for the account.
const stats = await truncus.getStats();
console.log(stats.sentToday);
console.log(stats.deliveryRate); // 0.98 (98%)
console.log(stats.bounceRate); // 0.01 (1%)Sandbox Mode
Test your integration without sending real emails:
const result = await truncus.sendEmail({
to: '[email protected]',
from: '[email protected]', // Domain validation skipped in sandbox
subject: 'Test',
html: '<p>Test</p>',
sandbox: true,
});
console.log(result.sandbox); // true
console.log(result.status); // 'sent' (simulated)Sandbox skips SES entirely. No emails are delivered. Open/click tracking is also disabled.
Scheduled Sending
Schedule an email for future delivery. The email is queued immediately and dispatched at sendAt:
const result = await truncus.sendEmail({
to: '[email protected]',
from: '[email protected]',
subject: 'Your weekly digest',
html: '...',
sendAt: '2026-03-10T09:00:00Z', // Must be a future datetime
});
console.log(result.status); // 'scheduled'
console.log(result.sendAt); // '2026-03-10T09:00:00.000Z'
// Cancel before dispatch
await truncus.cancelEmail(result.id);Open & Click Tracking
Tracking is enabled by default. Truncus injects a 1×1 pixel for opens and rewrites links for click tracking. Unsubscribe links are never rewritten.
// Default — both opens and clicks tracked
const result = await truncus.sendEmail({ ..., html: '...' });
// Disable tracking
const result = await truncus.sendEmail({
...,
html: '...',
trackOpens: false,
trackClicks: false,
});
// Check engagement after sending
const email = await truncus.getEmail(result.id);
console.log(email.openedAt); // First open timestamp
console.log(email.openCount); // Total opens (repeat opens counted)
console.log(email.clickCount); // Total link clicksEvents also fire for opens and clicks — query them with listEvents({ type: 'opened' }) or listEvents({ type: 'clicked' }).
Idempotency
Prevent duplicate sends by providing a stable idempotency key. If you retry with the same key, the original response is returned without sending again:
const result = await truncus.sendEmail({
to: '[email protected]',
from: '[email protected]',
subject: 'Order Confirmation',
html: '...',
idempotencyKey: `order-${orderId}-confirmation`,
});An idempotency key is auto-generated (UUID v4) when not provided.
Multi-tenant Apps
Isolate suppression lists per tenant so a bounce in one customer's account doesn't affect others:
const result = await truncus.sendEmail({
to: '[email protected]',
from: '[email protected]',
subject: 'Welcome!',
html: '...',
tenantId: 'tenant_123',
});Error Handling
import { TruncusError } from '@truncus/email';
try {
await truncus.sendEmail({ ... });
} catch (error) {
if (error instanceof TruncusError) {
console.log(error.code); // 'DOMAIN_NOT_VERIFIED'
console.log(error.message); // 'Domain is not verified'
console.log(error.status); // 400
}
}Error Codes
| Code | Status | Description |
|------|--------|-------------|
| MISSING_API_KEY | 401 | Authorization header missing |
| INVALID_API_KEY | 401 | API key is invalid or revoked |
| SCOPE_REQUIRED | 403 | API key missing required scope |
| INVALID_REQUEST | 400 | Request body validation failed |
| DOMAIN_NOT_FOUND | 404 | Domain doesn't exist |
| DOMAIN_NOT_VERIFIED | 400 | Domain pending DNS verification |
| DOMAIN_PAUSED | 400 | Domain paused due to high bounce/complaint rate |
| WARMUP_CAP_EXCEEDED | 429 | Daily sending limit reached |
| ALL_RECIPIENTS_SUPPRESSED | 422 | All recipients are on suppression list |
| PROVIDER_ERROR | 502 | AWS SES returned an error |
| TIMEOUT | 408 | Request timed out |
| NETWORK_ERROR | 0 | Network connectivity issue |
TypeScript
Full type definitions are included. Key types:
import type {
SendEmailOptions,
SendEmailResponse,
EmailDetails,
EmailStatus,
EventType,
EmailEvent,
Domain,
DomainStatus,
SuppressionStatus,
TruncusConfig,
} from '@truncus/email';License
MIT
