import * as pulumi from '@pulumi/pulumi'; import * as aws from '@pulumi/aws'; import { VercelOIDCConfig, OIDCConfig, DNSConfig, ResolvedTrackingConfig, EventsConfig, ArchivingConfig, SMTPConfig, SuppressionListConfig, WebhookConfig, TrackingConfig, SESEventType } from '@wraps/core'; export { ArchiveRetention, ArchivingConfig, EventsConfig, OIDCConfig, ResolvedTrackingConfig, SESEventType, SMTPConfig, SuppressionListConfig, SuppressionReason, TrackingConfig, VercelOIDCConfig, WebhookConfig, convertToSMTPPassword } from '@wraps/core'; /** * Transform functions to customize underlying resources before creation. * Each function receives the default resource args and returns modified args. */ type TransformFunctions = { /** Transform the IAM role resource */ role?: (args: aws.iam.RoleArgs) => aws.iam.RoleArgs; /** Transform the OIDC provider resource */ oidcProvider?: (args: aws.iam.OpenIdConnectProviderArgs) => aws.iam.OpenIdConnectProviderArgs; /** Transform the SES configuration set resource */ configSet?: (args: aws.ses.ConfigurationSetArgs) => aws.ses.ConfigurationSetArgs; /** Transform the SES domain identity resource */ domainIdentity?: (args: aws.ses.DomainIdentityArgs) => aws.ses.DomainIdentityArgs; /** Transform the DynamoDB table resource */ table?: (args: aws.dynamodb.TableArgs) => aws.dynamodb.TableArgs; /** Transform the SQS queue resource */ queue?: (args: aws.sqs.QueueArgs) => aws.sqs.QueueArgs; /** Transform the SQS dead letter queue resource */ dlq?: (args: aws.sqs.QueueArgs) => aws.sqs.QueueArgs; /** Transform the Lambda function resource */ lambda?: (args: aws.lambda.FunctionArgs) => aws.lambda.FunctionArgs; /** Transform the EventBridge rule resource */ eventRule?: (args: aws.cloudwatch.EventRuleArgs) => aws.cloudwatch.EventRuleArgs; /** Transform the ACM certificate resource */ certificate?: (args: aws.acm.CertificateArgs) => aws.acm.CertificateArgs; /** Transform the CloudFront distribution resource */ distribution?: (args: aws.cloudfront.DistributionArgs) => aws.cloudfront.DistributionArgs; }; /** * Arguments for creating a WrapsEmail component. * * Following SST's pattern of composition over presets: * - Minimal required config - just provide what you need * - Sensible defaults for everything else * - Transform functions for resource customization * - `.nodes` exposure for underlying resource access */ type WrapsEmailArgs = { /** * Vercel OIDC configuration for role assumption. * Required if deploying to Vercel. * Mutually exclusive with `oidc`. */ vercel?: pulumi.Input; /** * Alternative OIDC provider configuration (GitHub Actions, GitLab, etc.) * Use this if not deploying to Vercel. * Mutually exclusive with `vercel`. */ oidc?: pulumi.Input; /** * Primary sending domain. If provided, creates SES domain identity with DKIM. * DKIM tokens will be output for DNS configuration. */ domain?: pulumi.Input; /** * DNS provider configuration for automatic DNS record creation. * Supports Route53, Cloudflare, and Vercel. * If not provided, DNS records are output for manual creation. * * @example Route53 * ```typescript * dns: { provider: "route53", hostedZoneId: "Z1234567890" } * ``` * * @example Cloudflare * ```typescript * dns: { provider: "cloudflare", zoneId: "abc123", apiToken: pulumi.secret("token") } * ``` * * @example Vercel * ```typescript * dns: { provider: "vercel", apiToken: pulumi.secret("token"), teamId: "team_xxx" } * ``` */ dns?: pulumi.Input; /** * MAIL FROM subdomain for improved deliverability. * Combined with domain: "mail" + "example.com" = "mail.example.com" * @default "mail" (if domain is provided) */ mailFromSubdomain?: pulumi.Input; /** * Open/click tracking configuration. * @default { enabled: true, opens: true, clicks: true } */ tracking?: pulumi.Input; /** * Event tracking and history storage. * When enabled, deploys EventBridge → SQS → Lambda → DynamoDB pipeline. */ events?: pulumi.Input; /** * Email archiving via AWS Mail Manager. * Stores full email content for compliance/debugging. */ archiving?: pulumi.Input; /** * SMTP credentials for legacy systems (WordPress, PHP, etc.) * Creates IAM user with SES send permissions. * Credentials are shown once at creation - store securely! */ smtp?: pulumi.Input; /** * Suppression list configuration. * Automatically suppresses recipients who bounce or complain. * @default { enabled: true, reasons: ["BOUNCE", "COMPLAINT"] } */ suppressionList?: pulumi.Input; /** * Enable SES reputation metrics dashboard. * @default true */ reputationMetrics?: pulumi.Input; /** * Require TLS encryption for all outbound emails. * @default false */ tlsRequired?: pulumi.Input; /** * Enable dedicated IP address. * Adds ~$25/month to AWS costs. * @default false */ dedicatedIp?: pulumi.Input; /** * Enable sending on the SES configuration set. * @default true */ sendingEnabled?: pulumi.Input; /** * Webhook configuration for Wraps platform integration. * Forwards events to Wraps dashboard for unified analytics. * Only needed if using Wraps hosted dashboard. */ webhook?: pulumi.Input; /** * Tags to apply to all resources. */ tags?: pulumi.Input>; /** * Transform functions to customize underlying resources. * Receives the resource args before creation, return modified args. */ transform?: TransformFunctions; }; /** * Resolved configuration after applying defaults */ type ResolvedConfig = { vercel?: VercelOIDCConfig; oidc?: OIDCConfig; domain?: string; dns?: DNSConfig; mailFromSubdomain: string; tracking: ResolvedTrackingConfig; events?: EventsConfig; archiving?: ArchivingConfig; smtp?: SMTPConfig; suppressionList: Required; reputationMetrics: boolean; tlsRequired: boolean; dedicatedIp: boolean; sendingEnabled: boolean; webhook?: WebhookConfig; tags: Record; }; /** * Nodes exposed by the WrapsEmail component */ type WrapsEmailNodes = { /** IAM role for SDK authentication */ role: aws.iam.Role; /** OIDC provider (if Vercel or custom OIDC) */ oidcProvider?: aws.iam.OpenIdConnectProvider; /** SES configuration set */ configSet: aws.ses.ConfigurationSet; /** SES domain identity (if domain is provided) */ domainIdentity?: aws.ses.DomainIdentity; /** DKIM records (if domain is provided) */ domainDkim?: aws.ses.DomainDkim; /** DynamoDB table for email history (if events.storeHistory is true) */ table?: aws.dynamodb.Table; /** SQS queue for events (if events is configured) */ queue?: aws.sqs.Queue; /** SQS dead letter queue (if events is configured) */ dlq?: aws.sqs.Queue; /** Lambda function for event processing (if events.storeHistory is true) */ lambda?: aws.lambda.Function; /** EventBridge rule (if events is configured) */ eventRule?: aws.cloudwatch.EventRule; /** ACM certificate (if tracking.httpsEnabled is true) */ certificate?: aws.acm.Certificate; /** CloudFront distribution (if tracking.httpsEnabled is true) */ distribution?: aws.cloudfront.Distribution; /** SMTP IAM user (if smtp.enabled is true) */ smtpUser?: aws.iam.User; /** SMTP access key (if smtp.enabled is true) */ smtpAccessKey?: aws.iam.AccessKey; }; /** * Outputs from the WrapsEmail component */ type WrapsEmailOutputs = { /** IAM role ARN for SDK authentication */ roleArn: pulumi.Output; /** AWS region */ region: pulumi.Output; /** SES configuration set name */ configSetName: pulumi.Output; /** Primary domain (if configured) */ domain: pulumi.Output; /** DKIM tokens for DNS configuration */ dkimTokens: pulumi.Output; /** MAIL FROM domain (if configured) */ mailFromDomain: pulumi.Output; /** DynamoDB table name for email history */ tableName: pulumi.Output; /** SQS queue URL for events */ queueUrl: pulumi.Output; /** SQS dead letter queue URL */ dlqUrl: pulumi.Output; /** Lambda function ARN */ lambdaArn: pulumi.Output; /** Custom tracking domain (if configured) */ customTrackingDomain: pulumi.Output; /** Whether HTTPS tracking is enabled */ httpsTrackingEnabled: pulumi.Output; /** CloudFront distribution domain (if HTTPS tracking enabled) */ cloudFrontDomain: pulumi.Output; /** ACM certificate validation records (if HTTPS tracking enabled) */ acmCertificateValidationRecords: pulumi.Output | undefined>; /** Mail Manager Archive ARN (if archiving enabled) */ archiveArn: pulumi.Output; /** Whether archiving is enabled */ archivingEnabled: pulumi.Output; /** SMTP IAM user ARN (if SMTP enabled) */ smtpUserArn: pulumi.Output; /** SMTP username (IAM access key ID) - shown once! */ smtpUsername: pulumi.Output; /** SMTP password (derived from secret key) - shown once! */ smtpPassword: pulumi.Output; /** SMTP endpoint */ smtpEndpoint: pulumi.Output; /** Environment variables to set in your application */ envVars: pulumi.Output<{ WRAPS_AWS_ROLE_ARN: string; WRAPS_AWS_REGION: string; WRAPS_CONFIG_SET?: string; }>; }; /** * WrapsEmail - Pulumi component for deploying Wraps email infrastructure * * Following SST's pattern of composition over presets: * - Minimal required config - just provide what you need * - Sensible defaults for everything else * - Transform functions for resource customization * - `.nodes` exposure for underlying resource access * * @example * ```typescript * // Minimal - just Vercel OIDC for sending * const email = new WrapsEmail("email", { * vercel: { teamSlug: "my-team", projectName: "my-app" }, * }); * * // With domain and event tracking * 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: "SIX_MONTHS", * }, * }); * * // Access underlying resources * email.nodes.table?.arn; // For custom IAM policies * email.nodes.queue?.url; // For custom consumers * ``` */ declare class WrapsEmail extends pulumi.ComponentResource { /** * Underlying resources exposed for advanced use cases */ readonly nodes: WrapsEmailNodes; /** IAM role ARN for SDK authentication */ readonly roleArn: pulumi.Output; /** AWS region */ readonly region: pulumi.Output; /** SES configuration set name */ readonly configSetName: pulumi.Output; /** Primary domain (if configured) */ readonly domain: pulumi.Output; /** DKIM tokens for DNS configuration */ readonly dkimTokens: pulumi.Output; /** MAIL FROM domain (if configured) */ readonly mailFromDomain: pulumi.Output; /** DynamoDB table name for email history */ readonly tableName: pulumi.Output; /** SQS queue URL for events */ readonly queueUrl: pulumi.Output; /** SQS dead letter queue URL */ readonly dlqUrl: pulumi.Output; /** Lambda function ARN */ readonly lambdaArn: pulumi.Output; /** Custom tracking domain (if configured) */ readonly customTrackingDomain: pulumi.Output; /** Whether HTTPS tracking is enabled */ readonly httpsTrackingEnabled: pulumi.Output; /** CloudFront distribution domain (if HTTPS tracking enabled) */ readonly cloudFrontDomain: pulumi.Output; /** ACM certificate validation records (if HTTPS tracking enabled) */ readonly acmCertificateValidationRecords: pulumi.Output | undefined>; /** Mail Manager Archive ARN (if archiving enabled) */ readonly archiveArn: pulumi.Output; /** Whether archiving is enabled */ readonly archivingEnabled: pulumi.Output; /** SMTP IAM user ARN (if SMTP enabled) */ readonly smtpUserArn: pulumi.Output; /** SMTP username (IAM access key ID) - shown once! */ readonly smtpUsername: pulumi.Output; /** SMTP password (derived from secret key) - shown once! */ readonly smtpPassword: pulumi.Output; /** SMTP endpoint */ readonly smtpEndpoint: pulumi.Output; /** Environment variables to set in your application */ readonly envVars: pulumi.Output<{ WRAPS_AWS_ROLE_ARN: string; WRAPS_AWS_REGION: string; WRAPS_CONFIG_SET?: string; }>; constructor(name: string, args: WrapsEmailArgs, opts?: pulumi.ComponentResourceOptions); } /** * ACM certificate result */ type ACMResult = { certificate: aws.acm.Certificate; certificateValidation?: aws.acm.CertificateValidation; validationRecords: pulumi.Output>; }; /** * Create ACM certificate for custom tracking domain * * IMPORTANT: CloudFront requires ACM certificates to be created in us-east-1 region. * This function creates the certificate in us-east-1 regardless of the SES region. * * If a Route53 hosted zone ID is provided, DNS validation records will be created * automatically and we'll wait for validation. Otherwise, validation records are * returned for manual creation. */ declare function createACMCertificate(name: string, domain: string, hostedZoneId: string | undefined, tags: Record, transform: TransformFunctions["certificate"] | undefined, opts?: pulumi.ComponentResourceOptions): ACMResult; /** * CloudFront distribution result */ type CloudFrontResult = { distribution: aws.cloudfront.Distribution; domainName: pulumi.Output; webAcl?: aws.wafv2.WebAcl; }; /** * Create CloudFront distribution for HTTPS tracking domain * * This creates a CloudFront distribution that sits in front of AWS SES's tracking endpoint * (r.{region}.awstrack.me) and provides HTTPS support with a custom domain and SSL certificate. * Optionally creates a WAF Web ACL with rate limiting for security. */ declare function createCloudFrontTracking(name: string, customTrackingDomain: string, region: pulumi.Output, certificateArn: pulumi.Output, wafEnabled: boolean, tags: Record, transform: TransformFunctions["distribution"] | undefined, opts?: pulumi.ComponentResourceOptions): CloudFrontResult; /** * Create HTTPS tracking resources (ACM + CloudFront) * * This is the main entry point for setting up HTTPS tracking. * It creates: * 1. ACM certificate (in us-east-1 for CloudFront) * 2. Optional Route53 validation record * 3. CloudFront distribution * 4. Optional WAF Web ACL */ declare function createHTTPSTracking(name: string, config: { customTrackingDomain: string; region: pulumi.Output; hostedZoneId?: string; wafEnabled: boolean; }, tags: Record, transform: TransformFunctions | undefined, opts?: pulumi.ComponentResourceOptions): { cloudfront: CloudFrontResult; acmCertificateArn: pulumi.Output; acmValidationRecords: pulumi.Output>; }; /** * DynamoDB table result */ type DynamoDBResult = { table: aws.dynamodb.Table; }; /** * SQS resources result */ type SQSResult = { queue: aws.sqs.Queue; dlq: aws.sqs.Queue; }; /** * EventBridge resources result */ type EventBridgeResult = { rule: aws.cloudwatch.EventRule; target: aws.cloudwatch.EventTarget; queuePolicy: aws.sqs.QueuePolicy; webhookConnection?: aws.cloudwatch.EventConnection; webhookApiDestination?: aws.cloudwatch.EventApiDestination; webhookTarget?: aws.cloudwatch.EventTarget; }; /** * Create DynamoDB table for email history */ declare function createHistoryTable(name: string, tags: Record, transform?: TransformFunctions["table"], opts?: pulumi.ComponentResourceOptions): DynamoDBResult; /** * Create SQS queue with Dead Letter Queue for event processing */ declare function createEventQueues(name: string, tags: Record, transform?: { queue?: TransformFunctions["queue"]; dlq?: TransformFunctions["dlq"]; }, opts?: pulumi.ComponentResourceOptions): SQSResult; /** * Create EventBridge rule and targets for routing SES events */ declare function createEventBridgeRule(name: string, queueArn: pulumi.Output, queueUrl: pulumi.Output, tags: Record, webhook?: WebhookConfig, transform?: TransformFunctions["eventRule"], opts?: pulumi.ComponentResourceOptions): EventBridgeResult; /** * Create all event tracking infrastructure */ declare function createEventTracking(name: string, config: ResolvedConfig, tags: Record, transform?: TransformFunctions, opts?: pulumi.ComponentResourceOptions): { table?: aws.dynamodb.Table; queue: aws.sqs.Queue; dlq: aws.sqs.Queue; eventRule: aws.cloudwatch.EventRule; eventTarget: aws.cloudwatch.EventTarget; webhookConnection?: aws.cloudwatch.EventConnection; webhookApiDestination?: aws.cloudwatch.EventApiDestination; webhookTarget?: aws.cloudwatch.EventTarget; }; /** * IAM role configuration */ type IAMRoleConfig = { vercel?: VercelOIDCConfig; oidc?: OIDCConfig; oidcProvider?: aws.iam.OpenIdConnectProvider; config: ResolvedConfig; }; /** * IAM role result */ type IAMRoleResult = { role: aws.iam.Role; policy: aws.iam.RolePolicy; }; /** * Create IAM role for email infrastructure */ declare function createIAMRole(name: string, roleConfig: IAMRoleConfig, tags: Record, transform?: TransformFunctions["role"], opts?: pulumi.ComponentResourceOptions): IAMRoleResult; /** * Lambda function result */ type LambdaResult = { lambda: aws.lambda.Function; lambdaRole: aws.iam.Role; eventSourceMapping: aws.lambda.EventSourceMapping; }; /** * Create Lambda function for processing SES events */ declare function createEventProcessor(name: string, tableName: pulumi.Output, queueArn: pulumi.Output, accountId: pulumi.Output, retentionDays: number, tags: Record, transform?: TransformFunctions["lambda"], opts?: pulumi.ComponentResourceOptions): LambdaResult; /** * Mail Manager archive result * * Cost: $2/GB ingestion + $0.19/GB/month storage * See: https://docs.aws.amazon.com/ses/latest/dg/eb-archiving.html */ type MailManagerResult = { /** The archive ARN */ archiveArn: pulumi.Output; /** Whether archiving is enabled */ enabled: pulumi.Output; }; /** * Create a Mail Manager archive for storing full email content. * * Implements idempotent create-or-reuse: if an ACTIVE archive named * `wraps-email-archive` already exists it is reused rather than creating * a duplicate. On `ConflictException` (archive name blocked by one in * `PENDING_DELETION`) the provider retries with suffix `-2`, `-3`, … (up * to 10 attempts). * * Archiving is opt-in — when `config.archiving?.enabled` is false or * undefined this function returns disabled placeholder outputs without * creating any AWS resources. * * @param name - Component logical name (used only for the Pulumi resource node name; the AWS archive is always `wraps-email-archive`) * @param config - Resolved component configuration (reads `config.archiving`) * @param configSetName - SES configuration set to associate the archive with * @param region - AWS region * @param tags - Tags applied to the created archive * @param opts - Pulumi resource options */ declare function createMailManagerArchive(name: string, config: ResolvedConfig, configSetName: pulumi.Input, region: pulumi.Input, tags: Record, opts?: pulumi.ComponentResourceOptions): MailManagerResult; /** * OIDC provider result */ type OIDCProviderResult = { provider: aws.iam.OpenIdConnectProvider; }; /** * Create Vercel OIDC provider for AssumeRoleWithWebIdentity */ declare function createVercelOIDCProvider(name: string, config: VercelOIDCConfig, tags: Record, transform?: TransformFunctions["oidcProvider"], opts?: pulumi.ComponentResourceOptions): OIDCProviderResult; /** * Create custom OIDC provider (GitHub Actions, GitLab, etc.) */ declare function createCustomOIDCProvider(name: string, config: OIDCConfig, tags: Record, transform?: TransformFunctions["oidcProvider"], opts?: pulumi.ComponentResourceOptions): OIDCProviderResult; /** * SES resources result */ type SESResourcesResult = { configSet: aws.ses.ConfigurationSet; eventDestination?: aws.sesv2.ConfigurationSetEventDestination; domainIdentity?: aws.ses.DomainIdentity; domainDkim?: aws.ses.DomainDkim; mailFromAttributes?: aws.sesv2.EmailIdentityMailFromAttributes; dkimTokens: pulumi.Output; }; /** * Create SES configuration set */ declare function createConfigSet(name: string, _config: ResolvedConfig, _tags: Record, transform?: TransformFunctions["configSet"], opts?: pulumi.ComponentResourceOptions): aws.ses.ConfigurationSet; /** * Create SES configuration set with v2 API (supports more options) */ declare function createConfigSetV2(name: string, config: ResolvedConfig, tags: Record, opts?: pulumi.ComponentResourceOptions): aws.sesv2.ConfigurationSet; /** * Resolve the `matchingEventTypes` for the SES EventBridge event destination * from `events.types`. An empty array (or undefined) means "all", matching * `packages/cli`'s `resolveMatchingEventTypes` — see plan 183. Extracted * from the two-step `config.events.types || ALL_EVENT_TYPES` (here) plus * `eventTypes.length > 0 ? eventTypes : ALL_EVENT_TYPES` (previously inline * in `createEventDestination`) so the composed default-set derivation is a * single pure function, testable without constructing any Pulumi resource. */ declare function resolveMatchingEventTypes(eventTypes?: SESEventType[]): SESEventType[]; /** * Create EventBridge event destination for SES events */ declare function createEventDestination(name: string, configSetName: pulumi.Input, eventTypes: SESEventType[], opts?: pulumi.ComponentResourceOptions): aws.sesv2.ConfigurationSetEventDestination; /** * Create SES domain identity */ declare function createDomainIdentity(name: string, domain: string, _tags: Record, transform?: TransformFunctions["domainIdentity"], opts?: pulumi.ComponentResourceOptions): { domainIdentity: aws.ses.DomainIdentity; domainDkim: aws.ses.DomainDkim; }; /** * Create SES domain identity with v2 API (supports configuration set linking) */ declare function createDomainIdentityV2(name: string, domain: string, configSetName: pulumi.Input, tags: Record, opts?: pulumi.ComponentResourceOptions): aws.sesv2.EmailIdentity; /** * Configure MAIL FROM domain for better deliverability */ declare function createMailFromAttributes(name: string, domain: string, mailFromDomain: string, domainIdentity: aws.ses.DomainIdentity | aws.sesv2.EmailIdentity, opts?: pulumi.ComponentResourceOptions): aws.sesv2.EmailIdentityMailFromAttributes; /** * Create all SES resources based on configuration */ declare function createSESResources(name: string, config: ResolvedConfig, tags: Record, _transform?: TransformFunctions, opts?: pulumi.ComponentResourceOptions): SESResourcesResult; /** * SMTP credentials result */ type SMTPResult = { iamUser: aws.iam.User; accessKey: aws.iam.AccessKey; smtpUsername: pulumi.Output; smtpPassword: pulumi.Output; smtpEndpoint: pulumi.Output; }; /** * Create SMTP credentials for legacy systems. * * Creates an IAM user with ses:SendRawEmail permission scoped to the * specified SES configuration set, along with access keys that are * converted to SMTP credentials. * * IMPORTANT: The SMTP password is shown once and cannot be retrieved later. * Store it securely! */ declare function createSMTPCredentials(name: string, configSetName: pulumi.Input, region: pulumi.Output, tags: Record, opts?: pulumi.ComponentResourceOptions): SMTPResult; export { type ResolvedConfig, type TransformFunctions, WrapsEmail, type WrapsEmailArgs, type WrapsEmailNodes, type WrapsEmailOutputs, createACMCertificate, createCloudFrontTracking, createConfigSet, createConfigSetV2, createCustomOIDCProvider, createDomainIdentity, createDomainIdentityV2, createEventBridgeRule, createEventDestination, createEventProcessor, createEventQueues, createEventTracking, createHTTPSTracking, createHistoryTable, createIAMRole, createMailFromAttributes, createMailManagerArchive, createSESResources, createSMTPCredentials, createVercelOIDCProvider, resolveMatchingEventTypes };