# @wraps.dev/email SDK Reference

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

```bash
npm install @wraps.dev/email
# or: pnpm add @wraps.dev/email | yarn add @wraps.dev/email | bun add @wraps.dev/email
```

## Quick Start

```typescript
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

The SDK automatically detects AWS credentials from your environment.

### Constructor
```typescript
new WrapsEmail(config?: WrapsEmailConfig)
```

### Configuration Options

| Option | Type | Description |
|--------|------|-------------|
| `client` | SESClient | Pre-configured SES client (takes precedence) |
| `region` | string | AWS region (default: `us-east-1`) |
| `credentials` | object or AwsCredentialIdentityProvider | Explicit AWS credentials |
| `roleArn` | string | IAM role for OIDC assumption |
| `roleSessionName` | string | Session name for AssumeRole |
| `endpoint` | string | Custom endpoint (e.g., LocalStack) |
| `inboxBucketName` | string | S3 bucket for inbound email storage |
| `historyTableName` | string | DynamoDB table for email event history |
| `dynamodbClient` | DynamoDBDocumentClient | Pre-configured DynamoDB client |
| `sesv2Client` | SESv2Client | Pre-configured SES v2 client |
| `s3Client` | S3Client | Pre-configured S3 client for inbox |

### 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)

### Examples

```typescript
// Explicit credentials
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
const email = new WrapsEmail({
  region: 'us-east-1',
  endpoint: process.env.LOCALSTACK_URL, // e.g. localhost:4566
});
```

## Send Email

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

### Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `from` | string | Sender email address (must be verified) |
| `to` | string or string[] | Recipient email address(es) |
| `subject` | string | Email subject line |
| `html` | string | HTML email body (optional if text or react provided) |
| `text` | string | Plain text fallback (optional — auto-generated from html if omitted) |
| `react` | ReactElement | React.email component (optional) |
| `cc` | string or string[] | CC recipients (optional) |
| `bcc` | string or string[] | BCC recipients (optional) |
| `replyTo` | string or string[] | Reply-to address(es) (optional) |
| `attachments` | Attachment[] | File attachments (optional) |
| `tags` | Record<string, string> | SES message tags (optional) |
| `configurationSetName` | string | Configuration set for tracking (optional) |

### Response

| Field | Type | Description |
|-------|------|-------------|
| `messageId` | string | Unique message identifier from SES |
| `requestId` | string | AWS request ID |

### Examples

```typescript
// 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);

// Multiple recipients
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 SES tags
await 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',
});
```

## React.email Support

```typescript
import { WelcomeEmail } from './emails/Welcome';

await email.send({
  from: 'hello@yourdomain.com',
  to: 'user@example.com',
  subject: 'Welcome to our platform',
  react: <WelcomeEmail name="John" orderId="12345" />,
});
```

The SDK automatically renders React components to HTML and plain text.

## Attachments

```typescript
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 from filename
    },
    { filename: 'chart.png', content: imageBuffer },
  ],
});
```

Limits: 100 attachments per email, 10 MB total message size.

## Template Management

SES templates stored in your AWS account with `{{variable}}` substitution.

```typescript
// Create a template
await email.templates.create({
  name: 'welcome-email',
  subject: 'Welcome to {{companyName}}, {{name}}!',
  html: '<h1>Welcome {{name}}!</h1><p>Click: <a href="{{confirmUrl}}">Confirm</a></p>',
  text: 'Welcome {{name}}! Click to confirm: {{confirmUrl}}',
});

// Create from React.email component
await email.templates.createFromReact({
  name: 'welcome-email-v2',
  subject: 'Welcome to {{companyName}}, {{name}}!',
  react: <WelcomeEmailTemplate />,
});

// List, get, update, delete
const templates = await email.templates.list();
const template = await email.templates.get('welcome-email');
await email.templates.update({ name: 'welcome-email', subject: 'Welcome aboard, {{name}}!', html: '...' });
await 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 Template

```typescript
email.sendTemplate(params: SendTemplateParams): Promise<SendEmailResult>
```

```typescript
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).

```typescript
email.sendBulkTemplate(params: SendBulkTemplateParams): Promise<SendBulkTemplateResult>
```

```typescript
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' },
});

result.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 call (max 100 entries). Unlike `sendBulkTemplate()`, no pre-created SES template needed — provide subject/HTML per recipient inline.

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

