@wraps.dev/cli 2.12.4 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -2068,6 +2068,17 @@ function calculateAlertingCost(config2) {
2068
2068
  description: `Reputation alerts (${numAlarms} CloudWatch alarms)`
2069
2069
  };
2070
2070
  }
2071
+ function calculateUserWebhookCost(config2, emailsPerMonth) {
2072
+ if (!config2.userWebhook?.enabled) {
2073
+ return;
2074
+ }
2075
+ const estimatedInvocations = emailsPerMonth * 2;
2076
+ const monthlyCost = estimatedInvocations / 1e6 * AWS_PRICING.EVENTBRIDGE_API_DEST_PER_MILLION;
2077
+ return {
2078
+ monthly: monthlyCost,
2079
+ description: "User webhook (API Destination invocations)"
2080
+ };
2081
+ }
2071
2082
  function calculateCosts(config2, emailsPerMonth = 1e4) {
2072
2083
  const tracking = calculateTrackingCost(config2);
2073
2084
  const reputationMetrics = calculateReputationMetricsCost(config2);
@@ -2078,8 +2089,9 @@ function calculateCosts(config2, emailsPerMonth = 1e4) {
2078
2089
  const waf = calculateWafCost(config2, emailsPerMonth);
2079
2090
  const smtpCredentials = calculateSMTPCredentialsCost(config2);
2080
2091
  const alerts = calculateAlertingCost(config2);
2092
+ const userWebhook = calculateUserWebhookCost(config2, emailsPerMonth);
2081
2093
  const sesEmailCost = Math.max(0, emailsPerMonth - FREE_TIER.SES_EMAILS) * AWS_PRICING.SES_PER_EMAIL;
2082
- const totalMonthlyCost = sesEmailCost + (tracking?.monthly || 0) + (reputationMetrics?.monthly || 0) + (eventTracking?.monthly || 0) + (dynamoDBHistory?.monthly || 0) + (emailArchiving?.monthly || 0) + (dedicatedIp?.monthly || 0) + (waf?.monthly || 0) + (smtpCredentials?.monthly || 0) + (alerts?.monthly || 0);
2094
+ const totalMonthlyCost = sesEmailCost + (tracking?.monthly || 0) + (reputationMetrics?.monthly || 0) + (eventTracking?.monthly || 0) + (dynamoDBHistory?.monthly || 0) + (emailArchiving?.monthly || 0) + (dedicatedIp?.monthly || 0) + (waf?.monthly || 0) + (smtpCredentials?.monthly || 0) + (alerts?.monthly || 0) + (userWebhook?.monthly || 0);
2083
2095
  return {
2084
2096
  tracking,
2085
2097
  reputationMetrics,
@@ -2090,6 +2102,7 @@ function calculateCosts(config2, emailsPerMonth = 1e4) {
2090
2102
  waf,
2091
2103
  smtpCredentials,
2092
2104
  alerts,
2105
+ userWebhook,
2093
2106
  total: {
2094
2107
  monthly: totalMonthlyCost,
2095
2108
  perEmail: AWS_PRICING.SES_PER_EMAIL,
@@ -2160,6 +2173,11 @@ function getCostSummary(config2, emailsPerMonth = 1e4) {
2160
2173
  ` - ${costs.alerts.description}: ${formatCost(costs.alerts.monthly)}`
2161
2174
  );
2162
2175
  }
2176
+ if (costs.userWebhook) {
2177
+ lines.push(
2178
+ ` - ${costs.userWebhook.description}: ${formatCost(costs.userWebhook.monthly)}`
2179
+ );
2180
+ }
2163
2181
  return lines.join("\n");
2164
2182
  }
2165
2183
  var AWS_PRICING, FREE_TIER;
@@ -2209,8 +2227,11 @@ var init_costs = __esm({
2209
2227
  // $5.00 per Web ACL per month
2210
2228
  WAF_RULE_PER_MONTH: 1,
2211
2229
  // $1.00 per rule per month
2212
- WAF_REQUESTS_PER_MILLION: 0.6
2230
+ WAF_REQUESTS_PER_MILLION: 0.6,
2213
2231
  // $0.60 per million requests
2232
+ // EventBridge API Destination pricing
2233
+ EVENTBRIDGE_API_DEST_PER_MILLION: 0.2
2234
+ // $0.20 per million API Destination invocations
2214
2235
  };
2215
2236
  FREE_TIER = {
2216
2237
  // SES: 3,000 emails/month for first 12 months (new AWS accounts only)
@@ -5068,10 +5089,10 @@ var init_constants = __esm({
5068
5089
  async function roleExists(roleName) {
5069
5090
  try {
5070
5091
  const { IAMClient: IAMClient4, GetRoleCommand: GetRoleCommand3 } = await import("@aws-sdk/client-iam");
5071
- const iam9 = new IAMClient4({
5092
+ const iam10 = new IAMClient4({
5072
5093
  region: getDefaultRegion()
5073
5094
  });
5074
- await iam9.send(new GetRoleCommand3({ RoleName: roleName }));
5095
+ await iam10.send(new GetRoleCommand3({ RoleName: roleName }));
5075
5096
  return true;
5076
5097
  } catch (error) {
5077
5098
  if (error instanceof Error && (error.name === "NoSuchEntityException" || error.Code === "NoSuchEntity" || error.Error?.Code === "NoSuchEntity")) {
@@ -5350,6 +5371,104 @@ var init_dist = __esm({
5350
5371
  }
5351
5372
  });
5352
5373
 
5374
+ // src/infrastructure/resources/eventbridge-user-webhook.ts
5375
+ var eventbridge_user_webhook_exports = {};
5376
+ __export(eventbridge_user_webhook_exports, {
5377
+ createUserWebhookResources: () => createUserWebhookResources
5378
+ });
5379
+ import * as aws6 from "@pulumi/aws";
5380
+ function createUserWebhookResources(config2) {
5381
+ const connection = new aws6.cloudwatch.EventConnection(
5382
+ "wraps-user-webhook-connection",
5383
+ {
5384
+ name: "wraps-user-webhook-connection",
5385
+ description: "Connection for user webhook endpoint",
5386
+ authorizationType: "API_KEY",
5387
+ authParameters: {
5388
+ apiKey: {
5389
+ key: "X-Wraps-Signature",
5390
+ value: config2.secret
5391
+ }
5392
+ }
5393
+ }
5394
+ );
5395
+ const apiDestination = new aws6.cloudwatch.EventApiDestination(
5396
+ "wraps-user-webhook-destination",
5397
+ {
5398
+ name: "wraps-user-webhook-destination",
5399
+ description: "Send SES events to user webhook endpoint",
5400
+ connectionArn: connection.arn,
5401
+ httpMethod: "POST",
5402
+ invocationEndpoint: config2.url,
5403
+ invocationRateLimitPerSecond: 300
5404
+ }
5405
+ );
5406
+ const role = new aws6.iam.Role("wraps-user-webhook-role", {
5407
+ name: "wraps-user-webhook-role",
5408
+ assumeRolePolicy: JSON.stringify({
5409
+ Version: "2012-10-17",
5410
+ Statement: [
5411
+ {
5412
+ Effect: "Allow",
5413
+ Principal: {
5414
+ Service: "events.amazonaws.com"
5415
+ },
5416
+ Action: "sts:AssumeRole"
5417
+ }
5418
+ ]
5419
+ }),
5420
+ tags: {
5421
+ ManagedBy: "wraps-cli",
5422
+ Service: "email"
5423
+ }
5424
+ });
5425
+ new aws6.iam.RolePolicy("wraps-user-webhook-policy", {
5426
+ role: role.name,
5427
+ policy: apiDestination.arn.apply(
5428
+ (destArn) => JSON.stringify({
5429
+ Version: "2012-10-17",
5430
+ Statement: [
5431
+ {
5432
+ Effect: "Allow",
5433
+ Action: ["events:InvokeApiDestination"],
5434
+ Resource: destArn
5435
+ }
5436
+ ]
5437
+ })
5438
+ )
5439
+ });
5440
+ const target = new aws6.cloudwatch.EventTarget(
5441
+ "wraps-user-webhook-target",
5442
+ {
5443
+ rule: config2.ruleName,
5444
+ eventBusName: config2.eventBusName,
5445
+ arn: apiDestination.arn,
5446
+ roleArn: role.arn,
5447
+ inputTransformer: {
5448
+ inputPaths: {
5449
+ event: "$.detail.eventType",
5450
+ detail: "$.detail",
5451
+ timestamp: "$.time",
5452
+ messageId: "$.detail.mail.messageId"
5453
+ },
5454
+ inputTemplate: '{"event":<event>,"detail":<detail>,"timestamp":<timestamp>,"messageId":<messageId>,"source":"wraps"}'
5455
+ }
5456
+ }
5457
+ );
5458
+ return {
5459
+ connection,
5460
+ apiDestination,
5461
+ target,
5462
+ role
5463
+ };
5464
+ }
5465
+ var init_eventbridge_user_webhook = __esm({
5466
+ "src/infrastructure/resources/eventbridge-user-webhook.ts"() {
5467
+ "use strict";
5468
+ init_esm_shims();
5469
+ }
5470
+ });
5471
+
5353
5472
  // src/infrastructure/resources/lambda.ts
5354
5473
  var lambda_exports = {};
5355
5474
  __export(lambda_exports, {
@@ -5362,7 +5481,7 @@ import { builtinModules } from "module";
5362
5481
  import { tmpdir } from "os";
5363
5482
  import { dirname as dirname2, join as join7 } from "path";
5364
5483
  import { fileURLToPath as fileURLToPath3 } from "url";
5365
- import * as aws7 from "@pulumi/aws";
5484
+ import * as aws8 from "@pulumi/aws";
5366
5485
  import * as pulumi11 from "@pulumi/pulumi";
5367
5486
  import { build } from "esbuild";
5368
5487
  function getPackageRoot() {
@@ -5437,7 +5556,7 @@ Try running: pnpm build`
5437
5556
  }
5438
5557
  async function deployLambdaFunctions(config2) {
5439
5558
  const eventProcessorCode = await getLambdaCode("event-processor");
5440
- const lambdaRole = new aws7.iam.Role("wraps-email-lambda-role", {
5559
+ const lambdaRole = new aws8.iam.Role("wraps-email-lambda-role", {
5441
5560
  assumeRolePolicy: JSON.stringify({
5442
5561
  Version: "2012-10-17",
5443
5562
  Statement: [
@@ -5453,11 +5572,11 @@ async function deployLambdaFunctions(config2) {
5453
5572
  Service: "email"
5454
5573
  }
5455
5574
  });
5456
- new aws7.iam.RolePolicyAttachment("wraps-email-lambda-basic-execution", {
5575
+ new aws8.iam.RolePolicyAttachment("wraps-email-lambda-basic-execution", {
5457
5576
  role: lambdaRole.name,
5458
5577
  policyArn: "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
5459
5578
  });
5460
- new aws7.iam.RolePolicy("wraps-email-lambda-policy", {
5579
+ new aws8.iam.RolePolicy("wraps-email-lambda-policy", {
5461
5580
  role: lambdaRole.name,
5462
5581
  policy: pulumi11.all([config2.tableName, config2.queueArn]).apply(
5463
5582
  ([tableName, queueArn]) => JSON.stringify({
@@ -5501,7 +5620,7 @@ async function deployLambdaFunctions(config2) {
5501
5620
  RETENTION_DAYS: config2.retentionDays.toString()
5502
5621
  }
5503
5622
  };
5504
- const eventProcessor = exists ? new aws7.lambda.Function(
5623
+ const eventProcessor = exists ? new aws8.lambda.Function(
5505
5624
  functionName,
5506
5625
  {
5507
5626
  name: functionName,
@@ -5523,7 +5642,7 @@ async function deployLambdaFunctions(config2) {
5523
5642
  import: functionName
5524
5643
  // Import existing function
5525
5644
  }
5526
- ) : new aws7.lambda.Function(functionName, {
5645
+ ) : new aws8.lambda.Function(functionName, {
5527
5646
  name: functionName,
5528
5647
  runtime: "nodejs24.x",
5529
5648
  handler: "index.handler",
@@ -5553,14 +5672,14 @@ async function deployLambdaFunctions(config2) {
5553
5672
  functionResponseTypes: ["ReportBatchItemFailures"]
5554
5673
  // Enable partial batch responses
5555
5674
  };
5556
- const eventSourceMapping = existingMappingUuid ? new aws7.lambda.EventSourceMapping(
5675
+ const eventSourceMapping = existingMappingUuid ? new aws8.lambda.EventSourceMapping(
5557
5676
  "wraps-email-event-source-mapping",
5558
5677
  mappingConfig,
5559
5678
  {
5560
5679
  import: existingMappingUuid
5561
5680
  // Import with the UUID
5562
5681
  }
5563
- ) : new aws7.lambda.EventSourceMapping(
5682
+ ) : new aws8.lambda.EventSourceMapping(
5564
5683
  "wraps-email-event-source-mapping",
5565
5684
  mappingConfig
5566
5685
  );
@@ -5587,7 +5706,7 @@ __export(acm_exports, {
5587
5706
  createACMCertificate: () => createACMCertificate
5588
5707
  });
5589
5708
  import { ACMClient as ACMClient2, DescribeCertificateCommand as DescribeCertificateCommand2 } from "@aws-sdk/client-acm";
5590
- import * as aws11 from "@pulumi/aws";
5709
+ import * as aws12 from "@pulumi/aws";
5591
5710
  async function checkCertificateValidation(domain) {
5592
5711
  try {
5593
5712
  const acm3 = new ACMClient2({ region: "us-east-1" });
@@ -5614,10 +5733,10 @@ async function checkCertificateValidation(domain) {
5614
5733
  }
5615
5734
  }
5616
5735
  async function createACMCertificate(config2) {
5617
- const usEast1Provider = new aws11.Provider("acm-us-east-1", {
5736
+ const usEast1Provider = new aws12.Provider("acm-us-east-1", {
5618
5737
  region: "us-east-1"
5619
5738
  });
5620
- const certificate = new aws11.acm.Certificate(
5739
+ const certificate = new aws12.acm.Certificate(
5621
5740
  "wraps-email-tracking-cert",
5622
5741
  {
5623
5742
  domainName: config2.domain,
@@ -5640,7 +5759,7 @@ async function createACMCertificate(config2) {
5640
5759
  );
5641
5760
  let certificateValidation;
5642
5761
  if (config2.hostedZoneId) {
5643
- const validationRecord = new aws11.route53.Record(
5762
+ const validationRecord = new aws12.route53.Record(
5644
5763
  "wraps-email-tracking-cert-validation",
5645
5764
  {
5646
5765
  zoneId: config2.hostedZoneId,
@@ -5650,7 +5769,7 @@ async function createACMCertificate(config2) {
5650
5769
  ttl: 60
5651
5770
  }
5652
5771
  );
5653
- certificateValidation = new aws11.acm.CertificateValidation(
5772
+ certificateValidation = new aws12.acm.CertificateValidation(
5654
5773
  "wraps-email-tracking-cert-validation-waiter",
5655
5774
  {
5656
5775
  certificateArn: certificate.arn,
@@ -5679,7 +5798,7 @@ var cloudfront_exports = {};
5679
5798
  __export(cloudfront_exports, {
5680
5799
  createCloudFrontTracking: () => createCloudFrontTracking
5681
5800
  });
5682
- import * as aws12 from "@pulumi/aws";
5801
+ import * as aws13 from "@pulumi/aws";
5683
5802
  async function findDistributionByAlias(alias) {
5684
5803
  try {
5685
5804
  const { CloudFrontClient, ListDistributionsCommand } = await import("@aws-sdk/client-cloudfront");
@@ -5695,10 +5814,10 @@ async function findDistributionByAlias(alias) {
5695
5814
  }
5696
5815
  }
5697
5816
  async function createWAFWebACL() {
5698
- const usEast1Provider = new aws12.Provider("waf-us-east-1", {
5817
+ const usEast1Provider = new aws13.Provider("waf-us-east-1", {
5699
5818
  region: "us-east-1"
5700
5819
  });
5701
- const webAcl = new aws12.wafv2.WebAcl(
5820
+ const webAcl = new aws13.wafv2.WebAcl(
5702
5821
  "wraps-email-tracking-waf",
5703
5822
  {
5704
5823
  scope: "CLOUDFRONT",
@@ -5813,14 +5932,14 @@ async function createCloudFrontTracking(config2) {
5813
5932
  Description: "Wraps email tracking CloudFront distribution"
5814
5933
  }
5815
5934
  };
5816
- const distribution = existingDistributionId ? new aws12.cloudfront.Distribution(
5935
+ const distribution = existingDistributionId ? new aws13.cloudfront.Distribution(
5817
5936
  "wraps-email-tracking-cdn",
5818
5937
  distributionConfig,
5819
5938
  {
5820
5939
  import: existingDistributionId
5821
5940
  // Import existing distribution
5822
5941
  }
5823
- ) : new aws12.cloudfront.Distribution(
5942
+ ) : new aws13.cloudfront.Distribution(
5824
5943
  "wraps-email-tracking-cdn",
5825
5944
  distributionConfig
5826
5945
  );
@@ -6002,10 +6121,10 @@ var s3_inbound_exports = {};
6002
6121
  __export(s3_inbound_exports, {
6003
6122
  createS3InboundResources: () => createS3InboundResources
6004
6123
  });
6005
- import * as aws13 from "@pulumi/aws";
6124
+ import * as aws14 from "@pulumi/aws";
6006
6125
  async function createS3InboundResources(config2) {
6007
6126
  const bucketName = `wraps-inbound-${config2.accountId}-${config2.region}`;
6008
- const bucket = new aws13.s3.BucketV2("wraps-inbound-bucket", {
6127
+ const bucket = new aws14.s3.BucketV2("wraps-inbound-bucket", {
6009
6128
  bucket: bucketName,
6010
6129
  forceDestroy: true,
6011
6130
  tags: {
@@ -6013,14 +6132,14 @@ async function createS3InboundResources(config2) {
6013
6132
  Service: "email-inbound"
6014
6133
  }
6015
6134
  });
6016
- new aws13.s3.BucketPublicAccessBlock("wraps-inbound-public-access-block", {
6135
+ new aws14.s3.BucketPublicAccessBlock("wraps-inbound-public-access-block", {
6017
6136
  bucket: bucket.id,
6018
6137
  blockPublicAcls: true,
6019
6138
  blockPublicPolicy: true,
6020
6139
  ignorePublicAcls: true,
6021
6140
  restrictPublicBuckets: true
6022
6141
  });
6023
- new aws13.s3.BucketServerSideEncryptionConfigurationV2(
6142
+ new aws14.s3.BucketServerSideEncryptionConfigurationV2(
6024
6143
  "wraps-inbound-encryption",
6025
6144
  {
6026
6145
  bucket: bucket.id,
@@ -6036,7 +6155,7 @@ async function createS3InboundResources(config2) {
6036
6155
  if (config2.retention) {
6037
6156
  const retentionDays = retentionToDays2(config2.retention);
6038
6157
  if (retentionDays > 0) {
6039
- new aws13.s3.BucketLifecycleConfigurationV2("wraps-inbound-lifecycle", {
6158
+ new aws14.s3.BucketLifecycleConfigurationV2("wraps-inbound-lifecycle", {
6040
6159
  bucket: bucket.id,
6041
6160
  rules: [
6042
6161
  {
@@ -6050,8 +6169,8 @@ async function createS3InboundResources(config2) {
6050
6169
  });
6051
6170
  }
6052
6171
  }
6053
- const identity = await aws13.getCallerIdentity();
6054
- new aws13.s3.BucketPolicy("wraps-inbound-bucket-policy", {
6172
+ const identity = await aws14.getCallerIdentity();
6173
+ new aws14.s3.BucketPolicy("wraps-inbound-bucket-policy", {
6055
6174
  bucket: bucket.id,
6056
6175
  policy: bucket.arn.apply(
6057
6176
  (arn) => JSON.stringify({
@@ -6093,9 +6212,9 @@ var sqs_inbound_exports = {};
6093
6212
  __export(sqs_inbound_exports, {
6094
6213
  createSQSInboundResources: () => createSQSInboundResources
6095
6214
  });
6096
- import * as aws14 from "@pulumi/aws";
6215
+ import * as aws15 from "@pulumi/aws";
6097
6216
  function createSQSInboundResources() {
6098
- const dlq = new aws14.sqs.Queue("wraps-inbound-events-dlq", {
6217
+ const dlq = new aws15.sqs.Queue("wraps-inbound-events-dlq", {
6099
6218
  name: "wraps-inbound-events-dlq",
6100
6219
  messageRetentionSeconds: 1209600,
6101
6220
  // 14 days
@@ -6105,7 +6224,7 @@ function createSQSInboundResources() {
6105
6224
  Description: "Dead letter queue for failed inbound email processing"
6106
6225
  }
6107
6226
  });
6108
- const queue = new aws14.sqs.Queue("wraps-inbound-events", {
6227
+ const queue = new aws15.sqs.Queue("wraps-inbound-events", {
6109
6228
  name: "wraps-inbound-events",
6110
6229
  visibilityTimeoutSeconds: 300,
6111
6230
  // Must be >= Lambda timeout (120s) * 2 + buffer
@@ -6142,11 +6261,11 @@ var lambda_inbound_exports = {};
6142
6261
  __export(lambda_inbound_exports, {
6143
6262
  deployInboundLambda: () => deployInboundLambda
6144
6263
  });
6145
- import * as aws15 from "@pulumi/aws";
6264
+ import * as aws16 from "@pulumi/aws";
6146
6265
  import * as pulumi12 from "@pulumi/pulumi";
6147
6266
  async function deployInboundLambda(config2) {
6148
6267
  const inboundProcessorCode = await getLambdaCode("inbound-processor");
6149
- const lambdaRole = new aws15.iam.Role("wraps-inbound-lambda-role", {
6268
+ const lambdaRole = new aws16.iam.Role("wraps-inbound-lambda-role", {
6150
6269
  name: "wraps-inbound-lambda-role",
6151
6270
  assumeRolePolicy: JSON.stringify({
6152
6271
  Version: "2012-10-17",
@@ -6163,11 +6282,11 @@ async function deployInboundLambda(config2) {
6163
6282
  Service: "email-inbound"
6164
6283
  }
6165
6284
  });
6166
- new aws15.iam.RolePolicyAttachment("wraps-inbound-lambda-basic-execution", {
6285
+ new aws16.iam.RolePolicyAttachment("wraps-inbound-lambda-basic-execution", {
6167
6286
  role: lambdaRole.name,
6168
6287
  policyArn: "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
6169
6288
  });
6170
- new aws15.iam.RolePolicy("wraps-inbound-lambda-policy", {
6289
+ new aws16.iam.RolePolicy("wraps-inbound-lambda-policy", {
6171
6290
  role: lambdaRole.name,
6172
6291
  policy: pulumi12.all([config2.bucketArn, config2.dlqArn]).apply(
6173
6292
  ([bucketArn, dlqArn]) => JSON.stringify({
@@ -6196,7 +6315,7 @@ async function deployInboundLambda(config2) {
6196
6315
  )
6197
6316
  });
6198
6317
  const functionName = "wraps-inbound-email-processor";
6199
- const lambdaFunction = new aws15.lambda.Function(functionName, {
6318
+ const lambdaFunction = new aws16.lambda.Function(functionName, {
6200
6319
  name: functionName,
6201
6320
  runtime: "nodejs20.x",
6202
6321
  handler: "index.handler",
@@ -6218,7 +6337,7 @@ async function deployInboundLambda(config2) {
6218
6337
  Service: "email-inbound"
6219
6338
  }
6220
6339
  });
6221
- const s3InvokePermission = new aws15.lambda.Permission(
6340
+ const s3InvokePermission = new aws16.lambda.Permission(
6222
6341
  "wraps-inbound-s3-invoke",
6223
6342
  {
6224
6343
  action: "lambda:InvokeFunction",
@@ -6246,9 +6365,9 @@ var eventbridge_inbound_exports = {};
6246
6365
  __export(eventbridge_inbound_exports, {
6247
6366
  createEventBridgeInboundResources: () => createEventBridgeInboundResources
6248
6367
  });
6249
- import * as aws16 from "@pulumi/aws";
6368
+ import * as aws17 from "@pulumi/aws";
6250
6369
  function createEventBridgeInboundResources(config2) {
6251
- const rule = new aws16.cloudwatch.EventRule("wraps-inbound-events-rule", {
6370
+ const rule = new aws17.cloudwatch.EventRule("wraps-inbound-events-rule", {
6252
6371
  name: "wraps-inbound-events-to-webhook",
6253
6372
  description: "Route inbound email events to webhook",
6254
6373
  eventBusName: "default",
@@ -6265,7 +6384,7 @@ function createEventBridgeInboundResources(config2) {
6265
6384
  let webhookApiDestination;
6266
6385
  let webhookTarget;
6267
6386
  if (config2.webhookUrl && config2.webhookSecret) {
6268
- webhookConnection = new aws16.cloudwatch.EventConnection(
6387
+ webhookConnection = new aws17.cloudwatch.EventConnection(
6269
6388
  "wraps-inbound-webhook-connection",
6270
6389
  {
6271
6390
  name: "wraps-inbound-webhook-connection",
@@ -6279,7 +6398,7 @@ function createEventBridgeInboundResources(config2) {
6279
6398
  }
6280
6399
  }
6281
6400
  );
6282
- webhookApiDestination = new aws16.cloudwatch.EventApiDestination(
6401
+ webhookApiDestination = new aws17.cloudwatch.EventApiDestination(
6283
6402
  "wraps-inbound-webhook-destination",
6284
6403
  {
6285
6404
  name: "wraps-inbound-webhook-destination",
@@ -6290,7 +6409,7 @@ function createEventBridgeInboundResources(config2) {
6290
6409
  invocationRateLimitPerSecond: 100
6291
6410
  }
6292
6411
  );
6293
- const webhookRole = new aws16.iam.Role("wraps-inbound-webhook-role", {
6412
+ const webhookRole = new aws17.iam.Role("wraps-inbound-webhook-role", {
6294
6413
  name: "wraps-inbound-eventbridge-webhook-role",
6295
6414
  assumeRolePolicy: JSON.stringify({
6296
6415
  Version: "2012-10-17",
@@ -6309,7 +6428,7 @@ function createEventBridgeInboundResources(config2) {
6309
6428
  Service: "email-inbound"
6310
6429
  }
6311
6430
  });
6312
- new aws16.iam.RolePolicy("wraps-inbound-webhook-policy", {
6431
+ new aws17.iam.RolePolicy("wraps-inbound-webhook-policy", {
6313
6432
  role: webhookRole.name,
6314
6433
  policy: webhookApiDestination.arn.apply(
6315
6434
  (destArn) => JSON.stringify({
@@ -6324,7 +6443,7 @@ function createEventBridgeInboundResources(config2) {
6324
6443
  })
6325
6444
  )
6326
6445
  });
6327
- webhookTarget = new aws16.cloudwatch.EventTarget(
6446
+ webhookTarget = new aws17.cloudwatch.EventTarget(
6328
6447
  "wraps-inbound-webhook-target",
6329
6448
  {
6330
6449
  rule: rule.name,
@@ -10496,10 +10615,10 @@ import * as aws3 from "@pulumi/aws";
10496
10615
  async function getExistingOIDCProviderArn(url) {
10497
10616
  try {
10498
10617
  const { IAMClient: IAMClient4, ListOpenIDConnectProvidersCommand } = await import("@aws-sdk/client-iam");
10499
- const iam9 = new IAMClient4({
10618
+ const iam10 = new IAMClient4({
10500
10619
  region: getDefaultRegion()
10501
10620
  });
10502
- const response = await iam9.send(new ListOpenIDConnectProvidersCommand({}));
10621
+ const response = await iam10.send(new ListOpenIDConnectProvidersCommand({}));
10503
10622
  const expectedArnSuffix = url.replace("https://", "");
10504
10623
  const provider = response.OpenIDConnectProviderList?.find(
10505
10624
  (p) => p.Arn?.endsWith(expectedArnSuffix)
@@ -15439,7 +15558,7 @@ import pc17 from "picocolors";
15439
15558
  // src/infrastructure/email-stack.ts
15440
15559
  init_esm_shims();
15441
15560
  init_dist();
15442
- import * as aws17 from "@pulumi/aws";
15561
+ import * as aws18 from "@pulumi/aws";
15443
15562
 
15444
15563
  // src/infrastructure/resources/alerting.ts
15445
15564
  init_esm_shims();
@@ -15713,11 +15832,11 @@ async function createDynamoDBTables(_config) {
15713
15832
 
15714
15833
  // src/infrastructure/resources/eventbridge.ts
15715
15834
  init_esm_shims();
15716
- import * as aws6 from "@pulumi/aws";
15835
+ import * as aws7 from "@pulumi/aws";
15717
15836
  import * as pulumi10 from "@pulumi/pulumi";
15718
15837
  async function createEventBridgeResources(config2) {
15719
15838
  const eventBusName = config2.eventBusArn.apply((arn) => arn.split("/").pop());
15720
- const rule = new aws6.cloudwatch.EventRule("wraps-email-events-rule", {
15839
+ const rule = new aws7.cloudwatch.EventRule("wraps-email-events-rule", {
15721
15840
  name: "wraps-email-events-to-sqs",
15722
15841
  description: "Route all SES email events to SQS for processing",
15723
15842
  eventBusName,
@@ -15731,7 +15850,7 @@ async function createEventBridgeResources(config2) {
15731
15850
  Service: "email"
15732
15851
  }
15733
15852
  });
15734
- new aws6.sqs.QueuePolicy("wraps-email-events-queue-policy", {
15853
+ new aws7.sqs.QueuePolicy("wraps-email-events-queue-policy", {
15735
15854
  queueUrl: config2.queueUrl,
15736
15855
  policy: pulumi10.all([config2.queueArn, rule.arn]).apply(
15737
15856
  ([queueArn, ruleArn]) => JSON.stringify({
@@ -15754,7 +15873,7 @@ async function createEventBridgeResources(config2) {
15754
15873
  })
15755
15874
  )
15756
15875
  });
15757
- const target = new aws6.cloudwatch.EventTarget("wraps-email-events-target", {
15876
+ const target = new aws7.cloudwatch.EventTarget("wraps-email-events-target", {
15758
15877
  rule: rule.name,
15759
15878
  eventBusName,
15760
15879
  arn: config2.queueArn
@@ -15765,7 +15884,7 @@ async function createEventBridgeResources(config2) {
15765
15884
  if (config2.webhook) {
15766
15885
  const { awsAccountNumber, webhookSecret, webhookUrl } = config2.webhook;
15767
15886
  const baseUrl = webhookUrl || "https://api.wraps.dev";
15768
- webhookConnection = new aws6.cloudwatch.EventConnection(
15887
+ webhookConnection = new aws7.cloudwatch.EventConnection(
15769
15888
  "wraps-webhook-connection",
15770
15889
  {
15771
15890
  name: "wraps-webhook-connection",
@@ -15779,7 +15898,7 @@ async function createEventBridgeResources(config2) {
15779
15898
  }
15780
15899
  }
15781
15900
  );
15782
- webhookApiDestination = new aws6.cloudwatch.EventApiDestination(
15901
+ webhookApiDestination = new aws7.cloudwatch.EventApiDestination(
15783
15902
  "wraps-webhook-destination",
15784
15903
  {
15785
15904
  name: "wraps-webhook-destination",
@@ -15791,7 +15910,7 @@ async function createEventBridgeResources(config2) {
15791
15910
  // Rate limit
15792
15911
  }
15793
15912
  );
15794
- const webhookRole = new aws6.iam.Role("wraps-webhook-role", {
15913
+ const webhookRole = new aws7.iam.Role("wraps-webhook-role", {
15795
15914
  name: "wraps-eventbridge-webhook-role",
15796
15915
  assumeRolePolicy: JSON.stringify({
15797
15916
  Version: "2012-10-17",
@@ -15810,7 +15929,7 @@ async function createEventBridgeResources(config2) {
15810
15929
  Service: "email"
15811
15930
  }
15812
15931
  });
15813
- new aws6.iam.RolePolicy("wraps-webhook-policy", {
15932
+ new aws7.iam.RolePolicy("wraps-webhook-policy", {
15814
15933
  role: webhookRole.name,
15815
15934
  policy: webhookApiDestination.arn.apply(
15816
15935
  (destArn) => JSON.stringify({
@@ -15825,19 +15944,37 @@ async function createEventBridgeResources(config2) {
15825
15944
  })
15826
15945
  )
15827
15946
  });
15828
- webhookTarget = new aws6.cloudwatch.EventTarget("wraps-webhook-target", {
15947
+ webhookTarget = new aws7.cloudwatch.EventTarget("wraps-webhook-target", {
15829
15948
  rule: rule.name,
15830
15949
  eventBusName,
15831
15950
  arn: webhookApiDestination.arn,
15832
15951
  roleArn: webhookRole.arn
15833
15952
  });
15834
15953
  }
15954
+ let userWebhookConnection;
15955
+ let userWebhookApiDestination;
15956
+ let userWebhookTarget;
15957
+ if (config2.userWebhook) {
15958
+ const { createUserWebhookResources: createUserWebhookResources2 } = await Promise.resolve().then(() => (init_eventbridge_user_webhook(), eventbridge_user_webhook_exports));
15959
+ const userWebhookResources = createUserWebhookResources2({
15960
+ url: config2.userWebhook.url,
15961
+ secret: config2.userWebhook.secret,
15962
+ ruleName: rule.name,
15963
+ eventBusName
15964
+ });
15965
+ userWebhookConnection = userWebhookResources.connection;
15966
+ userWebhookApiDestination = userWebhookResources.apiDestination;
15967
+ userWebhookTarget = userWebhookResources.target;
15968
+ }
15835
15969
  return {
15836
15970
  rule,
15837
15971
  target,
15838
15972
  webhookConnection,
15839
15973
  webhookApiDestination,
15840
- webhookTarget
15974
+ webhookTarget,
15975
+ userWebhookConnection,
15976
+ userWebhookApiDestination,
15977
+ userWebhookTarget
15841
15978
  };
15842
15979
  }
15843
15980
 
@@ -15977,7 +16114,7 @@ init_lambda();
15977
16114
 
15978
16115
  // src/infrastructure/resources/ses.ts
15979
16116
  init_esm_shims();
15980
- import * as aws8 from "@pulumi/aws";
16117
+ import * as aws9 from "@pulumi/aws";
15981
16118
  async function configurationSetExists(configSetName, region) {
15982
16119
  try {
15983
16120
  const { SESv2Client: SESv2Client9, GetConfigurationSetCommand: GetConfigurationSetCommand2 } = await import("@aws-sdk/client-sesv2");
@@ -16056,15 +16193,15 @@ async function createSESResources(config2) {
16056
16193
  }
16057
16194
  const configSetName = "wraps-email-tracking";
16058
16195
  const exists = await configurationSetExists(configSetName, config2.region);
16059
- const configSet = exists && !config2.skipResourceImports ? new aws8.sesv2.ConfigurationSet(configSetName, configSetOptions, {
16196
+ const configSet = exists && !config2.skipResourceImports ? new aws9.sesv2.ConfigurationSet(configSetName, configSetOptions, {
16060
16197
  import: configSetName
16061
- }) : new aws8.sesv2.ConfigurationSet(configSetName, configSetOptions);
16062
- const defaultEventBus = aws8.cloudwatch.getEventBusOutput({
16198
+ }) : new aws9.sesv2.ConfigurationSet(configSetName, configSetOptions);
16199
+ const defaultEventBus = aws9.cloudwatch.getEventBusOutput({
16063
16200
  name: "default"
16064
16201
  });
16065
16202
  if (config2.eventTrackingEnabled) {
16066
16203
  const eventDestName = "wraps-email-eventbridge";
16067
- new aws8.sesv2.ConfigurationSetEventDestination(
16204
+ new aws9.sesv2.ConfigurationSetEventDestination(
16068
16205
  "wraps-email-all-events",
16069
16206
  {
16070
16207
  configurationSetName: configSet.configurationSetName,
@@ -16104,7 +16241,7 @@ async function createSESResources(config2) {
16104
16241
  config2.domain,
16105
16242
  config2.region
16106
16243
  );
16107
- domainIdentity = identityExists && !config2.skipResourceImports ? new aws8.sesv2.EmailIdentity(
16244
+ domainIdentity = identityExists && !config2.skipResourceImports ? new aws9.sesv2.EmailIdentity(
16108
16245
  "wraps-email-domain",
16109
16246
  {
16110
16247
  emailIdentity: config2.domain,
@@ -16119,7 +16256,7 @@ async function createSESResources(config2) {
16119
16256
  {
16120
16257
  import: config2.domain
16121
16258
  }
16122
- ) : new aws8.sesv2.EmailIdentity("wraps-email-domain", {
16259
+ ) : new aws9.sesv2.EmailIdentity("wraps-email-domain", {
16123
16260
  emailIdentity: config2.domain,
16124
16261
  configurationSetName: configSet.configurationSetName,
16125
16262
  // Link configuration set to domain
@@ -16136,7 +16273,7 @@ async function createSESResources(config2) {
16136
16273
  );
16137
16274
  if (config2.mailFromDomain) {
16138
16275
  mailFromDomain = config2.mailFromDomain;
16139
- new aws8.sesv2.EmailIdentityMailFromAttributes(
16276
+ new aws9.sesv2.EmailIdentityMailFromAttributes(
16140
16277
  "wraps-email-mail-from",
16141
16278
  {
16142
16279
  emailIdentity: config2.domain,
@@ -16168,7 +16305,7 @@ async function createSESResources(config2) {
16168
16305
  init_esm_shims();
16169
16306
  init_constants();
16170
16307
  import { createHmac as createHmac2 } from "crypto";
16171
- import * as aws9 from "@pulumi/aws";
16308
+ import * as aws10 from "@pulumi/aws";
16172
16309
  function convertToSMTPPassword2(secretAccessKey, region) {
16173
16310
  const DATE = "11111111";
16174
16311
  const SERVICE = "ses";
@@ -16189,10 +16326,10 @@ function convertToSMTPPassword2(secretAccessKey, region) {
16189
16326
  async function userExists(userName) {
16190
16327
  try {
16191
16328
  const { IAMClient: IAMClient4, GetUserCommand } = await import("@aws-sdk/client-iam");
16192
- const iam9 = new IAMClient4({
16329
+ const iam10 = new IAMClient4({
16193
16330
  region: getDefaultRegion()
16194
16331
  });
16195
- await iam9.send(new GetUserCommand({ UserName: userName }));
16332
+ await iam10.send(new GetUserCommand({ UserName: userName }));
16196
16333
  return true;
16197
16334
  } catch (error) {
16198
16335
  if (error instanceof Error && (error.name === "NoSuchEntityException" || error.Code === "NoSuchEntity")) {
@@ -16204,7 +16341,7 @@ async function userExists(userName) {
16204
16341
  async function createSMTPCredentials(config2) {
16205
16342
  const userName = "wraps-email-smtp-user";
16206
16343
  const userAlreadyExists = await userExists(userName);
16207
- const iamUser = userAlreadyExists ? new aws9.iam.User(
16344
+ const iamUser = userAlreadyExists ? new aws10.iam.User(
16208
16345
  userName,
16209
16346
  {
16210
16347
  name: userName,
@@ -16215,14 +16352,14 @@ async function createSMTPCredentials(config2) {
16215
16352
  }
16216
16353
  },
16217
16354
  { import: userName }
16218
- ) : new aws9.iam.User(userName, {
16355
+ ) : new aws10.iam.User(userName, {
16219
16356
  name: userName,
16220
16357
  tags: {
16221
16358
  ManagedBy: "wraps-cli",
16222
16359
  Purpose: "SES SMTP Authentication"
16223
16360
  }
16224
16361
  });
16225
- new aws9.iam.UserPolicy("wraps-email-smtp-policy", {
16362
+ new aws10.iam.UserPolicy("wraps-email-smtp-policy", {
16226
16363
  user: iamUser.name,
16227
16364
  policy: JSON.stringify({
16228
16365
  Version: "2012-10-17",
@@ -16240,7 +16377,7 @@ async function createSMTPCredentials(config2) {
16240
16377
  ]
16241
16378
  })
16242
16379
  });
16243
- const accessKey = new aws9.iam.AccessKey("wraps-email-smtp-key", {
16380
+ const accessKey = new aws10.iam.AccessKey("wraps-email-smtp-key", {
16244
16381
  user: iamUser.name
16245
16382
  });
16246
16383
  const smtpPassword = accessKey.secret.apply(
@@ -16255,9 +16392,9 @@ async function createSMTPCredentials(config2) {
16255
16392
 
16256
16393
  // src/infrastructure/resources/sqs.ts
16257
16394
  init_esm_shims();
16258
- import * as aws10 from "@pulumi/aws";
16395
+ import * as aws11 from "@pulumi/aws";
16259
16396
  async function createSQSResources() {
16260
- const dlq = new aws10.sqs.Queue("wraps-email-events-dlq", {
16397
+ const dlq = new aws11.sqs.Queue("wraps-email-events-dlq", {
16261
16398
  name: "wraps-email-events-dlq",
16262
16399
  messageRetentionSeconds: 1209600,
16263
16400
  // 14 days
@@ -16267,7 +16404,7 @@ async function createSQSResources() {
16267
16404
  Description: "Dead letter queue for failed SES event processing"
16268
16405
  }
16269
16406
  });
16270
- const queue = new aws10.sqs.Queue("wraps-email-events", {
16407
+ const queue = new aws11.sqs.Queue("wraps-email-events", {
16271
16408
  name: "wraps-email-events",
16272
16409
  visibilityTimeoutSeconds: 60,
16273
16410
  // Must be >= Lambda timeout
@@ -16296,7 +16433,7 @@ async function createSQSResources() {
16296
16433
 
16297
16434
  // src/infrastructure/email-stack.ts
16298
16435
  async function deployEmailStack(config2) {
16299
- const identity = await aws17.getCallerIdentity();
16436
+ const identity = await aws18.getCallerIdentity();
16300
16437
  const accountId = identity.accountId;
16301
16438
  let oidcProvider;
16302
16439
  if (config2.provider === "vercel" && config2.vercel) {
@@ -16400,7 +16537,12 @@ async function deployEmailStack(config2) {
16400
16537
  queueArn: sqsResources.queue.arn,
16401
16538
  queueUrl: sqsResources.queue.url,
16402
16539
  // Include webhook config if provided (for Wraps platform integration)
16403
- webhook: config2.webhook
16540
+ webhook: config2.webhook,
16541
+ // Include user webhook config if provided
16542
+ userWebhook: emailConfig.userWebhook?.enabled && emailConfig.userWebhook.url && emailConfig.userWebhook.secret ? {
16543
+ url: emailConfig.userWebhook.url,
16544
+ secret: emailConfig.userWebhook.secret
16545
+ } : void 0
16404
16546
  });
16405
16547
  }
16406
16548
  let lambdaFunctions;
@@ -16461,7 +16603,7 @@ async function deployEmailStack(config2) {
16461
16603
  region: config2.region,
16462
16604
  dlqArn: sqsInbound.dlq.arn
16463
16605
  });
16464
- new aws17.s3.BucketNotification(
16606
+ new aws18.s3.BucketNotification(
16465
16607
  "wraps-inbound-s3-notification",
16466
16608
  {
16467
16609
  bucket: s3Inbound.bucket.id,
@@ -16522,7 +16664,10 @@ async function deployEmailStack(config2) {
16522
16664
  inboundBucketName: inboundResources?.bucketName,
16523
16665
  inboundBucketArn: inboundResources?.bucketArn,
16524
16666
  inboundLambdaArn: inboundResources?.lambdaArn,
16525
- inboundReceivingDomain: emailConfig.inbound?.receivingDomain
16667
+ inboundReceivingDomain: emailConfig.inbound?.receivingDomain,
16668
+ // User webhook outputs
16669
+ userWebhookUrl: emailConfig.userWebhook?.enabled ? emailConfig.userWebhook.url : void 0,
16670
+ userWebhookSecret: emailConfig.userWebhook?.enabled ? emailConfig.userWebhook.secret : void 0
16526
16671
  };
16527
16672
  }
16528
16673
 
@@ -16998,13 +17143,13 @@ async function scanLambdaFunctions(region) {
16998
17143
  }
16999
17144
  }
17000
17145
  async function scanIAMRoles(region) {
17001
- const iam9 = new IAMClient({ region });
17146
+ const iam10 = new IAMClient({ region });
17002
17147
  const roles = [];
17003
17148
  try {
17004
17149
  let marker;
17005
17150
  let hasMore = true;
17006
17151
  while (hasMore) {
17007
- const listResponse = await iam9.send(
17152
+ const listResponse = await iam10.send(
17008
17153
  new ListRolesCommand({
17009
17154
  Marker: marker,
17010
17155
  MaxItems: 100
@@ -21792,6 +21937,12 @@ ${pc30.bold("Current Configuration:")}
21792
21937
  );
21793
21938
  }
21794
21939
  }
21940
+ if (config2.userWebhook?.enabled) {
21941
+ console.log(` ${pc30.green("\u2713")} Webhook Endpoint`);
21942
+ console.log(
21943
+ ` ${pc30.dim("\u2514\u2500")} URL: ${pc30.cyan(config2.userWebhook.url)}`
21944
+ );
21945
+ }
21795
21946
  const currentCostData = calculateCosts(config2, 5e4);
21796
21947
  console.log(
21797
21948
  `
@@ -21852,6 +22003,11 @@ ${pc30.bold("Current Configuration:")}
21852
22003
  label: metadata.services.email?.webhookSecret ? "Manage Wraps Dashboard connection" : "Connect to Wraps Dashboard",
21853
22004
  hint: metadata.services.email?.webhookSecret ? "Regenerate secret or disconnect" : "Send events to dashboard for analytics"
21854
22005
  },
22006
+ {
22007
+ value: "user-webhook",
22008
+ label: config2.userWebhook?.enabled ? "Manage webhook endpoint" : "Configure webhook endpoint",
22009
+ hint: config2.userWebhook?.enabled ? `Sending events to ${config2.userWebhook.url}` : "Send SES events to your own URL"
22010
+ },
21855
22011
  {
21856
22012
  value: "smtp-credentials",
21857
22013
  label: metadata.services.email?.smtpCredentials?.enabled ? "Manage SMTP credentials" : "Enable SMTP credentials",
@@ -22679,6 +22835,166 @@ ${pc30.bold("Webhook Configuration:")}`);
22679
22835
  newPreset = void 0;
22680
22836
  break;
22681
22837
  }
22838
+ case "user-webhook": {
22839
+ const validateWebhookUrl = (value) => {
22840
+ try {
22841
+ const url = new URL(value);
22842
+ if (url.protocol !== "https:") {
22843
+ return "Webhook URL must use HTTPS";
22844
+ }
22845
+ if (!url.hostname.includes(".")) {
22846
+ return "Webhook URL must use a public hostname";
22847
+ }
22848
+ } catch {
22849
+ return "Please enter a valid URL";
22850
+ }
22851
+ };
22852
+ if (!config2.eventTracking?.enabled) {
22853
+ clack29.log.warn(
22854
+ "Event tracking must be enabled to configure a webhook endpoint."
22855
+ );
22856
+ clack29.log.info(
22857
+ "Enabling event tracking will allow SES events to be sent to your endpoint."
22858
+ );
22859
+ const enableEventTracking = await clack29.confirm({
22860
+ message: "Enable event tracking now?",
22861
+ initialValue: true
22862
+ });
22863
+ if (clack29.isCancel(enableEventTracking) || !enableEventTracking) {
22864
+ clack29.cancel("Webhook configuration cancelled.");
22865
+ process.exit(0);
22866
+ }
22867
+ updatedConfig = {
22868
+ ...config2,
22869
+ eventTracking: {
22870
+ enabled: true,
22871
+ eventBridge: true,
22872
+ events: [
22873
+ "SEND",
22874
+ "DELIVERY",
22875
+ "OPEN",
22876
+ "CLICK",
22877
+ "BOUNCE",
22878
+ "COMPLAINT"
22879
+ ],
22880
+ dynamoDBHistory: config2.eventTracking?.dynamoDBHistory ?? false,
22881
+ archiveRetention: config2.eventTracking?.archiveRetention ?? "90days"
22882
+ }
22883
+ };
22884
+ }
22885
+ if (config2.userWebhook?.enabled) {
22886
+ clack29.log.info(
22887
+ `Webhook endpoint currently sending events to: ${pc30.cyan(config2.userWebhook.url)}`
22888
+ );
22889
+ const action = await clack29.select({
22890
+ message: "What would you like to do?",
22891
+ options: [
22892
+ {
22893
+ value: "change-url",
22894
+ label: "Change webhook URL",
22895
+ hint: config2.userWebhook.url
22896
+ },
22897
+ {
22898
+ value: "regenerate-secret",
22899
+ label: "Regenerate webhook secret",
22900
+ hint: "Create new secret (update your endpoint)"
22901
+ },
22902
+ {
22903
+ value: "disable",
22904
+ label: "Disable webhook",
22905
+ hint: "Stop sending events to your endpoint"
22906
+ },
22907
+ {
22908
+ value: "cancel",
22909
+ label: "Cancel",
22910
+ hint: "Keep current settings"
22911
+ }
22912
+ ]
22913
+ });
22914
+ if (clack29.isCancel(action) || action === "cancel") {
22915
+ clack29.log.info("No changes made.");
22916
+ process.exit(0);
22917
+ }
22918
+ if (action === "disable") {
22919
+ const confirmDisable = await clack29.confirm({
22920
+ message: "Are you sure? Events will no longer be sent to your webhook endpoint.",
22921
+ initialValue: false
22922
+ });
22923
+ if (clack29.isCancel(confirmDisable) || !confirmDisable) {
22924
+ clack29.log.info("Webhook not disabled.");
22925
+ process.exit(0);
22926
+ }
22927
+ updatedConfig = {
22928
+ ...updatedConfig,
22929
+ userWebhook: { enabled: false }
22930
+ };
22931
+ newPreset = void 0;
22932
+ break;
22933
+ }
22934
+ if (action === "change-url") {
22935
+ const newUrl = await clack29.text({
22936
+ message: "New webhook URL:",
22937
+ placeholder: "https://your-app.com/webhooks/email-events",
22938
+ validate: validateWebhookUrl
22939
+ });
22940
+ if (clack29.isCancel(newUrl)) {
22941
+ clack29.cancel("Webhook configuration cancelled.");
22942
+ process.exit(0);
22943
+ }
22944
+ updatedConfig = {
22945
+ ...updatedConfig,
22946
+ userWebhook: {
22947
+ enabled: true,
22948
+ url: newUrl,
22949
+ secret: config2.userWebhook.secret
22950
+ }
22951
+ };
22952
+ newPreset = void 0;
22953
+ break;
22954
+ }
22955
+ if (action === "regenerate-secret") {
22956
+ const newSecret = generateWebhookSecret();
22957
+ updatedConfig = {
22958
+ ...updatedConfig,
22959
+ userWebhook: {
22960
+ enabled: true,
22961
+ url: config2.userWebhook.url,
22962
+ secret: newSecret
22963
+ }
22964
+ };
22965
+ newPreset = void 0;
22966
+ break;
22967
+ }
22968
+ } else {
22969
+ clack29.log.info(`
22970
+ ${pc30.bold("Webhook Endpoint")}
22971
+ `);
22972
+ clack29.log.info(
22973
+ pc30.dim("Send SES events (send, delivery, open, click, bounce, etc.)")
22974
+ );
22975
+ clack29.log.info(pc30.dim("to your own HTTP endpoint in real-time.\n"));
22976
+ const webhookUrl = await clack29.text({
22977
+ message: "Webhook URL (must be HTTPS):",
22978
+ placeholder: "https://your-app.com/webhooks/email-events",
22979
+ validate: validateWebhookUrl
22980
+ });
22981
+ if (clack29.isCancel(webhookUrl)) {
22982
+ clack29.cancel("Webhook configuration cancelled.");
22983
+ process.exit(0);
22984
+ }
22985
+ const secret = generateWebhookSecret();
22986
+ updatedConfig = {
22987
+ ...updatedConfig,
22988
+ userWebhook: {
22989
+ enabled: true,
22990
+ url: webhookUrl,
22991
+ secret
22992
+ }
22993
+ };
22994
+ newPreset = void 0;
22995
+ }
22996
+ break;
22997
+ }
22682
22998
  case "smtp-credentials": {
22683
22999
  if (metadata.services.email?.smtpCredentials?.enabled) {
22684
23000
  clack29.log.info(
@@ -23318,6 +23634,29 @@ ${pc30.green("\u2713")} ${pc30.bold("Upgrade complete!")}
23318
23634
  )
23319
23635
  );
23320
23636
  }
23637
+ if (upgradeAction === "user-webhook" && updatedConfig.userWebhook?.enabled) {
23638
+ console.log(pc30.bold("Webhook Endpoint Configuration\n"));
23639
+ console.log(` ${pc30.green("\u2713")} EventBridge API Destination created`);
23640
+ console.log(
23641
+ ` ${pc30.green("\u2713")} Events will be sent to: ${pc30.cyan(updatedConfig.userWebhook.url)}
23642
+ `
23643
+ );
23644
+ if (updatedConfig.userWebhook.secret !== config2.userWebhook?.secret) {
23645
+ console.log(pc30.bold(" Webhook Secret (save this now!):\n"));
23646
+ console.log(
23647
+ ` ${pc30.bgBlack(pc30.white(` ${updatedConfig.userWebhook.secret} `))}
23648
+ `
23649
+ );
23650
+ console.log(
23651
+ pc30.dim(" Include this in the X-Wraps-Signature header validation")
23652
+ );
23653
+ console.log(
23654
+ pc30.dim(
23655
+ " to verify requests are from your Wraps deployment.\n"
23656
+ )
23657
+ );
23658
+ }
23659
+ }
23321
23660
  if (upgradeAction === "smtp-credentials" && outputs.smtpUsername && outputs.smtpPassword) {
23322
23661
  console.log(pc30.bold("\n\u{1F4E7} SMTP Connection Details\n"));
23323
23662
  console.log(` ${pc30.cyan("Server:")} ${outputs.smtpEndpoint}`);
@@ -23368,6 +23707,9 @@ ${pc30.green("\u2713")} ${pc30.bold("Upgrade complete!")}
23368
23707
  if (updatedConfig.alerts?.enabled) {
23369
23708
  enabledFeatures.push("alerts");
23370
23709
  }
23710
+ if (updatedConfig.userWebhook?.enabled) {
23711
+ enabledFeatures.push("user_webhook");
23712
+ }
23371
23713
  trackServiceUpgrade("email", {
23372
23714
  from_preset: metadata.services.email?.preset,
23373
23715
  to_preset: newPreset,
@@ -23454,33 +23796,59 @@ function getTriggerName(type) {
23454
23796
  return names[type] || "Trigger";
23455
23797
  }
23456
23798
  function flattenSteps(stepDefs, steps, transitions, fromStepId, branch) {
23457
- let prevId = fromStepId;
23799
+ let prevIds = [fromStepId];
23458
23800
  let firstStepInBranch = true;
23459
23801
  for (const def of stepDefs) {
23460
23802
  const step = toWorkflowStep(def);
23461
23803
  steps.push(step);
23462
- const transition = {
23463
- id: `t-${prevId}-${step.id}`,
23464
- fromStepId: prevId,
23465
- toStepId: step.id
23466
- };
23467
- if (firstStepInBranch && branch) {
23468
- transition.condition = { branch };
23804
+ for (const prevId of prevIds) {
23805
+ const transition = {
23806
+ id: `t-${prevId}-${step.id}`,
23807
+ fromStepId: prevId,
23808
+ toStepId: step.id
23809
+ };
23810
+ if (firstStepInBranch && branch && prevId === fromStepId) {
23811
+ transition.condition = { branch };
23812
+ }
23813
+ transitions.push(transition);
23469
23814
  }
23470
- transitions.push(transition);
23471
23815
  firstStepInBranch = false;
23472
23816
  if (def.type === "condition" && def.branches) {
23817
+ const leafIds = [];
23473
23818
  if (def.branches.yes && def.branches.yes.length > 0) {
23474
- flattenSteps(def.branches.yes, steps, transitions, step.id, "yes");
23819
+ const yesLeaves = flattenSteps(
23820
+ def.branches.yes,
23821
+ steps,
23822
+ transitions,
23823
+ step.id,
23824
+ "yes"
23825
+ );
23826
+ leafIds.push(...yesLeaves);
23827
+ } else {
23828
+ leafIds.push(step.id);
23475
23829
  }
23476
23830
  if (def.branches.no && def.branches.no.length > 0) {
23477
- flattenSteps(def.branches.no, steps, transitions, step.id, "no");
23831
+ const noLeaves = flattenSteps(
23832
+ def.branches.no,
23833
+ steps,
23834
+ transitions,
23835
+ step.id,
23836
+ "no"
23837
+ );
23838
+ leafIds.push(...noLeaves);
23839
+ } else if (!leafIds.includes(step.id)) {
23840
+ leafIds.push(step.id);
23478
23841
  }
23479
- return null;
23842
+ prevIds = leafIds;
23843
+ continue;
23844
+ }
23845
+ if (def.type === "exit") {
23846
+ prevIds = [];
23847
+ continue;
23480
23848
  }
23481
- prevId = step.id;
23849
+ prevIds = [step.id];
23482
23850
  }
23483
- return prevId;
23851
+ return prevIds;
23484
23852
  }
23485
23853
  function toWorkflowStep(def) {
23486
23854
  return {
@@ -25434,10 +25802,10 @@ async function deployEventBridge(metadata, region, identity, webhookSecret, prog
25434
25802
  var WRAPS_PLATFORM_ACCOUNT_ID = "905130073023";
25435
25803
  async function updatePlatformRole(metadata, progress, externalId) {
25436
25804
  const roleName = "wraps-console-access-role";
25437
- const iam9 = new IAMClient2({ region: "us-east-1" });
25805
+ const iam10 = new IAMClient2({ region: "us-east-1" });
25438
25806
  let roleExists2 = false;
25439
25807
  try {
25440
- await iam9.send(new GetRoleCommand({ RoleName: roleName }));
25808
+ await iam10.send(new GetRoleCommand({ RoleName: roleName }));
25441
25809
  roleExists2 = true;
25442
25810
  } catch (error) {
25443
25811
  const isNotFound = error instanceof Error && (error.name === "NoSuchEntityException" || error.name === "NoSuchEntity" || error.message.includes("NoSuchEntity"));
@@ -25450,7 +25818,7 @@ async function updatePlatformRole(metadata, progress, externalId) {
25450
25818
  const policy = buildConsolePolicyDocument(emailConfig, smsConfig);
25451
25819
  if (roleExists2) {
25452
25820
  await progress.execute("Updating platform access role", async () => {
25453
- await iam9.send(
25821
+ await iam10.send(
25454
25822
  new PutRolePolicyCommand({
25455
25823
  RoleName: roleName,
25456
25824
  PolicyName: "wraps-console-access-policy",
@@ -25478,7 +25846,7 @@ async function updatePlatformRole(metadata, progress, externalId) {
25478
25846
  }
25479
25847
  ]
25480
25848
  };
25481
- await iam9.send(
25849
+ await iam10.send(
25482
25850
  new CreateRoleCommand({
25483
25851
  RoleName: roleName,
25484
25852
  Description: "Allows Wraps dashboard to access CloudWatch metrics and SES data",
@@ -25489,7 +25857,7 @@ async function updatePlatformRole(metadata, progress, externalId) {
25489
25857
  ]
25490
25858
  })
25491
25859
  );
25492
- await iam9.send(
25860
+ await iam10.send(
25493
25861
  new PutRolePolicyCommand({
25494
25862
  RoleName: roleName,
25495
25863
  PolicyName: "wraps-console-access-policy",
@@ -25910,10 +26278,10 @@ Run ${pc35.cyan("wraps email init")} or ${pc35.cyan("wraps sms init")} first.
25910
26278
  progress.succeed("Event streaming already configured");
25911
26279
  }
25912
26280
  const roleName = "wraps-console-access-role";
25913
- const iam9 = new IAMClient2({ region: "us-east-1" });
26281
+ const iam10 = new IAMClient2({ region: "us-east-1" });
25914
26282
  let roleExists2 = false;
25915
26283
  try {
25916
- await iam9.send(new GetRoleCommand({ RoleName: roleName }));
26284
+ await iam10.send(new GetRoleCommand({ RoleName: roleName }));
25917
26285
  roleExists2 = true;
25918
26286
  } catch (error) {
25919
26287
  const isNotFound = error instanceof Error && (error.name === "NoSuchEntityException" || error.name === "NoSuchEntity" || error.message.includes("NoSuchEntity"));
@@ -25926,7 +26294,7 @@ Run ${pc35.cyan("wraps email init")} or ${pc35.cyan("wraps sms init")} first.
25926
26294
  const smsConfig = metadata.services.sms?.config;
25927
26295
  const policy = buildConsolePolicyDocument(emailConfig, smsConfig);
25928
26296
  await progress.execute("Updating platform access role", async () => {
25929
- await iam9.send(
26297
+ await iam10.send(
25930
26298
  new PutRolePolicyCommand({
25931
26299
  RoleName: roleName,
25932
26300
  PolicyName: "wraps-console-access-policy",
@@ -26078,10 +26446,10 @@ Run ${pc37.cyan("wraps email init")} to deploy infrastructure first.
26078
26446
  process.exit(1);
26079
26447
  }
26080
26448
  const roleName = "wraps-console-access-role";
26081
- const iam9 = new IAMClient3({ region: "us-east-1" });
26449
+ const iam10 = new IAMClient3({ region: "us-east-1" });
26082
26450
  let roleExists2 = false;
26083
26451
  try {
26084
- await iam9.send(new GetRoleCommand2({ RoleName: roleName }));
26452
+ await iam10.send(new GetRoleCommand2({ RoleName: roleName }));
26085
26453
  roleExists2 = true;
26086
26454
  } catch (error) {
26087
26455
  const isNotFound = error instanceof Error && (error.name === "NoSuchEntityException" || error.name === "NoSuchEntity" || error.message.includes("NoSuchEntity"));
@@ -26150,7 +26518,7 @@ Run ${pc37.cyan("wraps email init")} to deploy infrastructure first.
26150
26518
  }
26151
26519
  ]
26152
26520
  };
26153
- await iam9.send(
26521
+ await iam10.send(
26154
26522
  new CreateRoleCommand2({
26155
26523
  RoleName: roleName,
26156
26524
  Description: "Allows Wraps dashboard to access CloudWatch metrics and SES data",
@@ -26162,7 +26530,7 @@ Run ${pc37.cyan("wraps email init")} to deploy infrastructure first.
26162
26530
  })
26163
26531
  );
26164
26532
  const { PutRolePolicyCommand: PutRolePolicyCommand2 } = await import("@aws-sdk/client-iam");
26165
- await iam9.send(
26533
+ await iam10.send(
26166
26534
  new PutRolePolicyCommand2({
26167
26535
  RoleName: roleName,
26168
26536
  PolicyName: "wraps-console-access-policy",
@@ -26173,7 +26541,7 @@ Run ${pc37.cyan("wraps email init")} to deploy infrastructure first.
26173
26541
  } else {
26174
26542
  await progress.execute("Updating IAM role permissions", async () => {
26175
26543
  const { PutRolePolicyCommand: PutRolePolicyCommand2 } = await import("@aws-sdk/client-iam");
26176
- await iam9.send(
26544
+ await iam10.send(
26177
26545
  new PutRolePolicyCommand2({
26178
26546
  RoleName: roleName,
26179
26547
  PolicyName: "wraps-console-access-policy",
@@ -27523,7 +27891,7 @@ import {
27523
27891
  } from "@aws-sdk/client-cloudwatch";
27524
27892
  async function fetchSESMetrics(roleArn, region, timeRange, tableName) {
27525
27893
  const credentials = roleArn ? await assumeRole(roleArn, region) : void 0;
27526
- const cloudwatch5 = new CloudWatchClient({ region, credentials });
27894
+ const cloudwatch6 = new CloudWatchClient({ region, credentials });
27527
27895
  const queries = [
27528
27896
  {
27529
27897
  Id: "sends",
@@ -27571,7 +27939,7 @@ async function fetchSESMetrics(roleArn, region, timeRange, tableName) {
27571
27939
  }
27572
27940
  }
27573
27941
  ];
27574
- const response = await cloudwatch5.send(
27942
+ const response = await cloudwatch6.send(
27575
27943
  new GetMetricDataCommand({
27576
27944
  MetricDataQueries: queries,
27577
27945
  StartTime: timeRange.start,
@@ -28356,7 +28724,7 @@ import {
28356
28724
  import { unmarshall as unmarshall4 } from "@aws-sdk/util-dynamodb";
28357
28725
  async function fetchSMSSpendLimits(region) {
28358
28726
  const smsClient = new PinpointSMSVoiceV2Client({ region });
28359
- const cloudwatch5 = new CloudWatchClient2({ region });
28727
+ const cloudwatch6 = new CloudWatchClient2({ region });
28360
28728
  try {
28361
28729
  const spendLimits = await smsClient.send(
28362
28730
  new DescribeSpendLimitsCommand({})
@@ -28370,7 +28738,7 @@ async function fetchSMSSpendLimits(region) {
28370
28738
  }
28371
28739
  const now = /* @__PURE__ */ new Date();
28372
28740
  const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
28373
- const metricsResponse = await cloudwatch5.send(
28741
+ const metricsResponse = await cloudwatch6.send(
28374
28742
  new GetMetricDataCommand2({
28375
28743
  MetricDataQueries: [
28376
28744
  {
@@ -29370,7 +29738,7 @@ import pc41 from "picocolors";
29370
29738
  // src/infrastructure/sms-stack.ts
29371
29739
  init_esm_shims();
29372
29740
  init_constants();
29373
- import * as aws18 from "@pulumi/aws";
29741
+ import * as aws19 from "@pulumi/aws";
29374
29742
  import * as pulumi24 from "@pulumi/pulumi";
29375
29743
  init_resource_checks();
29376
29744
  async function createSMSIAMRole(config2) {
@@ -29467,7 +29835,7 @@ async function createSMSIAMRole(config2) {
29467
29835
  });
29468
29836
  }
29469
29837
  function createSMSConfigurationSet() {
29470
- return new aws18.pinpoint.Smsvoicev2ConfigurationSet("wraps-sms-config", {
29838
+ return new aws19.pinpoint.Smsvoicev2ConfigurationSet("wraps-sms-config", {
29471
29839
  name: "wraps-sms-config",
29472
29840
  defaultMessageType: "TRANSACTIONAL",
29473
29841
  tags: {
@@ -29477,7 +29845,7 @@ function createSMSConfigurationSet() {
29477
29845
  });
29478
29846
  }
29479
29847
  function createSMSOptOutList() {
29480
- return new aws18.pinpoint.Smsvoicev2OptOutList("wraps-sms-optouts", {
29848
+ return new aws19.pinpoint.Smsvoicev2OptOutList("wraps-sms-optouts", {
29481
29849
  name: "wraps-sms-optouts",
29482
29850
  tags: {
29483
29851
  ManagedBy: "wraps-cli",
@@ -29545,7 +29913,7 @@ async function createSMSPhoneNumber(phoneNumberType, optOutList) {
29545
29913
  }
29546
29914
  };
29547
29915
  if (existingArn) {
29548
- return new aws18.pinpoint.Smsvoicev2PhoneNumber(
29916
+ return new aws19.pinpoint.Smsvoicev2PhoneNumber(
29549
29917
  "wraps-sms-number",
29550
29918
  phoneConfig,
29551
29919
  {
@@ -29554,7 +29922,7 @@ async function createSMSPhoneNumber(phoneNumberType, optOutList) {
29554
29922
  }
29555
29923
  );
29556
29924
  }
29557
- return new aws18.pinpoint.Smsvoicev2PhoneNumber(
29925
+ return new aws19.pinpoint.Smsvoicev2PhoneNumber(
29558
29926
  "wraps-sms-number",
29559
29927
  phoneConfig,
29560
29928
  {
@@ -29583,10 +29951,10 @@ async function createSMSSQSResources() {
29583
29951
  Description: "Dead letter queue for failed SMS event processing"
29584
29952
  }
29585
29953
  };
29586
- const dlq = dlqUrl ? new aws18.sqs.Queue(dlqName, dlqConfig, {
29954
+ const dlq = dlqUrl ? new aws19.sqs.Queue(dlqName, dlqConfig, {
29587
29955
  import: dlqUrl,
29588
29956
  customTimeouts: { create: "2m", update: "2m", delete: "2m" }
29589
- }) : new aws18.sqs.Queue(dlqName, dlqConfig, {
29957
+ }) : new aws19.sqs.Queue(dlqName, dlqConfig, {
29590
29958
  customTimeouts: { create: "2m", update: "2m", delete: "2m" }
29591
29959
  });
29592
29960
  const queueConfig = {
@@ -29609,10 +29977,10 @@ async function createSMSSQSResources() {
29609
29977
  Description: "Queue for SMS events from SNS"
29610
29978
  }
29611
29979
  };
29612
- const queue = queueUrl ? new aws18.sqs.Queue(queueName, queueConfig, {
29980
+ const queue = queueUrl ? new aws19.sqs.Queue(queueName, queueConfig, {
29613
29981
  import: queueUrl,
29614
29982
  customTimeouts: { create: "2m", update: "2m", delete: "2m" }
29615
- }) : new aws18.sqs.Queue(queueName, queueConfig, {
29983
+ }) : new aws19.sqs.Queue(queueName, queueConfig, {
29616
29984
  customTimeouts: { create: "2m", update: "2m", delete: "2m" }
29617
29985
  });
29618
29986
  return { queue, dlq };
@@ -29628,13 +29996,13 @@ async function createSMSSNSResources(config2) {
29628
29996
  Description: "SNS topic for SMS delivery events"
29629
29997
  }
29630
29998
  };
29631
- const topic = topicArn ? new aws18.sns.Topic("wraps-sms-events-topic", topicConfig, {
29999
+ const topic = topicArn ? new aws19.sns.Topic("wraps-sms-events-topic", topicConfig, {
29632
30000
  import: topicArn,
29633
30001
  customTimeouts: { create: "2m", update: "2m", delete: "2m" }
29634
- }) : new aws18.sns.Topic("wraps-sms-events-topic", topicConfig, {
30002
+ }) : new aws19.sns.Topic("wraps-sms-events-topic", topicConfig, {
29635
30003
  customTimeouts: { create: "2m", update: "2m", delete: "2m" }
29636
30004
  });
29637
- new aws18.sns.TopicPolicy("wraps-sms-events-topic-policy", {
30005
+ new aws19.sns.TopicPolicy("wraps-sms-events-topic-policy", {
29638
30006
  arn: topic.arn,
29639
30007
  policy: topic.arn.apply(
29640
30008
  (topicArn2) => JSON.stringify({
@@ -29651,7 +30019,7 @@ async function createSMSSNSResources(config2) {
29651
30019
  })
29652
30020
  )
29653
30021
  });
29654
- new aws18.sqs.QueuePolicy("wraps-sms-events-queue-policy", {
30022
+ new aws19.sqs.QueuePolicy("wraps-sms-events-queue-policy", {
29655
30023
  queueUrl: config2.queueUrl,
29656
30024
  policy: pulumi24.all([config2.queueArn, topic.arn]).apply(
29657
30025
  ([queueArn, topicArn2]) => JSON.stringify({
@@ -29670,7 +30038,7 @@ async function createSMSSNSResources(config2) {
29670
30038
  })
29671
30039
  )
29672
30040
  });
29673
- const subscription = new aws18.sns.TopicSubscription(
30041
+ const subscription = new aws19.sns.TopicSubscription(
29674
30042
  "wraps-sms-events-subscription",
29675
30043
  {
29676
30044
  topic: topic.arn,
@@ -29719,17 +30087,17 @@ async function createSMSDynamoDBTable() {
29719
30087
  Service: "sms"
29720
30088
  }
29721
30089
  };
29722
- return exists ? new aws18.dynamodb.Table(tableName, tableConfig, {
30090
+ return exists ? new aws19.dynamodb.Table(tableName, tableConfig, {
29723
30091
  import: tableName,
29724
30092
  customTimeouts: { create: "5m", update: "5m", delete: "5m" }
29725
- }) : new aws18.dynamodb.Table(tableName, tableConfig, {
30093
+ }) : new aws19.dynamodb.Table(tableName, tableConfig, {
29726
30094
  customTimeouts: { create: "5m", update: "5m", delete: "5m" }
29727
30095
  });
29728
30096
  }
29729
30097
  async function deploySMSLambdaFunction(config2) {
29730
30098
  const { getLambdaCode: getLambdaCode2 } = await Promise.resolve().then(() => (init_lambda(), lambda_exports));
29731
30099
  const codeDir = await getLambdaCode2("sms-event-processor");
29732
- const lambdaRole = new aws18.iam.Role("wraps-sms-lambda-role", {
30100
+ const lambdaRole = new aws19.iam.Role("wraps-sms-lambda-role", {
29733
30101
  name: "wraps-sms-lambda-role",
29734
30102
  assumeRolePolicy: JSON.stringify({
29735
30103
  Version: "2012-10-17",
@@ -29746,11 +30114,11 @@ async function deploySMSLambdaFunction(config2) {
29746
30114
  Service: "sms"
29747
30115
  }
29748
30116
  });
29749
- new aws18.iam.RolePolicyAttachment("wraps-sms-lambda-basic-execution", {
30117
+ new aws19.iam.RolePolicyAttachment("wraps-sms-lambda-basic-execution", {
29750
30118
  role: lambdaRole.name,
29751
30119
  policyArn: "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
29752
30120
  });
29753
- new aws18.iam.RolePolicy("wraps-sms-lambda-policy", {
30121
+ new aws19.iam.RolePolicy("wraps-sms-lambda-policy", {
29754
30122
  role: lambdaRole.name,
29755
30123
  policy: pulumi24.all([config2.tableName, config2.queueArn]).apply(
29756
30124
  ([tableName, queueArn]) => JSON.stringify({
@@ -29782,7 +30150,7 @@ async function deploySMSLambdaFunction(config2) {
29782
30150
  })
29783
30151
  )
29784
30152
  });
29785
- const eventProcessor = new aws18.lambda.Function(
30153
+ const eventProcessor = new aws19.lambda.Function(
29786
30154
  "wraps-sms-event-processor",
29787
30155
  {
29788
30156
  name: "wraps-sms-event-processor",
@@ -29810,7 +30178,7 @@ async function deploySMSLambdaFunction(config2) {
29810
30178
  customTimeouts: { create: "5m", update: "5m", delete: "2m" }
29811
30179
  }
29812
30180
  );
29813
- new aws18.lambda.EventSourceMapping(
30181
+ new aws19.lambda.EventSourceMapping(
29814
30182
  "wraps-sms-event-source-mapping",
29815
30183
  {
29816
30184
  eventSourceArn: config2.queueArn,
@@ -29826,7 +30194,7 @@ async function deploySMSLambdaFunction(config2) {
29826
30194
  return eventProcessor;
29827
30195
  }
29828
30196
  async function deploySMSStack(config2) {
29829
- const identity = await aws18.getCallerIdentity();
30197
+ const identity = await aws19.getCallerIdentity();
29830
30198
  const accountId = identity.accountId;
29831
30199
  let oidcProvider;
29832
30200
  if (config2.provider === "vercel" && config2.vercel) {