Email SDK Reference
Complete reference for @wraps.dev/email TypeScript SDK.
A TypeScript-first SDK for sending emails through your Wraps-deployed AWS SES infrastructure. Simple, type-safe, and intuitive API with React.email support.
npm install @wraps.dev/email
import { WrapsEmail } from '@wraps.dev/email';const email = new WrapsEmail();const result = await email.send({ from: 'hello@yourdomain.com', to: 'user@example.com', subject: 'Welcome!', html: '<h1>Hello!</h1>',});console.log('Message ID:', result.messageId);Create a new WrapsEmail client. The SDK automatically detects AWS credentials from your environment.
new WrapsEmail(config?: WrapsEmailConfig)client (optional): Pre-configured SES client. Takes precedence over all other auth options.region (optional): AWS region where your infrastructure is deployed. Defaults to us-east-1credentials (optional): AWS credentials (static object or provider function). Auto-detected if not provided.roleArn (optional): IAM Role ARN to assume (for OIDC federation or cross-account access).endpoint (optional): Custom SES endpoint (for testing with LocalStack).inboxBucketName (optional): S3 bucket for inbound email storage. Enables inbox API.historyTableName (optional): DynamoDB table for email event history. Enables events API.client option)roleArn option)credentials option)~/.aws/credentials, IAM role)Example with Options
const email = new WrapsEmail({ region: 'us-west-2', credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, sessionToken: process.env.AWS_SESSION_TOKEN, // optional },});Testing with LocalStack
// Testing with LocalStackconst email = new WrapsEmail({ region: 'us-east-1', endpoint: 'http://localhost:4566',});WrapsEmailConfig Type
type WrapsEmailConfig = { client?: SESClient; // Pre-configured SES client (takes precedence) s3Client?: S3Client; // Pre-configured S3 client for inbox inboxBucketName?: string; // S3 bucket for inbound email storage region?: string; // AWS region (default: 'us-east-1') credentials?: { // Explicit AWS credentials (static or provider) accessKeyId: string; secretAccessKey: string; sessionToken?: string; } | AwsCredentialIdentityProvider; roleArn?: string; // IAM role for OIDC assumption roleSessionName?: string; // Session name for AssumeRole endpoint?: string; // Custom endpoint (e.g., LocalStack) historyTableName?: string; // DynamoDB table for email event history dynamodbClient?: DynamoDBDocumentClient; // Pre-configured DynamoDB client sesv2Client?: SESv2Client; // Pre-configured SES v2 client for suppression};Send an email using AWS SES through your Wraps infrastructure.
email.send(params: SendEmailParams): Promise<SendEmailResult>| Parameter | Type | Description |
|---|---|---|
from | string | Sender email address (must be verified) |
to | string | string[] | Recipient email address(es) |
subject | string | Email subject line |
html | string | HTML email body (optional) |
text | string | Plain text email body (optional) |
react | ReactElement | React.email component (optional) |
attachments | Attachment[] | File attachments (optional) |
tags | Record<string, string> | SES message tags (optional) |
cc, bcc, replyTo | string | string[] | Additional recipients (optional) |
Basic Email
const result = await email.send({ from: 'hello@yourdomain.com', to: 'user@example.com', subject: 'Welcome to our app', html: '<h1>Welcome!</h1><p>Thanks for signing up.</p>', text: 'Welcome! Thanks for signing up.',});console.log('Message ID:', result.messageId);console.log('Request ID:', result.requestId);Multiple Recipients
const result = await email.send({ from: 'newsletter@yourdomain.com', to: ['user1@example.com', 'user2@example.com'], cc: 'manager@yourdomain.com', bcc: ['archive@yourdomain.com'], replyTo: 'support@yourdomain.com', subject: 'Weekly Newsletter', html: '<h1>This week\'s updates</h1>',});With Tags
// Add SES tags for tracking and analyticsawait email.send({ from: 'you@yourdomain.com', to: 'user@example.com', subject: 'Newsletter', html: '<p>Content</p>', tags: { campaign: 'newsletter-2025-01', type: 'marketing', }, configurationSetName: 'wraps-email-tracking', // optional});Use React.email components for beautiful, type-safe email templates. The SDK automatically renders React components to HTML and plain text.
import { WelcomeEmail } from './emails/Welcome';// Send using React.email componentawait email.send({ from: 'hello@yourdomain.com', to: 'user@example.com', subject: 'Welcome to our platform', react: <WelcomeEmail name="John" orderId="12345" />,});Send emails with file attachments (PDFs, images, documents, etc.). The SDK automatically handles MIME encoding.
// Single attachmentconst result = await email.send({ from: 'you@yourdomain.com', to: 'user@example.com', subject: 'Your invoice', html: '<p>Please find your invoice attached.</p>', attachments: [ { filename: 'invoice.pdf', content: pdfBuffer, // Buffer or base64 string contentType: 'application/pdf', // Optional - auto-detected }, ],});// Multiple attachmentsawait email.send({ from: 'you@yourdomain.com', to: 'user@example.com', subject: 'Monthly Report', html: '<h1>Monthly Report</h1>', attachments: [ { filename: 'report.pdf', content: pdfBuffer }, { filename: 'chart.png', content: imageBuffer }, { filename: 'data.csv', content: csvBuffer }, ],});| Field | Type | Description |
|---|---|---|
filename | string | Filename with extension |
content | Buffer | string | File content (Buffer or base64 string) |
contentType | string | MIME type (optional - auto-detected from filename) |
SES templates allow you to store reusable email designs with variables in your AWS account.
Create Template
// Create a template with variablesawait email.templates.create({ name: 'welcome-email', subject: 'Welcome to {{companyName}}, {{name}}!', html: ` <h1>Welcome {{name}}!</h1> <p>Click to confirm: <a href="{{confirmUrl}}">Confirm Account</a></p> `, text: 'Welcome {{name}}! Click to confirm: {{confirmUrl}}',});Create from React.email
import { WelcomeEmailTemplate } from './emails/Welcome';// Create template from React.email componentawait email.templates.createFromReact({ name: 'welcome-email-v2', subject: 'Welcome to {{companyName}}, {{name}}!', react: <WelcomeEmailTemplate />, // React component should use {{variable}} syntax});Manage Templates
// Get template detailsconst template = await email.templates.get('welcome-email');console.log(template.name, template.subject);// List all templatesconst templates = await email.templates.list();templates.forEach(t => console.log(t.name, t.createdTimestamp));// Update a templateawait email.templates.update({ name: 'welcome-email', subject: 'Welcome aboard, {{name}}!', html: '<h1>Welcome {{name}}!</h1>...',});// Delete a templateawait email.templates.delete('welcome-email');| Method | Description |
|---|---|
templates.create(params) | Create a new SES template |
templates.createFromReact(params) | Create template from React component |
templates.update(params) | Update an existing template |
templates.get(name) | Get template details |
templates.list() | List all templates |
templates.delete(name) | Delete a template |
Send an email using a pre-defined SES template. Templates support variable substitution using {{variable}} syntax.
email.sendTemplate(params: SendTemplateParams): Promise<SendEmailResult>const result = await email.sendTemplate({ from: 'hello@yourdomain.com', to: 'user@example.com', template: 'welcome-email', templateData: { name: 'John Doe', companyName: 'Acme Corp', confirmUrl: 'https://example.com/confirm/abc123', },});console.log('Email sent:', result.messageId);Send personalized templated emails to multiple recipients (up to 50 per call). Each recipient can have unique template data merged with default values.
email.sendBulkTemplate(params: SendBulkTemplateParams): Promise<SendBulkTemplateResult>const result = await email.sendBulkTemplate({ from: 'hello@yourdomain.com', template: 'weekly-digest', destinations: [ { to: 'alice@example.com', templateData: { name: 'Alice', unreadCount: 5 }, }, { to: 'bob@example.com', templateData: { name: 'Bob', unreadCount: 12 }, }, ], defaultTemplateData: { companyName: 'Acme Corp', year: '2025', },});// Check results for each recipientresult.status.forEach((item, i) => { if (item.status === 'success') { console.log(`Email ${i + 1} sent: ${item.messageId}`); } else { console.log(`Email ${i + 1} failed: ${item.error}`); }});Send unique emails to multiple recipients in a single API call (max 100 entries). Unlike sendBulkTemplate() which requires a pre-created SES template, sendBatch() lets you provide different subject, HTML, and text per recipient inline.
email.sendBatch(params: SendBatchParams): Promise<SendBatchResult>| Parameter | Type | Description |
|---|---|---|
from | string | EmailAddress | Sender email address (must be verified) |
entries | BatchEmailEntry[] | List of recipients with unique content (max 100) |
entries[].to | string | EmailAddress | Recipient email address |
entries[].subject | string | Email subject for this entry |
entries[].html | string | HTML body (optional, mutually exclusive with react) |
entries[].text | string | Plain text body (optional) |
entries[].react | ReactElement | React.email component (optional) |
entries[].tags | Record<string, string> | Per-entry SES tags (replaces defaults for this entry) |
replyTo | string | string[] | Reply-to address(es), shared across all entries (optional) |
tags | Record<string, string> | Default SES message tags (optional) |
configurationSetName | string | Configuration set for tracking (optional) |
| Field | Type | Description |
|---|---|---|
results | BatchEntryResult[] | Per-entry results in the same order as input |
successCount | number | Number of entries sent successfully |
failureCount | number | Number of entries that failed |
const result = await email.sendBatch({ from: 'hello@yourdomain.com', entries: [ { to: 'alice@example.com', subject: 'Hi Alice', html: '<p>Hello Alice! Your order #1001 has shipped.</p>', }, { to: 'bob@example.com', subject: 'Hi Bob', html: '<p>Hello Bob! Your order #1002 has shipped.</p>', }, { to: 'carol@example.com', subject: 'Hi Carol', html: '<p>Hello Carol! Your order #1003 has shipped.</p>', }, ], replyTo: 'support@yourdomain.com', tags: { campaign: 'order-shipped' },});console.log(`Sent: ${result.successCount}, Failed: ${result.failureCount}`);for (const entry of result.results) { if (entry.status === 'success') { console.log(`Entry ${entry.index}: ${entry.messageId}`); } else { console.log(`Entry ${entry.index} failed: ${entry.error}`); }}Convert HTML to plain text for email fallback. Handles block elements, line breaks, lists, links, and HTML entities. No external dependencies.
The SDK uses this internally to auto-generate plain text from HTML when you don't provide a text parameter in send() or sendBatch(). You can also import and use it directly.
htmlToPlainText(html: string): stringimport { htmlToPlainText } from '@wraps.dev/email';const html = '<h1>Welcome!</h1><p>Thanks for <a href="https://example.com">signing up</a>.</p>';const text = htmlToPlainText(html);console.log(text);// Welcome!//// Thanks for signing up (https://example.com).Read, reply to, and forward inbound emails. Requires inbound email infrastructure to be deployed with wraps email inbound init.
Initialize with Inbox
const email = new WrapsEmail({ inboxBucketName: 'your-inbound-bucket-name',});List Inbound Emails
const { emails, nextToken } = await email.inbox.list({ maxResults: 20, continuationToken: undefined, // for pagination});for (const summary of emails) { console.log(summary.emailId, summary.key, summary.lastModified);}Get Email Details
const inboundEmail = await email.inbox.get('email-abc123');console.log('From:', inboundEmail.from.address);console.log('Subject:', inboundEmail.subject);console.log('HTML:', inboundEmail.html);console.log('Attachments:', inboundEmail.attachments.length);console.log('Spam:', inboundEmail.spamVerdict);console.log('Virus:', inboundEmail.virusVerdict);Reply to Email
// Reply with proper threading headers (In-Reply-To, References)const result = await email.inbox.reply('email-abc123', { from: 'support@yourdomain.com', text: 'Thanks for reaching out!', html: '<p>Thanks for reaching out!</p>', attachments: [], // optional});console.log('Reply sent:', result.messageId);Forward Email
// Passthrough mode (default): rewrites headers, keeps original bodyconst result = await email.inbox.forward('email-abc123', { from: 'noreply@yourdomain.com', to: 'team@yourdomain.com', addPrefix: '[Customer]', // optional subject prefix});// Wrapped mode: builds new message with forwarded contentconst result2 = await email.inbox.forward('email-abc123', { from: 'noreply@yourdomain.com', to: 'team@yourdomain.com', passthrough: false, text: 'FYI - see below', // optional intro text});Get Attachment URL
// Get presigned URL for an attachment (valid for 1 hour by default)const url = await email.inbox.getAttachment('email-abc123', 'attachment-id', { expiresIn: 3600, // seconds});Get Raw MIME Email
// Get a presigned URL for the raw MIME email (valid for 1 hour)const rawUrl = await email.inbox.getRaw('email-abc123');// Download the raw emailconst response = await fetch(rawUrl);const rawMime = await response.text();console.log('Raw MIME:', rawMime.substring(0, 200));Delete Email
// Delete email and all associated files (parsed JSON, raw MIME, attachments)await email.inbox.delete('email-abc123');| Method | Description |
|---|---|
inbox.list(options?) | List inbound emails with pagination |
inbox.get(emailId) | Get full email details by ID |
inbox.reply(emailId, options) | Reply with threading headers |
inbox.forward(emailId, options) | Forward to new recipients |
inbox.getAttachment(emailId, attachmentId, options?) | Get presigned URL for attachment |
inbox.getRaw(emailId) | Get presigned URL for raw MIME email |
inbox.delete(emailId) | Delete email and all associated files |
Track the delivery lifecycle of every email. Requires event tracking infrastructure deployed with wraps email init (Production or Enterprise preset).
Initialize with Events
const email = new WrapsEmail({ historyTableName: 'wraps-email-history',});Get Email Status
// Get full status and event timeline for an emailconst status = await email.events.get('message-id-from-send');if (status) { console.log('Status:', status.status); // 'delivered', 'opened', 'bounced', etc. console.log('From:', status.from); console.log('To:', status.to); console.log('Subject:', status.subject); console.log('Sent at:', new Date(status.sentAt)); console.log('Last event:', new Date(status.lastEventAt)); // Full event timeline for (const event of status.events) { console.log(` ${event.type} at ${new Date(event.timestamp)}`); if (event.metadata) console.log(' metadata:', event.metadata); }}List Emails with Status
// List recent emails with status (paginated)const { emails, nextToken } = await email.events.list({ accountId: '123456789012', startTime: new Date('2025-01-01'), maxResults: 20,});for (const item of emails) { console.log(`${item.messageId}: ${item.status} - ${item.subject}`);}// Fetch next pageif (nextToken) { const nextPage = await email.events.list({ accountId: '123456789012', continuationToken: nextToken, });}| Status | Description |
|---|---|
sent | Email accepted by SES |
delivered | Email delivered to recipient's mail server |
opened | Recipient opened the email (tracking pixel) |
clicked | Recipient clicked a link in the email |
bounced | Email bounced (hard or soft bounce) |
complained | Recipient marked as spam |
suppressed | Email suppressed by SES (on suppression list) |
Negative statuses (bounced, complained, suppressed) always override positive ones. Status is derived from the highest-priority event.
| Method | Description |
|---|---|
events.get(messageId) | Get full status and event timeline for an email |
events.list(options) | List emails with status, filtered by account and time range |
Manage the SES account-level suppression list. Emails on this list are automatically blocked from sending. The suppression API is always available — no extra configuration needed.
Check if Suppressed
// Check if an email is suppressedconst entry = await email.suppression.get('user@example.com');if (entry) { console.log('Suppressed:', entry.email); console.log('Reason:', entry.reason); // 'BOUNCE' or 'COMPLAINT' console.log('Since:', entry.lastUpdated); console.log('Message ID:', entry.messageId); // original bounce/complaint message} else { console.log('Email is not suppressed');}Add to Suppression List
// Manually add an email to the suppression listawait email.suppression.add('bad-address@example.com', 'BOUNCE');// Also works for complaintsawait email.suppression.add('unsubscribed@example.com', 'COMPLAINT');Remove from Suppression List
// Remove from suppression list (idempotent — won't throw if not found)await email.suppression.remove('user@example.com');List Suppressed Emails
// List all suppressed emails (paginated)const { entries, nextToken } = await email.suppression.list({ reason: 'BOUNCE', // optional: filter by reason startDate: new Date('2025-01-01'), // optional maxResults: 100, // default: 100, max: 1000});for (const entry of entries) { console.log(`${entry.email}: ${entry.reason} (since ${entry.lastUpdated})`);}// Paginateif (nextToken) { const nextPage = await email.suppression.list({ continuationToken: nextToken, });}| Method | Description |
|---|---|
suppression.get(email) | Check if an email is suppressed (returns null if not) |
suppression.add(email, reason) | Add email to suppression list |
suppression.remove(email) | Remove from suppression list (idempotent) |
suppression.list(options?) | List suppressed emails with filters and pagination |
The SDK throws typed errors for different failure scenarios.
| Error | Description |
|---|---|
ValidationError | Invalid input parameters (includes field property) |
SESError | AWS SES error (includes code, requestId, retryable properties) |
DynamoDBError | DynamoDB error from events API (includes code, requestId, retryable properties) |
import { WrapsEmail, SESError, DynamoDBError, ValidationError } from '@wraps.dev/email';try { await email.send({ ... });} catch (error) { if (error instanceof ValidationError) { // Invalid email address, missing required fields, etc. console.error('Validation error:', error.message); console.error('Field:', error.field); } else if (error instanceof SESError) { // AWS SES error (rate limit, unverified sender, etc.) console.error('SES error:', error.message); console.error('Code:', error.code); // 'MessageRejected', 'Throttling' console.error('Request ID:', error.requestId); console.error('Retryable:', error.retryable); } else if (error instanceof DynamoDBError) { // DynamoDB error (events API) console.error('DynamoDB error:', error.message); console.error('Code:', error.code); console.error('Retryable:', error.retryable); }}The SDK is written in TypeScript and provides full type safety out of the box.
import { WrapsEmail, SendEmailParams, SendEmailResult, SendTemplateParams,} from '@wraps.dev/email';const email = new WrapsEmail();// TypeScript validates all parametersconst params: SendEmailParams = { from: 'hello@yourdomain.com', to: 'user@example.com', subject: 'Test', html: '<p>Test</p>',};const result: SendEmailResult = await email.send(params);Important defaults and limits to keep in mind:
retryable is trueCheck out the package on npm for the latest version and changelog.
View PackageExplore the source code, report issues, or contribute.
View SourceLearn how to build beautiful email templates with React.
Learn More