Wraps Logo
DocsHome
SDK Reference

@wraps.dev/email 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.

Installation

npm install @wraps.dev/email

Quick Start

TypeScriptexample.ts
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);

Initialization

Create a new WrapsEmail client. The SDK automatically detects AWS credentials from your environment.

Constructor
new WrapsEmail(config?: WrapsEmailConfig)

Options

  • 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-1
  • credentials (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.

Authentication Order

  1. Pre-configured client (client option)
  2. OIDC role assumption (roleArn option)
  3. Explicit credentials (credentials option)
  4. AWS credential chain (env vars, ~/.aws/credentials, IAM role)

Example with Options

TypeScriptconfig.ts
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

TypeScriptlocalstack.ts
// Testing with LocalStackconst email = new WrapsEmail({  region: 'us-east-1',  endpoint: 'http://localhost:4566',});

WrapsEmailConfig Type

TypeScriptconfig-type.ts
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 Email

Send an email using AWS SES through your Wraps infrastructure.

Method
email.send(params: SendEmailParams): Promise<SendEmailResult>

Parameters

ParameterTypeDescription
fromstringSender email address (must be verified)
tostring | string[]Recipient email address(es)
subjectstringEmail subject line
htmlstringHTML email body (optional)
textstringPlain text email body (optional)
reactReactElementReact.email component (optional)
attachmentsAttachment[]File attachments (optional)
tagsRecord<string, string>SES message tags (optional)
cc, bcc, replyTostring | string[]Additional recipients (optional)

Examples

Basic Email

TypeScriptbasic-email.ts
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

TypeScriptmultiple-recipients.ts
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

TypeScriptwith-tags.ts
// 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});

React.email Support

Use React.email components for beautiful, type-safe email templates. The SDK automatically renders React components to HTML and plain text.

Reactreact-email.tsx
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" />,});

Attachments

Send emails with file attachments (PDFs, images, documents, etc.). The SDK automatically handles MIME encoding.

TypeScriptattachments.ts
// 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 },  ],});

Attachment Options

FieldTypeDescription
filenamestringFilename with extension
contentBuffer | stringFile content (Buffer or base64 string)
contentTypestringMIME type (optional - auto-detected from filename)

Limits

  • Maximum 100 attachments per email
  • Maximum message size: 10 MB (AWS SES limit)
  • Works with both HTML and React.email components

Template Management

SES templates allow you to store reusable email designs with variables in your AWS account.

Create Template

TypeScriptcreate-template.ts
// 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

Reactcreate-from-react.tsx
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

TypeScriptmanage-templates.ts
// 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');

Available Methods

MethodDescription
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 Template

Send an email using a pre-defined SES template. Templates support variable substitution using {{variable}} syntax.

Method
email.sendTemplate(params: SendTemplateParams): Promise<SendEmailResult>
TypeScriptsend-template.ts
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 Bulk Template

Send personalized templated emails to multiple recipients (up to 50 per call). Each recipient can have unique template data merged with default values.

Method
email.sendBulkTemplate(params: SendBulkTemplateParams): Promise<SendBulkTemplateResult>
TypeScriptsend-bulk-template.ts
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 Batch

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.

Method
email.sendBatch(params: SendBatchParams): Promise<SendBatchResult>

Parameters

ParameterTypeDescription
fromstring | EmailAddressSender email address (must be verified)
entriesBatchEmailEntry[]List of recipients with unique content (max 100)
entries[].tostring | EmailAddressRecipient email address
entries[].subjectstringEmail subject for this entry
entries[].htmlstringHTML body (optional, mutually exclusive with react)
entries[].textstringPlain text body (optional)
entries[].reactReactElementReact.email component (optional)
entries[].tagsRecord<string, string>Per-entry SES tags (replaces defaults for this entry)
replyTostring | string[]Reply-to address(es), shared across all entries (optional)
tagsRecord<string, string>Default SES message tags (optional)
configurationSetNamestringConfiguration set for tracking (optional)

Response

FieldTypeDescription
resultsBatchEntryResult[]Per-entry results in the same order as input
successCountnumberNumber of entries sent successfully
failureCountnumberNumber of entries that failed

Example

TypeScriptsend-batch.ts
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}`);  }}

htmlToPlainText Utility

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.

Signature
htmlToPlainText(html: string): string
TypeScripthtml-to-text.ts
import { 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).

Inbox (Inbound Emails)

Read, reply to, and forward inbound emails. Requires inbound email infrastructure to be deployed with wraps email inbound init.

Initialize with Inbox

TypeScriptinbox-init.ts
const email = new WrapsEmail({  inboxBucketName: 'your-inbound-bucket-name',});

List Inbound Emails

TypeScriptinbox-list.ts
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

TypeScriptinbox-get.ts
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

TypeScriptinbox-reply.ts
// 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

TypeScriptinbox-forward.ts
// 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

TypeScriptinbox-attachment.ts
// 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

TypeScriptinbox-get-raw.ts
// 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

TypeScriptinbox-delete.ts
// Delete email and all associated files (parsed JSON, raw MIME, attachments)await email.inbox.delete('email-abc123');

Available Methods

MethodDescription
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

Email Events

Track the delivery lifecycle of every email. Requires event tracking infrastructure deployed with wraps email init (Production or Enterprise preset).

Initialize with Events

TypeScriptevents-init.ts
const email = new WrapsEmail({  historyTableName: 'wraps-email-history',});

Get Email Status

TypeScriptevents-get.ts
// 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

TypeScriptevents-list.ts
// 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 Values

StatusDescription
sentEmail accepted by SES
deliveredEmail delivered to recipient's mail server
openedRecipient opened the email (tracking pixel)
clickedRecipient clicked a link in the email
bouncedEmail bounced (hard or soft bounce)
complainedRecipient marked as spam
suppressedEmail suppressed by SES (on suppression list)

Negative statuses (bounced, complained, suppressed) always override positive ones. Status is derived from the highest-priority event.

Available Methods

MethodDescription
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

Suppression List

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

TypeScriptsuppression-get.ts
// 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

TypeScriptsuppression-add.ts
// 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

TypeScriptsuppression-remove.ts
// Remove from suppression list (idempotent — won't throw if not found)await email.suppression.remove('user@example.com');

List Suppressed Emails

TypeScriptsuppression-list.ts
// 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,  });}

Available Methods

MethodDescription
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

Error Handling

The SDK throws typed errors for different failure scenarios.

ErrorDescription
ValidationErrorInvalid input parameters (includes field property)
SESErrorAWS SES error (includes code, requestId, retryable properties)
DynamoDBErrorDynamoDB error from events API (includes code, requestId, retryable properties)
TypeScripterror-handling.ts
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);  }}

TypeScript Support

The SDK is written in TypeScript and provides full type safety out of the box.

TypeScripttyped-usage.ts
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);

SDK Defaults & Limits

Important defaults and limits to keep in mind:

  • No automatic retry on failure — implement your own retry logic if retryable is true
  • Default pagination: 50 items per page
  • Presigned URL expiry: 1 hour (for attachments)
  • Bulk send limit: 50 destinations per call
  • Attachment limit: 100 per email (10 MB total message size)

Next Steps

View on npm

Check out the package on npm for the latest version and changelog.

View Package
View on GitHub

Explore the source code, report issues, or contribute.

View Source
React Email

Learn how to build beautiful email templates with React.

Learn More