@wraps.dev/cli 0.3.2 → 0.3.3

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
@@ -23,11 +23,152 @@ var mail_manager_exports = {};
23
23
  __export(mail_manager_exports, {
24
24
  createMailManagerArchive: () => createMailManagerArchive
25
25
  });
26
+ import {
27
+ CreateArchiveCommand,
28
+ GetArchiveCommand,
29
+ ListArchivesCommand,
30
+ MailManagerClient
31
+ } from "@aws-sdk/client-mailmanager";
32
+ import {
33
+ PutConfigurationSetArchivingOptionsCommand,
34
+ SESv2Client
35
+ } from "@aws-sdk/client-sesv2";
36
+ function retentionToAWSPeriod(retention) {
37
+ switch (retention) {
38
+ case "3months":
39
+ return "THREE_MONTHS";
40
+ case "6months":
41
+ return "SIX_MONTHS";
42
+ case "9months":
43
+ return "NINE_MONTHS";
44
+ case "1year":
45
+ return "ONE_YEAR";
46
+ case "18months":
47
+ return "EIGHTEEN_MONTHS";
48
+ case "2years":
49
+ return "TWO_YEARS";
50
+ case "30months":
51
+ return "THIRTY_MONTHS";
52
+ case "3years":
53
+ return "THREE_YEARS";
54
+ case "4years":
55
+ return "FOUR_YEARS";
56
+ case "5years":
57
+ return "FIVE_YEARS";
58
+ case "6years":
59
+ return "SIX_YEARS";
60
+ case "7years":
61
+ return "SEVEN_YEARS";
62
+ case "8years":
63
+ return "EIGHT_YEARS";
64
+ case "9years":
65
+ return "NINE_YEARS";
66
+ case "10years":
67
+ return "TEN_YEARS";
68
+ case "permanent":
69
+ return "PERMANENT";
70
+ default:
71
+ return "THREE_MONTHS";
72
+ }
73
+ }
26
74
  async function createMailManagerArchive(config) {
27
- void config;
28
- throw new Error(
29
- "Mail Manager Archive is not yet supported in Pulumi AWS provider. Email archiving with Mail Manager is coming soon."
30
- );
75
+ const region = config.region || process.env.AWS_REGION || "us-east-1";
76
+ const archiveName = `wraps-${config.name}-archive`;
77
+ const mailManagerClient = new MailManagerClient({ region });
78
+ const sesClient = new SESv2Client({ region });
79
+ const kmsKeyArn = config.kmsKeyArn;
80
+ if (!kmsKeyArn) {
81
+ }
82
+ const awsRetention = retentionToAWSPeriod(config.retention);
83
+ let archiveId;
84
+ let archiveArn;
85
+ try {
86
+ const listCommand = new ListArchivesCommand({});
87
+ const listResult = await mailManagerClient.send(listCommand);
88
+ const existingArchive = listResult.Archives?.find(
89
+ (archive) => archive.ArchiveName === archiveName
90
+ );
91
+ if (existingArchive?.ArchiveId) {
92
+ console.log(`Using existing Mail Manager archive: ${archiveName}`);
93
+ archiveId = existingArchive.ArchiveId;
94
+ const getCommand = new GetArchiveCommand({ ArchiveId: archiveId });
95
+ const getResult = await mailManagerClient.send(getCommand);
96
+ archiveArn = getResult.ArchiveArn;
97
+ }
98
+ } catch (error) {
99
+ console.log("Error checking for existing archive:", error);
100
+ }
101
+ if (!archiveId) {
102
+ try {
103
+ const createArchiveCommand = new CreateArchiveCommand({
104
+ ArchiveName: archiveName,
105
+ Retention: {
106
+ RetentionPeriod: awsRetention
107
+ },
108
+ ...kmsKeyArn && { KmsKeyArn: kmsKeyArn },
109
+ Tags: [
110
+ { Key: "ManagedBy", Value: "wraps-cli" },
111
+ { Key: "Name", Value: archiveName },
112
+ { Key: "Retention", Value: config.retention }
113
+ ]
114
+ });
115
+ const archiveResult = await mailManagerClient.send(createArchiveCommand);
116
+ archiveId = archiveResult.ArchiveId;
117
+ if (!archiveId) {
118
+ throw new Error(
119
+ "Failed to create Mail Manager Archive: No ArchiveId returned"
120
+ );
121
+ }
122
+ console.log(`Created new Mail Manager archive: ${archiveName}`);
123
+ } catch (error) {
124
+ if (error instanceof Error && error.name === "ConflictException" && error.message.includes("Archive already exists")) {
125
+ console.log(
126
+ "Archive was created concurrently, fetching existing archive..."
127
+ );
128
+ const listCommand = new ListArchivesCommand({});
129
+ const listResult = await mailManagerClient.send(listCommand);
130
+ const existingArchive = listResult.Archives?.find(
131
+ (archive) => archive.ArchiveName === archiveName
132
+ );
133
+ if (!existingArchive?.ArchiveId) {
134
+ throw new Error(
135
+ `Archive exists but couldn't find it: ${archiveName}`
136
+ );
137
+ }
138
+ archiveId = existingArchive.ArchiveId;
139
+ const getCommand = new GetArchiveCommand({ ArchiveId: archiveId });
140
+ const getResult = await mailManagerClient.send(getCommand);
141
+ archiveArn = getResult.ArchiveArn;
142
+ } else {
143
+ throw error;
144
+ }
145
+ }
146
+ }
147
+ if (!archiveArn) {
148
+ const identity = await import("@aws-sdk/client-sts").then(
149
+ (m) => new m.STSClient({ region }).send(new m.GetCallerIdentityCommand({}))
150
+ );
151
+ const accountId = identity.Account;
152
+ archiveArn = `arn:aws:ses:${region}:${accountId}:mailmanager-archive/${archiveId}`;
153
+ }
154
+ const configSetName = await new Promise((resolve) => {
155
+ config.configSetName.apply((name) => {
156
+ resolve(name);
157
+ });
158
+ });
159
+ const putArchivingOptionsCommand = new PutConfigurationSetArchivingOptionsCommand({
160
+ ConfigurationSetName: configSetName,
161
+ ArchiveArn: archiveArn
162
+ });
163
+ await sesClient.send(putArchivingOptionsCommand);
164
+ if (!(archiveId && archiveArn)) {
165
+ throw new Error("Failed to get archive ID or ARN");
166
+ }
167
+ return {
168
+ archiveId,
169
+ archiveArn,
170
+ kmsKeyArn
171
+ };
31
172
  }