```typescript
const result = await email.sendBatch({
  from: 'hello@yourdomain.com',
  entries: [
    { to: 'alice@example.com', subject: 'Hi Alice', html: '<p>Your order #1001 has shipped.</p>' },
    { to: 'bob@example.com', subject: 'Hi Bob', html: '<p>Your order #1002 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. Used internally by `send()` when `text` is omitted.

```typescript
import { htmlToPlainText } from '@wraps.dev/email';

const text = htmlToPlainText('<h1>Welcome!</h1><p>Thanks for <a href="https://example.com">signing up</a>.</p>');
// Welcome!
//
// Thanks for signing up (https://example.com).
```

## Inbox (Inbound Emails)

Read, reply to, and forward inbound emails. Requires `wraps email inbound init`.

```typescript
const email = new WrapsEmail({ inboxBucketName: 'your-inbound-bucket-name' });

// List inbound emails
const { emails, nextToken } = await email.inbox.list({ maxResults: 20 });

// Get full email details
const inboundEmail = await email.inbox.get('email-abc123');
console.log('From:', inboundEmail.from.address);
console.log('Subject:', inboundEmail.subject);
console.log('Spam verdict:', inboundEmail.spamVerdict);

// Reply with threading headers
await email.inbox.reply('email-abc123', {
  from: 'support@yourdomain.com',
  text: 'Thanks for reaching out!',
  html: '<p>Thanks for reaching out!</p>',
});

// Forward
await email.inbox.forward('email-abc123', {
  from: 'noreply@yourdomain.com',
  to: 'team@yourdomain.com',
  addPrefix: '[Customer]',
});

// Get presigned URL for attachment
const url = await email.inbox.getAttachment('email-abc123', 'attachment-id', { expiresIn: 3600 });

// Delete email and all associated files
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 |
| `inbox.getRaw(emailId)` | Get presigned URL for raw MIME email |
| `inbox.delete(emailId)` | Delete email and all files |

## Email Events

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

```typescript
const email = new WrapsEmail({ historyTableName: 'wraps-email-history' });

// Get full status and event timeline
const status = await email.events.get('message-id-from-send');
if (status) {
  console.log('Status:', status.status);   // 'delivered', 'opened', 'bounced', etc.
  console.log('Sent at:', new Date(status.sentAt));
  for (const event of status.events) {
    console.log(`  ${event.type} at ${new Date(event.timestamp)}`);
  }
}

// List recent emails with status
const { emails, nextToken } = await email.events.list({
  accountId: '123456789012',
  startTime: new Date('2025-01-01'),
  maxResults: 20,
});
```

| Status | Description |
|--------|-------------|
| `sent` | Email accepted by SES |
| `delivered` | Delivered to recipient's mail server |
| `opened` | Recipient opened the email |
| `clicked` | Recipient clicked a link |
| `bounced` | Email bounced (hard or soft) |
| `complained` | Recipient marked as spam |
| `suppressed` | On the SES suppression list |

## Suppression List

```typescript
// Check if suppressed
const entry = await email.suppression.get('user@example.com');
if (entry) {
  console.log('Suppressed:', entry.reason); // 'BOUNCE' or 'COMPLAINT'
}

// Add manually
await email.suppression.add('bad-address@example.com', 'BOUNCE');

// Remove (idempotent)
await email.suppression.remove('user@example.com');

// List with filters
const { entries, nextToken } = await email.suppression.list({
  reason: 'BOUNCE',
  startDate: new Date('2025-01-01'),
  maxResults: 100,
});
```

## Error Handling

```typescript
import { WrapsEmail, SESError, DynamoDBError, ValidationError } from '@wraps.dev/email';

try {
  await email.send({ ... });
} catch (error) {
  if (error instanceof ValidationError) {
    console.error('Validation error:', error.message, 'Field:', error.field);
  } else if (error instanceof SESError) {
    console.error('SES error:', error.message, 'Code:', error.code, 'Retryable:', error.retryable);
  } else if (error instanceof DynamoDBError) {
    console.error('DynamoDB error:', error.message, 'Retryable:', error.retryable);
  }
}
```

## TypeScript Support

The SDK is written in TypeScript and provides full type safety:

```typescript
import { WrapsEmail, SendEmailParams, SendEmailResult } from '@wraps.dev/email';

const email = new WrapsEmail();
const 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

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

## Resources

- npm: https://www.npmjs.com/package/@wraps.dev/email
- GitHub: https://github.com/wraps-team/wraps-js
