Pulumi Component Reference
Deploy Wraps infrastructure with Pulumi.
Pulumi component for deploying Wraps email infrastructure to your AWS account. Zero credentials stored, full AWS ownership, SST-style API with transform functions.
npm install @wraps.dev/pulumi
Requirements: Node.js 20+, Pulumi 3.x, @pulumi/aws 6.x or 7.x
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;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;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", }), },});| Property | Type | Description |
|---|---|---|
vercel | VercelOIDCConfig | Vercel OIDC configuration (teamSlug, projectName) |
oidc | OIDCConfig | Custom OIDC provider (GitHub Actions, GitLab, etc.) |
domain | string | Primary sending domain with DKIM |
dns | DNSConfig | DNS provider for automatic record creation |
mailFromSubdomain | string | MAIL FROM subdomain (default: "mail") |
events | EventsConfig | Event tracking and history storage |
smtp | SMTPConfig | SMTP credentials for legacy systems |
reputationMetrics | boolean | Enable SES reputation metrics (default: true) |
tags | Record<string, string> | Tags to apply to all resources |
transform | TransformFunctions | Transform functions for resource customization |
| Property | Type | Description |
|---|---|---|
roleArn | Output<string> | IAM role ARN for SDK authentication |
region | Output<string> | AWS region |
configSetName | Output<string> | SES configuration set name |
dkimTokens | Output<string[]> | DKIM tokens for DNS configuration |
tableName | Output<string> | DynamoDB table name (if history enabled) |
queueUrl | Output<string> | SQS queue URL (if events enabled) |
smtpUsername | Output<string> | SMTP username (if SMTP enabled) |
smtpPassword | Output<string> | SMTP password (if SMTP enabled) |
envVars | Output<object> | Environment variables for your app |
Transform functions let you customize underlying Pulumi resources before creation. Each function receives the default resource args and returns modified args.
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 }), },});| Function | Resource Type | When Available |
|---|---|---|
role | aws.iam.RoleArgs | Always |
oidcProvider | aws.iam.OpenIdConnectProviderArgs | If OIDC configured |
configSet | aws.ses.ConfigurationSetArgs | Always |
table | aws.dynamodb.TableArgs | If events.storeHistory |
queue | aws.sqs.QueueArgs | If events configured |
lambda | aws.lambda.FunctionArgs | If events.storeHistory |
distribution | aws.cloudfront.DistributionArgs | If HTTPS tracking |
Access the underlying Pulumi resources via the .nodes property for advanced customization.
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: "*", }], }),});| Property | Type | When Available |
|---|---|---|
role | aws.iam.Role | Always |
oidcProvider | aws.iam.OpenIdConnectProvider | If OIDC configured |
configSet | aws.ses.ConfigurationSet | Always |
domainIdentity | aws.ses.DomainIdentity | If domain configured |
table | aws.dynamodb.Table | If events.storeHistory |
queue | aws.sqs.Queue | If events configured |
lambda | aws.lambda.Function | If events.storeHistory |
distribution | aws.cloudfront.Distribution | If HTTPS tracking |
Automatically create DNS records with your preferred provider.
// 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", },});Use custom OIDC configuration for GitHub Actions or other providers.
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",});Convenience output for environment variables to configure your application.
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",// }After deploying, use the @wraps.dev/email SDK to send emails:
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>",});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.
wraps-email-role)| Grant | CLI (email init) | This library |
|---|---|---|
Suppression list management (ListSuppressedDestinations, GetSuppressedDestination, PutSuppressedDestination, DeleteSuppressedDestination) | Yes | No |
ses:GetSendQuota | No | Yes |
cloudwatch:ListMetrics | No | Yes |
Sending, DynamoDB history access, EventBridge, SQS, and Mail Manager archive access are granted identically by both.
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.
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.
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.
Check out the package on npm for the latest version and changelog.
View PackageLearn how to send emails with the @wraps.dev/email SDK.
SDK ReferenceLearn more about Pulumi and infrastructure as code.
Pulumi Docs