32
173
  var init_mail_manager = __esm({
33
174
  "src/infrastructure/resources/mail-manager.ts"() {
@@ -239,13 +380,22 @@ var init_aws = __esm({
239
380
  function estimateStorageSize(emailsPerMonth, retention, numEventTypes = 8) {
240
381
  const avgRecordSizeKB = 2;
241
382
  const retentionMonths = {
242
- "7days": 0.25,
243
- "30days": 1,
244
- "90days": 3,
383
+ "3months": 3,
245
384
  "6months": 6,
385
+ "9months": 9,
246
386
  "1year": 12,
247
387
  "18months": 18,
248
- indefinite: 120
388
+ "2years": 24,
389
+ "30months": 30,
390
+ "3years": 36,
391
+ "4years": 48,
392
+ "5years": 60,
393
+ "6years": 72,
394
+ "7years": 84,
395
+ "8years": 96,
396
+ "9years": 108,
397
+ "10years": 120,
398
+ permanent: 120
249
399
  // Assume 10 years for cost estimation
250
400
  }[retention];
251
401
  const totalKB = emailsPerMonth * numEventTypes * (retentionMonths ?? 12) * avgRecordSizeKB;
@@ -254,13 +404,22 @@ function estimateStorageSize(emailsPerMonth, retention, numEventTypes = 8) {
254
404
  function estimateArchiveStorageSize(emailsPerMonth, retention) {
255
405
  const avgEmailSizeKB = 50;
256
406
  const retentionMonths = {
257
- "7days": 0.25,
258
- "30days": 1,
259
- "90days": 3,
407
+ "3months": 3,
260
408
  "6months": 6,
409
+ "9months": 9,
261
410
  "1year": 12,
262
411
  "18months": 18,
263
- indefinite: 120
412
+ "2years": 24,
413
+ "30months": 30,
414
+ "3years": 36,
415
+ "4years": 48,
416
+ "5years": 60,
417
+ "6years": 72,
418
+ "7years": 84,
419
+ "8years": 96,
420
+ "9years": 108,
421
+ "10years": 120,
422
+ permanent: 120
264
423
  // Assume 10 years for cost estimation
265
424
  }[retention];
266
425
  const totalKB = emailsPerMonth * (retentionMonths ?? 12) * avgEmailSizeKB;
@@ -300,7 +459,7 @@ function calculateDynamoDBCost(config, emailsPerMonth) {
300
459
  if (!config.eventTracking?.dynamoDBHistory) {
301
460
  return;
302
461
  }
303
- const retention = config.eventTracking.archiveRetention || "90days";
462
+ const retention = config.eventTracking.archiveRetention || "3months";
304
463
  const numEventTypes = config.eventTracking.events?.length || 8;
305
464
  const totalEvents = emailsPerMonth * numEventTypes;
306
465
  const writeCost = Math.max(0, totalEvents - FREE_TIER.DYNAMODB_WRITES) / 1e6 * AWS_PRICING.DYNAMODB_WRITE_PER_MILLION;
@@ -561,7 +720,7 @@ function getPresetInfo(preset) {
561
720
  "Everything in Starter",
562
721
  "Reputation metrics dashboard",
563
722
  "Real-time event tracking (EventBridge)",
564
- "90-day email history storage",
723
+ "3-month email history storage",
565
724
  "Optional: Email archiving with rendered viewer",
566
725
  "Complete event visibility"
567
726
  ]
@@ -630,9 +789,9 @@ function validateConfig(config) {
630
789
  "\u{1F4A1} Event tracking is enabled but history storage is disabled. Events will only be available in real-time."
631
790
  );
632
791
  }
633
- if (config.eventTracking?.archiveRetention === "indefinite") {
792
+ if (config.eventTracking?.archiveRetention === "permanent") {
634
793
  warnings.push(
635
- "\u26A0\uFE0F Indefinite retention can become expensive. Consider 90-day or 1-year retention."
794
+ "\u26A0\uFE0F Permanent retention can become expensive. Consider 3-month or 1-year retention."
636
795
  );
637
796
  }
638
797
  return warnings;
@@ -661,7 +820,7 @@ var init_presets = __esm({
661
820
  // Email archiving disabled by default
662
821
  emailArchiving: {
663
822
  enabled: false,
664
- retention: "30days"
823
+ retention: "3months"
665
824
  },
666
825
  sendingEnabled: true
667
826
  };
@@ -691,13 +850,13 @@ var init_presets = __esm({
691
850
  "RENDERING_FAILURE"
692
851
  ],
693
852
  dynamoDBHistory: true,
694
- archiveRetention: "90days"
853
+ archiveRetention: "3months"
695
854
  },
696
- // Email archiving with 90-day retention
855
+ // Email archiving with 3-month retention
697
856
  emailArchiving: {
698
857
  enabled: false,
699
858
  // User can opt-in
700
- retention: "90days"
859
+ retention: "3months"
701
860
  },
702
861
  sendingEnabled: true
703
862
  };
@@ -1125,7 +1284,7 @@ async function promptEmailArchiving() {
1125
1284
  process.exit(0);
1126
1285
  }
1127
1286
  if (!enabled) {
1128
- return { enabled: false, retention: "90days" };
1287
+ return { enabled: false, retention: "3months" };
1129
1288
  }
1130
1289
  const retention = await clack3.select({
1131
1290
  message: "Email archive retention period:",
@@ -1133,7 +1292,7 @@ async function promptEmailArchiving() {
1133
1292
  { value: "7days", label: "7 days", hint: "~$1-2/mo for 10k emails" },
1134
1293
  { value: "30days", label: "30 days", hint: "~$2-4/mo for 10k emails" },
1135
1294
  {
1136
- value: "90days",
1295
+ value: "3months",
1137
1296
  label: "90 days (recommended)",
1138
1297
  hint: "~$5-10/mo for 10k emails"
1139
1298
  },
@@ -1149,7 +1308,7 @@ async function promptEmailArchiving() {
1149
1308
  hint: "~$35-60/mo for 10k emails"
1150
1309
  }
1151
1310
  ],
1152
- initialValue: "90days"
1311
+ initialValue: "3months"
1153
1312
  });
1154
1313
  if (clack3.isCancel(retention)) {
1155
1314
  clack3.cancel("Operation cancelled.");
@@ -1188,7 +1347,7 @@ async function promptCustomConfig() {
1188
1347
  process.exit(0);
1189
1348
  }
1190
1349
  let dynamoDBHistory = false;
1191
- let archiveRetention = "90days";
1350
+ let archiveRetention = "3months";
1192
1351
  if (eventTrackingEnabled) {
1193
1352
  dynamoDBHistory = await clack3.confirm({
1194
1353
  message: "Store email history in DynamoDB?",
@@ -1205,7 +1364,7 @@ async function promptCustomConfig() {
1205
1364
  { value: "7days", label: "7 days", hint: "Minimal storage cost" },
1206
1365
  { value: "30days", label: "30 days", hint: "Development/testing" },
1207
1366
  {
1208
- value: "90days",
1367
+ value: "3months",
1209
1368
  label: "90 days (recommended)",
1210
1369
  hint: "Standard retention"
1211
1370
  },
@@ -1255,7 +1414,7 @@ async function promptCustomConfig() {
1255
1414
  clack3.cancel("Operation cancelled.");
1256
1415
  process.exit(0);
1257
1416
  }
1258
- let emailArchiveRetention = "90days";
1417
+ let emailArchiveRetention = "3months";
1259
1418
  if (emailArchivingEnabled) {
1260
1419
  emailArchiveRetention = await clack3.select({
1261
1420
  message: "Email archive retention period:",
@@ -1263,7 +1422,7 @@ async function promptCustomConfig() {
1263
1422
  { value: "7days", label: "7 days", hint: "~$1-2/mo for 10k emails" },
1264
1423
  { value: "30days", label: "30 days", hint: "~$2-4/mo for 10k emails" },
1265
1424
  {
1266
- value: "90days",
1425
+ value: "3months",
1267
1426
  label: "90 days (recommended)",
1268
1427
  hint: "~$5-10/mo for 10k emails"
1269
1428
  },
@@ -1279,7 +1438,7 @@ async function promptCustomConfig() {
1279
1438
  hint: "~$35-60/mo for 10k emails"
1280
1439
  }
1281
1440
  ],
1282
- initialValue: "90days"
1441
+ initialValue: "3months"
1283
1442
  });
1284
1443
  if (clack3.isCancel(emailArchiveRetention)) {
1285
1444
  clack3.cancel("Operation cancelled.");
@@ -1320,12 +1479,12 @@ async function promptCustomConfig() {
1320
1479
  "RENDERING_FAILURE"
1321
1480
  ],
1322
1481
  dynamoDBHistory: Boolean(dynamoDBHistory),
1323
- archiveRetention: typeof archiveRetention === "string" ? archiveRetention : "90days"
1482
+ archiveRetention: typeof archiveRetention === "string" ? archiveRetention : "3months"
1324
1483
  } : { enabled: false },
1325
1484
  emailArchiving: emailArchivingEnabled ? {
1326
1485
  enabled: true,
1327
- retention: typeof emailArchiveRetention === "string" ? emailArchiveRetention : "90days"
1328
- } : { enabled: false, retention: "90days" },
1486
+ retention: typeof emailArchiveRetention === "string" ? emailArchiveRetention : "3months"
1487
+ } : { enabled: false, retention: "3months" },
1329
1488
  dedicatedIp,
1330
1489
  sendingEnabled: true
1331
1490
  };
@@ -1373,13 +1532,113 @@ var init_assume_role = __esm({
1373
1532
  // src/utils/archive.ts
1374
1533
  import {
1375
1534
  GetArchiveMessageCommand,
1376
- MailManagerClient
1535
+ GetArchiveSearchResultsCommand,
1536
+ MailManagerClient as MailManagerClient2,
1537
+ StartArchiveSearchCommand
1377
1538
  } from "@aws-sdk/client-mailmanager";
1378
1539
  import { simpleParser } from "mailparser";
1379
- async function getArchivedEmail(_archiveId, messageId, region) {
1380
- const client = new MailManagerClient({ region });
1540
+ function extractArchiveId(archiveArnOrId) {
1541
+ if (archiveArnOrId.startsWith("arn:")) {
1542
+ const parts = archiveArnOrId.split("/");
1543
+ return parts.at(-1);
1544
+ }
1545
+ return archiveArnOrId;
1546
+ }
1547
+ async function getArchivedEmail(archiveArnOrId, searchCriteria, region) {
1548
+ const client = new MailManagerClient2({ region });
1549
+ const archiveId = extractArchiveId(archiveArnOrId);
1550
+ const searchTime = searchCriteria.timestamp || /* @__PURE__ */ new Date();
1551
+ const dayBefore = new Date(searchTime.getTime() - 24 * 60 * 60 * 1e3);
1552
+ const dayAfter = new Date(searchTime.getTime() + 24 * 60 * 60 * 1e3);
1553
+ const filters = [];
1554
+ if (searchCriteria.from) {
1555
+ filters.push({
1556
+ StringExpression: {
1557
+ Evaluate: {
1558
+ Attribute: "FROM"
1559
+ },
1560
+ Operator: "CONTAINS",
1561
+ Values: [searchCriteria.from]
1562
+ }
1563
+ });
1564
+ }
1565
+ if (searchCriteria.to) {
1566
+ filters.push({
1567
+ StringExpression: {
1568
+ Evaluate: {
1569
+ Attribute: "TO"
1570
+ },
1571
+ Operator: "CONTAINS",
1572
+ Values: [searchCriteria.to]
1573
+ }
1574
+ });
1575
+ }
1576
+ if (searchCriteria.subject) {
1577
+ filters.push({
1578
+ StringExpression: {
1579
+ Evaluate: {
1580
+ Attribute: "SUBJECT"
1581
+ },
1582
+ Operator: "CONTAINS",
1583
+ Values: [searchCriteria.subject]
1584
+ }
1585
+ });
1586
+ }
1587
+ if (filters.length === 0) {
1588
+ throw new Error(
1589
+ "At least one search criterion (from, to, or subject) is required"
1590
+ );
1591
+ }
1592
+ const searchCommand = new StartArchiveSearchCommand({
1593
+ ArchiveId: archiveId,
1594
+ FromTimestamp: dayBefore,
1595
+ ToTimestamp: dayAfter,
1596
+ Filters: {
1597
+ Include: filters
1598
+ },
1599
+ MaxResults: 10
1600
+ // Get a few results in case there are multiple matches
1601
+ });
1602
+ const searchResponse = await client.send(searchCommand);
1603
+ const searchId = searchResponse.SearchId;
1604
+ if (!searchId) {
1605
+ throw new Error("Failed to start archive search");
1606
+ }
1607
+ let archivedMessageId;
1608
+ let attempts = 0;
1609
+ const maxAttempts = 20;
1610
+ const pollInterval = 1e3;
1611
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
1612
+ while (attempts < maxAttempts) {
1613
+ try {
1614
+ const resultsCommand = new GetArchiveSearchResultsCommand({
1615
+ SearchId: searchId
1616
+ });
1617
+ const resultsResponse = await client.send(resultsCommand);
1618
+ if (resultsResponse.Rows && resultsResponse.Rows.length > 0) {
1619
+ archivedMessageId = resultsResponse.Rows[0].ArchivedMessageId;
1620
+ break;
1621
+ }
1622
+ if (resultsResponse.Rows && resultsResponse.Rows.length === 0) {
1623
+ break;
1624
+ }
1625
+ } catch (error) {
1626
+ if (error instanceof Error && error.name === "ConflictException" && error.message.includes("still in progress")) {
1627
+ console.log(`Search still in progress, attempt ${attempts + 1}...`);
1628
+ } else {
1629
+ throw error;
1630
+ }
1631
+ }
1632
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
1633
+ attempts++;
1634
+ }
1635
+ if (!archivedMessageId) {
1636
+ throw new Error(
1637
+ "Email not found in archive with the provided search criteria. It may have been sent before archiving was enabled."
1638
+ );
1639
+ }
1381
1640
  const command2 = new GetArchiveMessageCommand({
1382
- ArchivedMessageId: messageId
1641
+ ArchivedMessageId: archivedMessageId
1383
1642
  });
1384
1643
  const response = await client.send(command2);
1385
1644
  if (!response.MessageDownloadLink) {
@@ -1418,8 +1677,7 @@ async function getArchivedEmail(_archiveId, messageId, region) {
1418
1677
  return addr.text || "";
1419
1678
  };
1420
1679
  return {
1421
- messageId,
1422
- // Use the input messageId since response may not have MessageMetadata
1680
+ messageId: parsed.messageId || headers["message-id"]?.toString() || "",
1423
1681
  from: getAddressText(parsed.from),
1424
1682
  to: getAddressText(parsed.to),
1425
1683
  subject: parsed.subject || "",
@@ -1446,14 +1704,20 @@ __export(email_archive_exports, {
1446
1704
  fetchArchivedEmail: () => fetchArchivedEmail
1447
1705
  });
1448
1706
  async function fetchArchivedEmail(messageId, options) {
1449
- const { region, archiveArn } = options;
1707
+ const { region, archiveArn, from, to, subject, timestamp } = options;
1450
1708
  try {
1451
1709
  console.log("Fetching archived email:", {
1452
1710
  messageId,
1453
1711
  archiveArn,
1454
1712
  region
1455
1713
  });
1456
- const email = await getArchivedEmail(archiveArn, messageId, region);
1714
+ const searchCriteria = {
1715
+ from,
1716
+ to,
1717
+ subject,
1718
+ timestamp
1719
+ };
1720
+ const email = await getArchivedEmail(archiveArn, searchCriteria, region);
1457
1721
  console.log("Archived email fetched successfully:", {
1458
1722
  messageId: email.messageId,
1459
1723
  hasHtml: !!email.html,
@@ -1880,11 +2144,18 @@ async function createIAMRole(config) {
1880
2144
  statements.push({
1881
2145
  Effect: "Allow",
1882
2146
  Action: [
1883
- "ses:GetArchive",
2147
+ // Archive search operations
2148
+ "ses:StartArchiveSearch",
2149
+ "ses:GetArchiveSearchResults",
2150
+ // Archive message retrieval
1884
2151
  "ses:GetArchiveMessage",
1885
2152
  "ses:GetArchiveMessageContent",
1886
- "ses:SearchArchive",
1887
- "ses:StartArchiveExport"
2153
+ // Archive metadata
2154
+ "ses:GetArchive",
2155
+ "ses:ListArchives",
2156
+ // Archive export (for future use)
2157
+ "ses:StartArchiveExport",
2158
+ "ses:GetArchiveExport"
1888
2159
  ],
1889
2160
  Resource: "arn:aws:ses:*:*:mailmanager-archive/*"
1890
2161
  });
@@ -2271,7 +2542,8 @@ async function deployEmailStack(config) {
2271
2542
  archiveResources = await createMailManagerArchive2({
2272
2543
  name: "email",
2273
2544
  retention: emailConfig.emailArchiving.retention,
2274
- configSetName: sesResources.configSet.configurationSetName
2545
+ configSetName: sesResources.configSet.configurationSetName,
2546
+ region: config.region
2275
2547
  });
2276
2548
  }
2277
2549
  return {
@@ -2288,7 +2560,8 @@ async function deployEmailStack(config) {
2288
2560
  dlqUrl: sqsResources?.dlq.url,
2289
2561
  customTrackingDomain: sesResources?.customTrackingDomain,
2290
2562
  mailFromDomain: sesResources?.mailFromDomain,
2291
- archiveArn: archiveResources?.archive.arn,
2563
+ archiveId: archiveResources?.archiveId,
2564
+ archiveArn: archiveResources?.archiveArn,
2292
2565
  archivingEnabled: emailConfig.emailArchiving?.enabled,
2293
2566
  archiveRetention: emailConfig.emailArchiving?.enabled ? emailConfig.emailArchiving.retention : void 0
2294
2567
  };
@@ -2606,11 +2879,11 @@ ${domainStrings.join("\n")}`);
2606
2879
  const retentionLabel = {
2607
2880
  "7days": "7 days",
2608
2881
  "30days": "30 days",
2609
- "90days": "90 days",
2882
+ "3months": "90 days",
2610
2883
  "6months": "6 months",
2611
2884
  "1year": "1 year",
2612
2885
  "18months": "18 months"
2613
- }[status2.resources.archiveRetention || "90days"] || "90 days";
2886
+ }[status2.resources.archiveRetention || "3months"] || "90 days";
2614
2887
  featureLines.push(
2615
2888
  ` ${pc2.green("\u2713")} Email Archiving ${pc2.dim(`(${retentionLabel} retention)`)}`
2616
2889
  );
@@ -3199,7 +3472,7 @@ import { Router as createRouter } from "express";
3199
3472
  init_esm_shims();
3200
3473
  init_assume_role();
3201
3474
  import { GetSendQuotaCommand, SESClient as SESClient3 } from "@aws-sdk/client-ses";
3202
- import { GetEmailIdentityCommand, SESv2Client } from "@aws-sdk/client-sesv2";
3475
+ import { GetEmailIdentityCommand, SESv2Client as SESv2Client2 } from "@aws-sdk/client-sesv2";
3203
3476
  async function fetchSendQuota(roleArn, region) {
3204
3477
  const credentials = roleArn ? await assumeRole(roleArn, region) : void 0;
3205
3478
  const ses = new SESClient3({ region, credentials });
@@ -3212,7 +3485,7 @@ async function fetchSendQuota(roleArn, region) {
3212
3485
  }
3213
3486
  async function fetchDomainInfo(roleArn, region, domain) {
3214
3487
  const credentials = roleArn ? await assumeRole(roleArn, region) : void 0;
3215
- const sesv22 = new SESv2Client({ region, credentials });
3488
+ const sesv22 = new SESv2Client2({ region, credentials });
3216
3489
  const response = await sesv22.send(
3217
3490
  new GetEmailIdentityCommand({
3218
3491
  EmailIdentity: domain
@@ -3631,11 +3904,33 @@ function createEmailsRouter(config) {
3631
3904
  error: "Archive ARN not configured."
3632
3905
  });
3633
3906
  }
3907
+ if (!config.tableName) {
3908
+ console.log("No table name configured");
3909
+ return res.status(400).json({
3910
+ error: "Email tracking not enabled. Need email metadata to search archive."
3911
+ });
3912
+ }
3913
+ console.log("Fetching email metadata from DynamoDB...");
3914
+ const emailDetails = await fetchEmailById(id, {
3915
+ region: config.region,
3916
+ tableName: config.tableName
3917
+ });
3918
+ if (!emailDetails) {
3919
+ console.log("Email metadata not found in DynamoDB for ID:", id);
3920
+ return res.status(404).json({
3921
+ error: "Email metadata not found. Cannot search archive."
3922
+ });
3923
+ }
3634
3924
  console.log("Fetching archived email from Mail Manager...");
3635
3925
  const { fetchArchivedEmail: fetchArchivedEmail2 } = await Promise.resolve().then(() => (init_email_archive(), email_archive_exports));
3636
3926
  const archivedEmail = await fetchArchivedEmail2(id, {
3637
3927
  region: config.region,
3638
- archiveArn: config.archiveArn
3928
+ archiveArn: config.archiveArn,
3929
+ from: emailDetails.from,
3930
+ to: emailDetails.to[0],
3931
+ // Use first recipient for search
3932
+ subject: emailDetails.subject,
3933
+ timestamp: new Date(emailDetails.sentAt)
3639
3934
  });
3640
3935
  if (!archivedEmail) {
3641
3936
  console.log("Archived email not found for message ID:", id);
@@ -3882,11 +4177,11 @@ init_assume_role();
3882
4177
  import {
3883
4178
  GetConfigurationSetCommand,
3884
4179
  GetEmailIdentityCommand as GetEmailIdentityCommand2,
3885
- SESv2Client as SESv2Client2
4180
+ SESv2Client as SESv2Client3
3886
4181
  } from "@aws-sdk/client-sesv2";
3887
4182
  async function fetchConfigurationSet(roleArn, region, configSetName) {
3888
4183
  const credentials = roleArn ? await assumeRole(roleArn, region) : void 0;
3889
- const sesv22 = new SESv2Client2({ region, credentials });
4184
+ const sesv22 = new SESv2Client3({ region, credentials });
3890
4185
  const response = await sesv22.send(
3891
4186
  new GetConfigurationSetCommand({
3892
4187
  ConfigurationSetName: configSetName
@@ -3916,7 +4211,7 @@ async function fetchConfigurationSet(roleArn, region, configSetName) {
3916
4211
  }
3917
4212
  async function fetchEmailIdentity(roleArn, region, identityName) {
3918
4213
  const credentials = roleArn ? await assumeRole(roleArn, region) : void 0;
3919
- const sesv22 = new SESv2Client2({ region, credentials });
4214
+ const sesv22 = new SESv2Client3({ region, credentials });
3920
4215
  const response = await sesv22.send(
3921
4216
  new GetEmailIdentityCommand2({
3922
4217
  EmailIdentity: identityName
@@ -4108,10 +4403,10 @@ function createSettingsRouter(config) {
4108
4403
  console.log(
4109
4404
  `[Settings] Updating sending options for ${configSetName}: ${enabled}`
4110
4405
  );
4111
- const { SESv2Client: SESv2Client4, PutConfigurationSetSendingOptionsCommand } = await import("@aws-sdk/client-sesv2");
4406
+ const { SESv2Client: SESv2Client5, PutConfigurationSetSendingOptionsCommand } = await import("@aws-sdk/client-sesv2");
4112
4407
  const { assumeRole: assumeRole2 } = await Promise.resolve().then(() => (init_assume_role(), assume_role_exports));
4113
4408
  const credentials = config.roleArn ? await assumeRole2(config.roleArn, config.region) : void 0;
4114
- const sesClient = new SESv2Client4({ region: config.region, credentials });
4409
+ const sesClient = new SESv2Client5({ region: config.region, credentials });
4115
4410
  await sesClient.send(
4116
4411
  new PutConfigurationSetSendingOptionsCommand({
4117
4412
  ConfigurationSetName: configSetName,
@@ -4145,10 +4440,10 @@ function createSettingsRouter(config) {
4145
4440
  console.log(
4146
4441
  `[Settings] Updating reputation options for ${configSetName}: ${enabled}`
4147
4442
  );
4148
- const { SESv2Client: SESv2Client4, PutConfigurationSetReputationOptionsCommand } = await import("@aws-sdk/client-sesv2");
4443
+ const { SESv2Client: SESv2Client5, PutConfigurationSetReputationOptionsCommand } = await import("@aws-sdk/client-sesv2");
4149
4444
  const { assumeRole: assumeRole2 } = await Promise.resolve().then(() => (init_assume_role(), assume_role_exports));
4150
4445
  const credentials = config.roleArn ? await assumeRole2(config.roleArn, config.region) : void 0;
4151
- const sesClient = new SESv2Client4({ region: config.region, credentials });
4446
+ const sesClient = new SESv2Client5({ region: config.region, credentials });
4152
4447
  await sesClient.send(
4153
4448
  new PutConfigurationSetReputationOptionsCommand({
4154
4449
  ConfigurationSetName: configSetName,
@@ -4188,10 +4483,10 @@ function createSettingsRouter(config) {
4188
4483
  console.log(
4189
4484
  `[Settings] Updating tracking domain for ${configSetName}: ${domain}`
4190
4485
  );
4191
- const { SESv2Client: SESv2Client4, PutConfigurationSetTrackingOptionsCommand } = await import("@aws-sdk/client-sesv2");
4486
+ const { SESv2Client: SESv2Client5, PutConfigurationSetTrackingOptionsCommand } = await import("@aws-sdk/client-sesv2");
4192
4487
  const { assumeRole: assumeRole2 } = await Promise.resolve().then(() => (init_assume_role(), assume_role_exports));
4193
4488
  const credentials = config.roleArn ? await assumeRole2(config.roleArn, config.region) : void 0;
4194
- const sesClient = new SESv2Client4({
4489
+ const sesClient = new SESv2Client5({
4195
4490
  region: config.region,
4196
4491
  credentials
4197
4492
  });
@@ -4819,8 +5114,8 @@ Run ${pc9.cyan("wraps init")} to deploy infrastructure.
4819
5114
  process.exit(1);
4820
5115
  }
4821
5116
  const domains = await listSESDomains(region);
4822
- const { SESv2Client: SESv2Client4, GetEmailIdentityCommand: GetEmailIdentityCommand4 } = await import("@aws-sdk/client-sesv2");
4823
- const sesv2Client = new SESv2Client4({ region });
5117
+ const { SESv2Client: SESv2Client5, GetEmailIdentityCommand: GetEmailIdentityCommand4 } = await import("@aws-sdk/client-sesv2");
5118
+ const sesv2Client = new SESv2Client5({ region });
4824
5119
  const domainsWithTokens = await Promise.all(
4825
5120
  domains.map(async (d) => {
4826
5121
  try {
@@ -4981,7 +5276,11 @@ ${pc10.bold("Current Configuration:")}
4981
5276
  lambdaFunctions: result.lambdaFunctions,
4982
5277
  domain: result.domain,
4983
5278
  dkimTokens: result.dkimTokens,
4984
- customTrackingDomain: result.customTrackingDomain
5279
+ customTrackingDomain: result.customTrackingDomain,
5280
+ mailFromDomain: result.mailFromDomain,
5281
+ archiveArn: result.archiveArn,
5282
+ archivingEnabled: result.archivingEnabled,
5283
+ archiveRetention: result.archiveRetention
4985
5284
  };
4986
5285
  }
4987
5286
  },
@@ -5008,7 +5307,11 @@ ${pc10.bold("Current Configuration:")}
5008
5307
  lambdaFunctions: pulumiOutputs.lambdaFunctions?.value,
5009
5308
  domain: pulumiOutputs.domain?.value,
5010
5309
  dkimTokens: pulumiOutputs.dkimTokens?.value,
5011
- customTrackingDomain: pulumiOutputs.customTrackingDomain?.value
5310
+ customTrackingDomain: pulumiOutputs.customTrackingDomain?.value,
5311
+ mailFromDomain: pulumiOutputs.mailFromDomain?.value,
5312
+ archiveArn: pulumiOutputs.archiveArn?.value,
5313
+ archivingEnabled: pulumiOutputs.archivingEnabled?.value,
5314
+ archiveRetention: pulumiOutputs.archiveRetention?.value
5012
5315
  };
5013
5316
  }
5014
5317
  );
@@ -5119,7 +5422,7 @@ ${pc11.bold("Current Configuration:")}
5119
5422
  console.log(` ${pc11.green("\u2713")} Event Tracking (EventBridge)`);
5120
5423
  if (config.eventTracking.dynamoDBHistory) {
5121
5424
  console.log(
5122
- ` ${pc11.dim("\u2514\u2500")} Email History: ${pc11.cyan(config.eventTracking.archiveRetention || "90days")}`
5425
+ ` ${pc11.dim("\u2514\u2500")} Email History: ${pc11.cyan(config.eventTracking.archiveRetention || "3months")}`
5123
5426
  );
5124
5427
  }
5125
5428
  }
@@ -5128,14 +5431,23 @@ ${pc11.bold("Current Configuration:")}
5128
5431
  }
5129
5432
  if (config.emailArchiving?.enabled) {
5130
5433
  const retentionLabel = {
5131
- "7days": "7 days",
5132
- "30days": "30 days",
5133
- "90days": "90 days",
5434
+ "3months": "3 months",
5134
5435
  "6months": "6 months",
5436
+ "9months": "9 months",
5135
5437
  "1year": "1 year",
5136
5438
  "18months": "18 months",
5137
- indefinite: "indefinite"
5138
- }[config.emailArchiving.retention] || "90 days";
5439
+ "2years": "2 years",
5440
+ "30months": "30 months",
5441
+ "3years": "3 years",
5442
+ "4years": "4 years",
5443
+ "5years": "5 years",
5444
+ "6years": "6 years",
5445
+ "7years": "7 years",
5446
+ "8years": "8 years",
5447
+ "9years": "9 years",
5448
+ "10years": "10 years",
5449
+ permanent: "permanent"
5450
+ }[config.emailArchiving.retention] || "3 months";
5139
5451
  console.log(` ${pc11.green("\u2713")} Email Archiving (${retentionLabel})`);
5140
5452
  }
5141
5453
  const currentCostData = calculateCosts(config, 5e4);
@@ -5265,18 +5577,8 @@ ${pc11.bold("Current Configuration:")}
5265
5577
  message: "Email archive retention period:",
5266
5578
  options: [
5267
5579
  {
5268
- value: "7days",
5269
- label: "7 days",
5270
- hint: "~$1-2/mo for 10k emails"
5271
- },
5272
- {
5273
- value: "30days",
5274
- label: "30 days",
5275
- hint: "~$2-4/mo for 10k emails"
5276
- },
5277
- {
5278
- value: "90days",
5279
- label: "90 days (recommended)",
5580
+ value: "3months",
5581
+ label: "3 months (minimum)",
5280
5582
  hint: "~$5-10/mo for 10k emails"
5281
5583
  },
5282
5584
  {
@@ -5286,13 +5588,38 @@ ${pc11.bold("Current Configuration:")}
5286
5588
  },
5287
5589
  {
5288
5590
  value: "1year",
5289
- label: "1 year",
5591
+ label: "1 year (recommended)",
5290
5592
  hint: "~$25-40/mo for 10k emails"
5291
5593
  },
5292
5594
  {
5293
- value: "18months",
5294
- label: "18 months",
5295
- hint: "~$35-60/mo for 10k emails"
5595
+ value: "2years",
5596
+ label: "2 years",
5597
+ hint: "~$50-80/mo for 10k emails"
5598
+ },
5599
+ {
5600
+ value: "3years",
5601
+ label: "3 years",
5602
+ hint: "~$75-120/mo for 10k emails"
5603
+ },
5604
+ {
5605
+ value: "5years",
5606
+ label: "5 years",
5607
+ hint: "~$125-200/mo for 10k emails"
5608
+ },
5609
+ {
5610
+ value: "7years",
5611
+ label: "7 years",
5612
+ hint: "~$175-280/mo for 10k emails"
5613
+ },
5614
+ {
5615
+ value: "10years",
5616
+ label: "10 years",
5617
+ hint: "~$250-400/mo for 10k emails"
5618
+ },
5619
+ {
5620
+ value: "permanent",
5621
+ label: "Permanent",
5622
+ hint: "Expensive, not recommended"
5296
5623
  }
5297
5624
  ],
5298
5625
  initialValue: config.emailArchiving.retention
@@ -5326,18 +5653,8 @@ ${pc11.bold("Current Configuration:")}
5326
5653
  message: "Email archive retention period:",
5327
5654
  options: [
5328
5655
  {
5329
- value: "7days",
5330
- label: "7 days",
5331
- hint: "~$1-2/mo for 10k emails"
5332
- },
5333
- {
5334
- value: "30days",
5335
- label: "30 days",
5336
- hint: "~$2-4/mo for 10k emails"
5337
- },
5338
- {
5339
- value: "90days",
5340
- label: "90 days (recommended)",
5656
+ value: "3months",
5657
+ label: "3 months (minimum)",
5341
5658
  hint: "~$5-10/mo for 10k emails"
5342
5659
  },
5343
5660
  {
@@ -5347,16 +5664,41 @@ ${pc11.bold("Current Configuration:")}
5347
5664
  },
5348
5665
  {
5349
5666
  value: "1year",
5350
- label: "1 year",
5667
+ label: "1 year (recommended)",
5351
5668
  hint: "~$25-40/mo for 10k emails"
5352
5669
  },
5353
5670
  {
5354
- value: "18months",
5355
- label: "18 months",
5356
- hint: "~$35-60/mo for 10k emails"
5671
+ value: "2years",
5672
+ label: "2 years",
5673
+ hint: "~$50-80/mo for 10k emails"
5674
+ },
5675
+ {
5676
+ value: "3years",
5677
+ label: "3 years",
5678
+ hint: "~$75-120/mo for 10k emails"
5679
+ },
5680
+ {
5681
+ value: "5years",
5682
+ label: "5 years",
5683
+ hint: "~$125-200/mo for 10k emails"
5684
+ },
5685
+ {
5686
+ value: "7years",
5687
+ label: "7 years",
5688
+ hint: "~$175-280/mo for 10k emails"
5689
+ },
5690
+ {
5691
+ value: "10years",
5692
+ label: "10 years",
5693
+ hint: "~$250-400/mo for 10k emails"
5694
+ },
5695
+ {
5696
+ value: "permanent",
5697
+ label: "Permanent",
5698
+ hint: "Expensive, not recommended"
5357
5699
  }
5358
5700
  ],
5359
- initialValue: "90days"
5701
+ initialValue: "3months"
5360
5702
  });
5361
5703
  if (clack11.isCancel(retention)) {
5362
5704
  clack11.cancel("Upgrade cancelled.");
@@ -5443,11 +5785,9 @@ ${pc11.bold("Current Configuration:")}
5443
5785
  const retention = await clack11.select({
5444
5786
  message: "Email history retention period (event data in DynamoDB):",
5445
5787
  options: [
5446
- { value: "7days", label: "7 days", hint: "Minimal storage cost" },
5447
- { value: "30days", label: "30 days", hint: "Development/testing" },
5448
5788
  {
5449
- value: "90days",
5450
- label: "90 days (recommended)",
5789
+ value: "3months",
5790
+ label: "3 months (recommended)",
5451
5791
  hint: "Standard retention"
5452
5792
  },
5453
5793
  {
@@ -5460,9 +5800,19 @@ ${pc11.bold("Current Configuration:")}
5460
5800
  value: "18months",
5461
5801
  label: "18 months",
5462
5802
  hint: "Long-term retention"
5803
+ },
5804
+ {
5805
+ value: "2years",
5806
+ label: "2 years",
5807
+ hint: "Extended compliance"
5808
+ },
5809
+ {
5810
+ value: "permanent",
5811
+ label: "Permanent",
5812
+ hint: "Not recommended (expensive)"
5463
5813
  }
5464
5814
  ],
5465
- initialValue: config.eventTracking?.archiveRetention || "90days"
5815
+ initialValue: config.eventTracking?.archiveRetention || "3months"
5466
5816
  });
5467
5817
  if (clack11.isCancel(retention)) {
5468
5818
  clack11.cancel("Upgrade cancelled.");
@@ -5728,14 +6078,14 @@ ${pc11.green("\u2713")} ${pc11.bold("Upgrade complete!")}
5728
6078
  init_esm_shims();
5729
6079
  init_aws();
5730
6080
  import { Resolver } from "dns/promises";
5731
- import { GetEmailIdentityCommand as GetEmailIdentityCommand3, SESv2Client as SESv2Client3 } from "@aws-sdk/client-sesv2";
6081
+ import { GetEmailIdentityCommand as GetEmailIdentityCommand3, SESv2Client as SESv2Client4 } from "@aws-sdk/client-sesv2";
5732
6082
  import * as clack12 from "@clack/prompts";
5733
6083
  import pc12 from "picocolors";
5734
6084
  async function verify(options) {
5735
6085
  clack12.intro(pc12.bold(`Verifying ${options.domain}`));
5736
6086
  const progress = new DeploymentProgress();
5737
6087
  const region = await getAWSRegion();
5738
- const sesClient = new SESv2Client3({ region });
6088
+ const sesClient = new SESv2Client4({ region });
5739
6089
  let identity;
5740
6090
  let dkimTokens = [];
5741
6091
  let mailFromDomain;