Wraps Logo
DocsHome
Pulumi Reference

@wraps.dev/pulumi

Pulumi component for deploying Wraps email infrastructure to your AWS account. Zero credentials stored, full AWS ownership, SST-style API with transform functions.

Installation

npm install @wraps.dev/pulumi

Requirements: Node.js 20+, Pulumi 3.x, @pulumi/aws 6.x or 7.x

Quick Start

TypeScriptindex.ts
import { WrapsEmail } from "@wraps.dev/pulumi";// Minimal setup with Vercel OIDCconst email = new WrapsEmail("email", {  vercel: {    teamSlug: "my-team",    projectName: "my-app",  },});// Export the role ARN for your Vercel environmentexport const roleArn = email.roleArn;export const configSetName = email.configSetName;export const envVars = email.envVars;

With Domain and Event Tracking

TypeScriptwith-domain.ts
const email = new WrapsEmail("email", {  vercel: {    teamSlug: "my-team",    projectName: "my-app",  },  domain: "example.com",  events: {    types: ["SEND", "DELIVERY", "BOUNCE", "COMPLAINT", "OPEN", "CLICK"],    storeHistory: true,    retention: "90days",  },});// Export DKIM tokens for DNS configurationexport const dkimTokens = email.dkimTokens;

Full Configuration

TypeScriptfull-config.ts
const email = new WrapsEmail("email", {  // Authentication (choose one)  vercel: {    teamSlug: "my-team",    projectName: "my-app",  },  // Or custom OIDC:  // oidc: {  //   providerUrl: "https://token.actions.githubusercontent.com",  //   audience: "sts.amazonaws.com",  //   subjectPattern: "repo:my-org/my-repo:*",  // },  // Domain configuration  domain: "example.com",  mailFromSubdomain: "mail", // Creates mail.example.com  // DNS provider (auto-creates DNS records)  dns: {    provider: "route53",    hostedZoneId: "Z1234567890",  },  // Or Cloudflare:  // dns: { provider: "cloudflare", zoneId: "abc123", apiToken: pulumi.secret("token") },  // Event tracking  events: {    types: ["SEND", "DELIVERY", "BOUNCE", "COMPLAINT", "OPEN", "CLICK"],    storeHistory: true,    retention: "90days", // "7days" | "30days" | "90days" | "6months" | "1year" | "unlimited"  },  // Email settings  reputationMetrics: true,  tlsRequired: false,  sendingEnabled: true,  // Suppression list  suppressionList: {    enabled: true,    reasons: ["BOUNCE", "COMPLAINT"],  },  // SMTP credentials (for legacy systems)  smtp: {    enabled: false,  },  // Resource tags  tags: {    Environment: "production",  },  // Transform underlying resources  transform: {    table: (args) => ({      ...args,      billingMode: "PROVISIONED",    }),  },});

Constructor Args

PropertyTypeDescription
vercelVercelOIDCConfigVercel OIDC configuration (teamSlug, projectName)
oidcOIDCConfigCustom OIDC provider (GitHub Actions, GitLab, etc.)
domainstringPrimary sending domain with DKIM
dnsDNSConfigDNS provider for automatic record creation
mailFromSubdomainstringMAIL FROM subdomain (default: "mail")
eventsEventsConfigEvent tracking and history storage
smtpSMTPConfigSMTP credentials for legacy systems
reputationMetricsbooleanEnable SES reputation metrics (default: true)
tagsRecord<string, string>Tags to apply to all resources
transformTransformFunctionsTransform functions for resource customization

Outputs

PropertyTypeDescription
roleArnOutput<string>IAM role ARN for SDK authentication
regionOutput<string>AWS region
configSetNameOutput<string>SES configuration set name
dkimTokensOutput<string[]>DKIM tokens for DNS configuration
tableNameOutput<string>DynamoDB table name (if history enabled)
queueUrlOutput<string>SQS queue URL (if events enabled)
smtpUsernameOutput<string>SMTP username (if SMTP enabled)
smtpPasswordOutput<string>SMTP password (if SMTP enabled)
envVarsOutput<object>Environment variables for your app

Transform Functions

Transform functions let you customize underlying Pulumi resources before creation. Each function receives the default resource args and returns modified args.

TypeScripttransform.ts
const email = new WrapsEmail("email", {  vercel: { teamSlug: "my-team", projectName: "my-app" },  domain: "example.com",  events: { types: ["SEND"], storeHistory: true },  // Transform underlying Pulumi resources before creation  transform: {    // Customize IAM role    role: (args) => ({      ...args,      maxSessionDuration: 7200, // 2 hours    }),    // Customize DynamoDB table    table: (args) => ({      ...args,      billingMode: "PROVISIONED",      readCapacity: 5,      writeCapacity: 5,    }),    // Customize SQS queue    queue: (args) => ({      ...args,      visibilityTimeoutSeconds: 120,    }),    // Customize Lambda function    lambda: (args) => ({      ...args,      memorySize: 1024,      timeout: 60,    }),    // Customize CloudFront distribution    distribution: (args) => ({      ...args,      priceClass: "PriceClass_100", // US/Europe only    }),  },});

Available Transform Functions

FunctionResource TypeWhen Available
roleaws.iam.RoleArgsAlways
oidcProvideraws.iam.OpenIdConnectProviderArgsIf OIDC configured
configSetaws.ses.ConfigurationSetArgsAlways
tableaws.dynamodb.TableArgsIf events.storeHistory
queueaws.sqs.QueueArgsIf events configured
lambdaaws.lambda.FunctionArgsIf events.storeHistory
distributionaws.cloudfront.DistributionArgsIf HTTPS tracking

Accessing Underlying Resources

Access the underlying Pulumi resources via the .nodes property for advanced customization.

TypeScriptnodes.ts
const email = new WrapsEmail("email", { /* ... */ });// Access raw Pulumi resources via .nodesemail.nodes.role;           // aws.iam.Roleemail.nodes.configSet;      // aws.ses.ConfigurationSetemail.nodes.domainIdentity; // aws.ses.DomainIdentity (if domain)email.nodes.domainDkim;     // aws.ses.DomainDkim (if domain)email.nodes.table;          // aws.dynamodb.Table (if events.storeHistory)email.nodes.queue;          // aws.sqs.Queue (if events)email.nodes.dlq;            // aws.sqs.Queue (if events)email.nodes.lambda;         // aws.lambda.Function (if events.storeHistory)email.nodes.eventRule;      // aws.cloudwatch.EventRule (if events)email.nodes.oidcProvider;   // aws.iam.OpenIdConnectProvider (if OIDC)email.nodes.distribution;   // aws.cloudfront.Distribution (if HTTPS tracking)// Example: Add custom policy to roleconst customPolicy = new aws.iam.RolePolicy("custom", {  role: email.nodes.role.name,  policy: JSON.stringify({    Version: "2012-10-17",    Statement: [{      Action: ["ses:ListIdentities"],      Effect: "Allow",      Resource: "*",    }],  }),});

Available Nodes

PropertyTypeWhen Available
roleaws.iam.RoleAlways
oidcProvideraws.iam.OpenIdConnectProviderIf OIDC configured
configSetaws.ses.ConfigurationSetAlways
domainIdentityaws.ses.DomainIdentityIf domain configured
tableaws.dynamodb.TableIf events.storeHistory
queueaws.sqs.QueueIf events configured
lambdaaws.lambda.FunctionIf events.storeHistory
distributionaws.cloudfront.DistributionIf HTTPS tracking

DNS Providers

Automatically create DNS records with your preferred provider.

TypeScriptdns-providers.ts
// Route53 (automatic DNS records)const emailRoute53 = new WrapsEmail("email", {  domain: "example.com",  dns: {    provider: "route53",    hostedZoneId: "Z1234567890",  },});// Cloudflareconst emailCloudflare = new WrapsEmail("email", {  domain: "example.com",  dns: {    provider: "cloudflare",    zoneId: "abc123def456",    apiToken: pulumi.secret(process.env.CLOUDFLARE_API_TOKEN!),  },});// Vercel DNSconst emailVercel = new WrapsEmail("email", {  domain: "example.com",  dns: {    provider: "vercel",    apiToken: pulumi.secret(process.env.VERCEL_TOKEN!),    teamId: "team_xxx",  },});

GitHub Actions OIDC

Use custom OIDC configuration for GitHub Actions or other providers.

TypeScriptgithub-actions.ts
const email = new WrapsEmail("email", {  oidc: {    providerUrl: "https://token.actions.githubusercontent.com",    audience: "sts.amazonaws.com",    subjectPattern: "repo:my-org/my-repo:*",  },  domain: "example.com",});

Environment Variables Output

Convenience output for environment variables to configure your application.

TypeScriptenv-vars.ts
const email = new WrapsEmail("email", { /* ... */ });// Convenience output for environment variablesexport const envVars = email.envVars;// Returns:// {//   WRAPS_AWS_ROLE_ARN: "arn:aws:iam::123456789:role/wraps-email-role",//   WRAPS_AWS_REGION: "us-east-1",//   WRAPS_CONFIG_SET: "wraps-email-tracking",// }

Using with @wraps.dev/email SDK

After deploying, use the @wraps.dev/email SDK to send emails:

TypeScriptsend-email.ts
import { WrapsEmail } from "@wraps.dev/email";const email = new WrapsEmail();await email.send({  from: "hello@example.com",  to: "user@example.com",  subject: "Hello from Wraps!",  html: "<h1>Welcome!</h1>",});

Differences from the CLI

This library and npx @wraps.dev/cli email init both deploy the same email stack, but they are two independent implementations, not one codebase with two entry points. They are not identical. This section states the differences plainly, not as a case for either one.

IAM permissions on the deployed role (wraps-email-role)

GrantCLI (email init)This library
Suppression list management (ListSuppressedDestinations, GetSuppressedDestination, PutSuppressedDestination, DeleteSuppressedDestination)YesNo
ses:GetSendQuotaNoYes
cloudwatch:ListMetricsNoYes

Sending, DynamoDB history access, EventBridge, SQS, and Mail Manager archive access are granted identically by both.

No replay archive, no delivery-failure alarm

The CLI path creates a 30-day EventBridge archive (wraps-email-events-archive) of every raw SES event, so a broken pipeline can be repaired and replayed instead of losing events, plus a CloudWatch alarm on EventBridge delivery failures. This library creates neither. The BYOC page describes the event pipeline's replay path. That description is accurate for the CLI, not for this library.

No inbound email

The CLI supports receiving email (wraps email inbound init): an S3 bucket, a receiving Lambda, and an EventBridge rule for inbound mail. This library has no equivalent resources at all.

What CI checks, and what it does not

Nothing in this monorepo depends on this package, so no build or test run here exercises it against the CLI's actual behavior. A small set of guards keeps the two from drifting on the values that are supposed to be identical: physical resource names, the default SES event-type set, and the EventBridge rule's event pattern. Those guards do not cover IAM statement shape or feature coverage. The differences on this page are not something a test enforces.

Next Steps

View on npm

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

View Package
Email SDK

Learn how to send emails with the @wraps.dev/email SDK.

SDK Reference
Pulumi Docs

Learn more about Pulumi and infrastructure as code.

Pulumi Docs