@wraps.dev/cli 0.2.0 → 0.3.2
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 +644 -26
- package/dist/cli.js.map +1 -1
- package/dist/console/assets/index-BXE8qB8y.css +1 -0
- package/dist/console/assets/index-BmOMPb6r.js +381 -0
- package/dist/console/index.html +2 -2
- package/dist/lambda/event-processor/.bundled +1 -1
- package/package.json +4 -1
- package/dist/console/assets/index-D7Es86Zn.css +0 -1
- package/dist/console/assets/index-VnWZyGbz.js +0 -366
package/dist/cli.js
CHANGED
|
@@ -18,6 +18,24 @@ var init_esm_shims = __esm({
|
|
|
18
18
|
}
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
+
// src/infrastructure/resources/mail-manager.ts
|
|
22
|
+
var mail_manager_exports = {};
|
|
23
|
+
__export(mail_manager_exports, {
|
|
24
|
+
createMailManagerArchive: () => createMailManagerArchive
|
|
25
|
+
});
|
|
26
|
+
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
|
+
);
|
|
31
|
+
}
|
|
32
|
+
var init_mail_manager = __esm({
|
|
33
|
+
"src/infrastructure/resources/mail-manager.ts"() {
|
|
34
|
+
"use strict";
|
|
35
|
+
init_esm_shims();
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
21
39
|
// src/utils/errors.ts
|
|
22
40
|
import * as clack from "@clack/prompts";
|
|
23
41
|
import pc from "picocolors";
|
|
@@ -224,11 +242,28 @@ function estimateStorageSize(emailsPerMonth, retention, numEventTypes = 8) {
|
|
|
224
242
|
"7days": 0.25,
|
|
225
243
|
"30days": 1,
|
|
226
244
|
"90days": 3,
|
|
245
|
+
"6months": 6,
|
|
246
|
+
"1year": 12,
|
|
247
|
+
"18months": 18,
|
|
248
|
+
indefinite: 120
|
|
249
|
+
// Assume 10 years for cost estimation
|
|
250
|
+
}[retention];
|
|
251
|
+
const totalKB = emailsPerMonth * numEventTypes * (retentionMonths ?? 12) * avgRecordSizeKB;
|
|
252
|
+
return totalKB / 1024 / 1024;
|
|
253
|
+
}
|
|
254
|
+
function estimateArchiveStorageSize(emailsPerMonth, retention) {
|
|
255
|
+
const avgEmailSizeKB = 50;
|
|
256
|
+
const retentionMonths = {
|
|
257
|
+
"7days": 0.25,
|
|
258
|
+
"30days": 1,
|
|
259
|
+
"90days": 3,
|
|
260
|
+
"6months": 6,
|
|
227
261
|
"1year": 12,
|
|
228
|
-
|
|
229
|
-
|
|
262
|
+
"18months": 18,
|
|
263
|
+
indefinite: 120
|
|
264
|
+
// Assume 10 years for cost estimation
|
|
230
265
|
}[retention];
|
|
231
|
-
const totalKB = emailsPerMonth *
|
|
266
|
+
const totalKB = emailsPerMonth * (retentionMonths ?? 12) * avgEmailSizeKB;
|
|
232
267
|
return totalKB / 1024 / 1024;
|
|
233
268
|
}
|
|
234
269
|
function calculateEventTrackingCost(config, emailsPerMonth) {
|
|
@@ -307,19 +342,35 @@ function calculateDedicatedIpCost(config) {
|
|
|
307
342
|
description: "Dedicated IP address (requires 100k+ emails/day for warmup)"
|
|
308
343
|
};
|
|
309
344
|
}
|
|
345
|
+
function calculateEmailArchivingCost(config, emailsPerMonth) {
|
|
346
|
+
if (!config.emailArchiving?.enabled) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
const retention = config.emailArchiving.retention;
|
|
350
|
+
const storageGB = estimateArchiveStorageSize(emailsPerMonth, retention);
|
|
351
|
+
const monthlyDataGB = emailsPerMonth * 50 / 1024 / 1024;
|
|
352
|
+
const ingestionCost = monthlyDataGB * AWS_PRICING.MAIL_MANAGER_INGESTION_PER_GB;
|
|
353
|
+
const storageCost = storageGB * AWS_PRICING.MAIL_MANAGER_STORAGE_PER_GB;
|
|
354
|
+
return {
|
|
355
|
+
monthly: ingestionCost + storageCost,
|
|
356
|
+
description: `Email archiving (${retention}, ~${storageGB.toFixed(2)} GB at steady-state)`
|
|
357
|
+
};
|
|
358
|
+
}
|
|
310
359
|
function calculateCosts(config, emailsPerMonth = 1e4) {
|
|
311
360
|
const tracking = calculateTrackingCost(config);
|
|
312
361
|
const reputationMetrics = calculateReputationMetricsCost(config);
|
|
313
362
|
const eventTracking = calculateEventTrackingCost(config, emailsPerMonth);
|
|
314
363
|
const dynamoDBHistory = calculateDynamoDBCost(config, emailsPerMonth);
|
|
364
|
+
const emailArchiving = calculateEmailArchivingCost(config, emailsPerMonth);
|
|
315
365
|
const dedicatedIp = calculateDedicatedIpCost(config);
|
|
316
366
|
const sesEmailCost = Math.max(0, emailsPerMonth - FREE_TIER.SES_EMAILS) * AWS_PRICING.SES_PER_EMAIL;
|
|
317
|
-
const totalMonthlyCost = sesEmailCost + (tracking?.monthly || 0) + (reputationMetrics?.monthly || 0) + (eventTracking?.monthly || 0) + (dynamoDBHistory?.monthly || 0) + (dedicatedIp?.monthly || 0);
|
|
367
|
+
const totalMonthlyCost = sesEmailCost + (tracking?.monthly || 0) + (reputationMetrics?.monthly || 0) + (eventTracking?.monthly || 0) + (dynamoDBHistory?.monthly || 0) + (emailArchiving?.monthly || 0) + (dedicatedIp?.monthly || 0);
|
|
318
368
|
return {
|
|
319
369
|
tracking,
|
|
320
370
|
reputationMetrics,
|
|
321
371
|
eventTracking,
|
|
322
372
|
dynamoDBHistory,
|
|
373
|
+
emailArchiving,
|
|
323
374
|
dedicatedIp,
|
|
324
375
|
total: {
|
|
325
376
|
monthly: totalMonthlyCost,
|
|
@@ -366,6 +417,11 @@ function getCostSummary(config, emailsPerMonth = 1e4) {
|
|
|
366
417
|
` - ${costs.dynamoDBHistory.description}: ${formatCost(costs.dynamoDBHistory.monthly)}`
|
|
367
418
|
);
|
|
368
419
|
}
|
|
420
|
+
if (costs.emailArchiving) {
|
|
421
|
+
lines.push(
|
|
422
|
+
` - ${costs.emailArchiving.description}: ${formatCost(costs.emailArchiving.monthly)}`
|
|
423
|
+
);
|
|
424
|
+
}
|
|
369
425
|
if (costs.dedicatedIp) {
|
|
370
426
|
lines.push(
|
|
371
427
|
` - ${costs.dedicatedIp.description}: ${formatCost(costs.dedicatedIp.monthly)}`
|
|
@@ -408,8 +464,13 @@ var init_costs = __esm({
|
|
|
408
464
|
// CloudWatch pricing
|
|
409
465
|
CLOUDWATCH_LOGS_PER_GB: 0.5,
|
|
410
466
|
// $0.50 per GB ingested
|
|
411
|
-
CLOUDWATCH_LOGS_STORAGE_PER_GB: 0.03
|
|
467
|
+
CLOUDWATCH_LOGS_STORAGE_PER_GB: 0.03,
|
|
412
468
|
// $0.03 per GB-month
|
|
469
|
+
// SES Mail Manager Archiving
|
|
470
|
+
MAIL_MANAGER_INGESTION_PER_GB: 2,
|
|
471
|
+
// $2.00 per GB ingested
|
|
472
|
+
MAIL_MANAGER_STORAGE_PER_GB: 0.19
|
|
473
|
+
// $0.19 per GB-month
|
|
413
474
|
};
|
|
414
475
|
FREE_TIER = {
|
|
415
476
|
// SES: 3,000 emails/month for first 12 months (new AWS accounts only)
|
|
@@ -487,7 +548,8 @@ function getPresetInfo(preset) {
|
|
|
487
548
|
features: [
|
|
488
549
|
"Open & click tracking",
|
|
489
550
|
"TLS encryption required",
|
|
490
|
-
"Automatic bounce/complaint suppression"
|
|
551
|
+
"Automatic bounce/complaint suppression",
|
|
552
|
+
"Optional: Email archiving (full content storage)"
|
|
491
553
|
]
|
|
492
554
|
},
|
|
493
555
|
production: {
|
|
@@ -500,6 +562,7 @@ function getPresetInfo(preset) {
|
|
|
500
562
|
"Reputation metrics dashboard",
|
|
501
563
|
"Real-time event tracking (EventBridge)",
|
|
502
564
|
"90-day email history storage",
|
|
565
|
+
"Optional: Email archiving with rendered viewer",
|
|
503
566
|
"Complete event visibility"
|
|
504
567
|
]
|
|
505
568
|
},
|
|
@@ -512,6 +575,7 @@ function getPresetInfo(preset) {
|
|
|
512
575
|
"Everything in Production",
|
|
513
576
|
"Dedicated IP address",
|
|
514
577
|
"1-year email history",
|
|
578
|
+
"Optional: 1-year+ email archiving",
|
|
515
579
|
"All event types tracked",
|
|
516
580
|
"Priority support eligibility"
|
|
517
581
|
]
|
|
@@ -594,6 +658,11 @@ var init_presets = __esm({
|
|
|
594
658
|
eventTracking: {
|
|
595
659
|
enabled: false
|
|
596
660
|
},
|
|
661
|
+
// Email archiving disabled by default
|
|
662
|
+
emailArchiving: {
|
|
663
|
+
enabled: false,
|
|
664
|
+
retention: "30days"
|
|
665
|
+
},
|
|
597
666
|
sendingEnabled: true
|
|
598
667
|
};
|
|
599
668
|
PRODUCTION_PRESET = {
|
|
@@ -624,6 +693,12 @@ var init_presets = __esm({
|
|
|
624
693
|
dynamoDBHistory: true,
|
|
625
694
|
archiveRetention: "90days"
|
|
626
695
|
},
|
|
696
|
+
// Email archiving with 90-day retention
|
|
697
|
+
emailArchiving: {
|
|
698
|
+
enabled: false,
|
|
699
|
+
// User can opt-in
|
|
700
|
+
retention: "90days"
|
|
701
|
+
},
|
|
627
702
|
sendingEnabled: true
|
|
628
703
|
};
|
|
629
704
|
ENTERPRISE_PRESET = {
|
|
@@ -656,6 +731,12 @@ var init_presets = __esm({
|
|
|
656
731
|
dynamoDBHistory: true,
|
|
657
732
|
archiveRetention: "1year"
|
|
658
733
|
},
|
|
734
|
+
// Email archiving with 1-year retention
|
|
735
|
+
emailArchiving: {
|
|
736
|
+
enabled: false,
|
|
737
|
+
// User can opt-in
|
|
738
|
+
retention: "1year"
|
|
739
|
+
},
|
|
659
740
|
dedicatedIp: true,
|
|
660
741
|
sendingEnabled: true
|
|
661
742
|
};
|
|
@@ -672,6 +753,7 @@ __export(prompts_exports, {
|
|
|
672
753
|
promptConflictResolution: () => promptConflictResolution,
|
|
673
754
|
promptCustomConfig: () => promptCustomConfig,
|
|
674
755
|
promptDomain: () => promptDomain,
|
|
756
|
+
promptEmailArchiving: () => promptEmailArchiving,
|
|
675
757
|
promptEstimatedVolume: () => promptEstimatedVolume,
|
|
676
758
|
promptFeatureSelection: () => promptFeatureSelection,
|
|
677
759
|
promptIntegrationLevel: () => promptIntegrationLevel,
|
|
@@ -1033,6 +1115,59 @@ async function promptEstimatedVolume() {
|
|
|
1033
1115
|
}
|
|
1034
1116
|
return volume;
|
|
1035
1117
|
}
|
|
1118
|
+
async function promptEmailArchiving() {
|
|
1119
|
+
const enabled = await clack3.confirm({
|
|
1120
|
+
message: "Enable email archiving? (Store full email content with HTML for viewing in dashboard)",
|
|
1121
|
+
initialValue: false
|
|
1122
|
+
});
|
|
1123
|
+
if (clack3.isCancel(enabled)) {
|
|
1124
|
+
clack3.cancel("Operation cancelled.");
|
|
1125
|
+
process.exit(0);
|
|
1126
|
+
}
|
|
1127
|
+
if (!enabled) {
|
|
1128
|
+
return { enabled: false, retention: "90days" };
|
|
1129
|
+
}
|
|
1130
|
+
const retention = await clack3.select({
|
|
1131
|
+
message: "Email archive retention period:",
|
|
1132
|
+
options: [
|
|
1133
|
+
{ value: "7days", label: "7 days", hint: "~$1-2/mo for 10k emails" },
|
|
1134
|
+
{ value: "30days", label: "30 days", hint: "~$2-4/mo for 10k emails" },
|
|
1135
|
+
{
|
|
1136
|
+
value: "90days",
|
|
1137
|
+
label: "90 days (recommended)",
|
|
1138
|
+
hint: "~$5-10/mo for 10k emails"
|
|
1139
|
+
},
|
|
1140
|
+
{
|
|
1141
|
+
value: "6months",
|
|
1142
|
+
label: "6 months",
|
|
1143
|
+
hint: "~$15-25/mo for 10k emails"
|
|
1144
|
+
},
|
|
1145
|
+
{ value: "1year", label: "1 year", hint: "~$25-40/mo for 10k emails" },
|
|
1146
|
+
{
|
|
1147
|
+
value: "18months",
|
|
1148
|
+
label: "18 months",
|
|
1149
|
+
hint: "~$35-60/mo for 10k emails"
|
|
1150
|
+
}
|
|
1151
|
+
],
|
|
1152
|
+
initialValue: "90days"
|
|
1153
|
+
});
|
|
1154
|
+
if (clack3.isCancel(retention)) {
|
|
1155
|
+
clack3.cancel("Operation cancelled.");
|
|
1156
|
+
process.exit(0);
|
|
1157
|
+
}
|
|
1158
|
+
clack3.log.info(
|
|
1159
|
+
pc3.dim(
|
|
1160
|
+
"Archiving stores full RFC 822 emails with HTML, attachments, and headers"
|
|
1161
|
+
)
|
|
1162
|
+
);
|
|
1163
|
+
clack3.log.info(
|
|
1164
|
+
pc3.dim("Cost: $2/GB ingestion + $0.19/GB/month storage (~50KB per email)")
|
|
1165
|
+
);
|
|
1166
|
+
return {
|
|
1167
|
+
enabled: true,
|
|
1168
|
+
retention
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1036
1171
|
async function promptCustomConfig() {
|
|
1037
1172
|
clack3.log.info("Custom configuration builder");
|
|
1038
1173
|
clack3.log.info("Configure each feature individually");
|
|
@@ -1112,6 +1247,53 @@ async function promptCustomConfig() {
|
|
|
1112
1247
|
clack3.cancel("Operation cancelled.");
|
|
1113
1248
|
process.exit(0);
|
|
1114
1249
|
}
|
|
1250
|
+
const emailArchivingEnabled = await clack3.confirm({
|
|
1251
|
+
message: "Enable email archiving? (Store full email content with HTML for viewing)",
|
|
1252
|
+
initialValue: false
|
|
1253
|
+
});
|
|
1254
|
+
if (clack3.isCancel(emailArchivingEnabled)) {
|
|
1255
|
+
clack3.cancel("Operation cancelled.");
|
|
1256
|
+
process.exit(0);
|
|
1257
|
+
}
|
|
1258
|
+
let emailArchiveRetention = "90days";
|
|
1259
|
+
if (emailArchivingEnabled) {
|
|
1260
|
+
emailArchiveRetention = await clack3.select({
|
|
1261
|
+
message: "Email archive retention period:",
|
|
1262
|
+
options: [
|
|
1263
|
+
{ value: "7days", label: "7 days", hint: "~$1-2/mo for 10k emails" },
|
|
1264
|
+
{ value: "30days", label: "30 days", hint: "~$2-4/mo for 10k emails" },
|
|
1265
|
+
{
|
|
1266
|
+
value: "90days",
|
|
1267
|
+
label: "90 days (recommended)",
|
|
1268
|
+
hint: "~$5-10/mo for 10k emails"
|
|
1269
|
+
},
|
|
1270
|
+
{
|
|
1271
|
+
value: "6months",
|
|
1272
|
+
label: "6 months",
|
|
1273
|
+
hint: "~$15-25/mo for 10k emails"
|
|
1274
|
+
},
|
|
1275
|
+
{ value: "1year", label: "1 year", hint: "~$25-40/mo for 10k emails" },
|
|
1276
|
+
{
|
|
1277
|
+
value: "18months",
|
|
1278
|
+
label: "18 months",
|
|
1279
|
+
hint: "~$35-60/mo for 10k emails"
|
|
1280
|
+
}
|
|
1281
|
+
],
|
|
1282
|
+
initialValue: "90days"
|
|
1283
|
+
});
|
|
1284
|
+
if (clack3.isCancel(emailArchiveRetention)) {
|
|
1285
|
+
clack3.cancel("Operation cancelled.");
|
|
1286
|
+
process.exit(0);
|
|
1287
|
+
}
|
|
1288
|
+
clack3.log.info(
|
|
1289
|
+
pc3.dim(
|
|
1290
|
+
"Note: Archiving stores full RFC 822 emails with HTML, attachments, and headers"
|
|
1291
|
+
)
|
|
1292
|
+
);
|
|
1293
|
+
clack3.log.info(
|
|
1294
|
+
pc3.dim("Cost: $2/GB ingestion + $0.19/GB/month storage (~50KB per email)")
|
|
1295
|
+
);
|
|
1296
|
+
}
|
|
1115
1297
|
return {
|
|
1116
1298
|
tracking: trackingEnabled ? {
|
|
1117
1299
|
enabled: true,
|
|
@@ -1140,6 +1322,10 @@ async function promptCustomConfig() {
|
|
|
1140
1322
|
dynamoDBHistory: Boolean(dynamoDBHistory),
|
|
1141
1323
|
archiveRetention: typeof archiveRetention === "string" ? archiveRetention : "90days"
|
|
1142
1324
|
} : { enabled: false },
|
|
1325
|
+
emailArchiving: emailArchivingEnabled ? {
|
|
1326
|
+
enabled: true,
|
|
1327
|
+
retention: typeof emailArchiveRetention === "string" ? emailArchiveRetention : "90days"
|
|
1328
|
+
} : { enabled: false, retention: "90days" },
|
|
1143
1329
|
dedicatedIp,
|
|
1144
1330
|
sendingEnabled: true
|
|
1145
1331
|
};
|
|
@@ -1184,6 +1370,114 @@ var init_assume_role = __esm({
|
|
|
1184
1370
|
}
|
|
1185
1371
|
});
|
|
1186
1372
|
|
|
1373
|
+
// src/utils/archive.ts
|
|
1374
|
+
import {
|
|
1375
|
+
GetArchiveMessageCommand,
|
|
1376
|
+
MailManagerClient
|
|
1377
|
+
} from "@aws-sdk/client-mailmanager";
|
|
1378
|
+
import { simpleParser } from "mailparser";
|
|
1379
|
+
async function getArchivedEmail(_archiveId, messageId, region) {
|
|
1380
|
+
const client = new MailManagerClient({ region });
|
|
1381
|
+
const command2 = new GetArchiveMessageCommand({
|
|
1382
|
+
ArchivedMessageId: messageId
|
|
1383
|
+
});
|
|
1384
|
+
const response = await client.send(command2);
|
|
1385
|
+
if (!response.MessageDownloadLink) {
|
|
1386
|
+
throw new Error("No download link available for archived message");
|
|
1387
|
+
}
|
|
1388
|
+
const emailResponse = await fetch(response.MessageDownloadLink);
|
|
1389
|
+
if (!emailResponse.ok) {
|
|
1390
|
+
throw new Error(`Failed to download email: ${emailResponse.statusText}`);
|
|
1391
|
+
}
|
|
1392
|
+
const emailRaw = await emailResponse.text();
|
|
1393
|
+
const parsed = await simpleParser(emailRaw);
|
|
1394
|
+
const attachments = parsed.attachments?.map((att) => ({
|
|
1395
|
+
filename: att.filename,
|
|
1396
|
+
contentType: att.contentType,
|
|
1397
|
+
size: att.size
|
|
1398
|
+
})) || [];
|
|
1399
|
+
const headers = {};
|
|
1400
|
+
if (parsed.headers) {
|
|
1401
|
+
for (const [key, value] of parsed.headers) {
|
|
1402
|
+
if (value instanceof Date) {
|
|
1403
|
+
headers[key] = value.toISOString();
|
|
1404
|
+
} else if (typeof value === "string") {
|
|
1405
|
+
headers[key] = value;
|
|
1406
|
+
} else if (Array.isArray(value) && value.every((v) => typeof v === "string")) {
|
|
1407
|
+
headers[key] = value;
|
|
1408
|
+
} else {
|
|
1409
|
+
headers[key] = JSON.stringify(value);
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
const getAddressText = (addr) => {
|
|
1414
|
+
if (!addr) return "";
|
|
1415
|
+
if (Array.isArray(addr)) {
|
|
1416
|
+
return addr.map((a) => a.text).join(", ");
|
|
1417
|
+
}
|
|
1418
|
+
return addr.text || "";
|
|
1419
|
+
};
|
|
1420
|
+
return {
|
|
1421
|
+
messageId,
|
|
1422
|
+
// Use the input messageId since response may not have MessageMetadata
|
|
1423
|
+
from: getAddressText(parsed.from),
|
|
1424
|
+
to: getAddressText(parsed.to),
|
|
1425
|
+
subject: parsed.subject || "",
|
|
1426
|
+
html: parsed.html || void 0,
|
|
1427
|
+
text: parsed.text || void 0,
|
|
1428
|
+
attachments,
|
|
1429
|
+
headers,
|
|
1430
|
+
timestamp: parsed.date || /* @__PURE__ */ new Date(),
|
|
1431
|
+
// Note: MessageMetadata is not available in GetArchiveMessageCommandOutput
|
|
1432
|
+
// These fields would need to be retrieved separately if needed
|
|
1433
|
+
metadata: {}
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1436
|
+
var init_archive = __esm({
|
|
1437
|
+
"src/utils/archive.ts"() {
|
|
1438
|
+
"use strict";
|
|
1439
|
+
init_esm_shims();
|
|
1440
|
+
}
|
|
1441
|
+
});
|
|
1442
|
+
|
|
1443
|
+
// src/console/services/email-archive.ts
|
|
1444
|
+
var email_archive_exports = {};
|
|
1445
|
+
__export(email_archive_exports, {
|
|
1446
|
+
fetchArchivedEmail: () => fetchArchivedEmail
|
|
1447
|
+
});
|
|
1448
|
+
async function fetchArchivedEmail(messageId, options) {
|
|
1449
|
+
const { region, archiveArn } = options;
|
|
1450
|
+
try {
|
|
1451
|
+
console.log("Fetching archived email:", {
|
|
1452
|
+
messageId,
|
|
1453
|
+
archiveArn,
|
|
1454
|
+
region
|
|
1455
|
+
});
|
|
1456
|
+
const email = await getArchivedEmail(archiveArn, messageId, region);
|
|
1457
|
+
console.log("Archived email fetched successfully:", {
|
|
1458
|
+
messageId: email.messageId,
|
|
1459
|
+
hasHtml: !!email.html,
|
|
1460
|
+
hasText: !!email.text,
|
|
1461
|
+
attachmentCount: email.attachments.length
|
|
1462
|
+
});
|
|
1463
|
+
return email;
|
|
1464
|
+
} catch (error) {
|
|
1465
|
+
if (error instanceof Error && (error.message.includes("not found") || error.message.includes("ResourceNotFoundException"))) {
|
|
1466
|
+
console.log("Archived email not found:", messageId);
|
|
1467
|
+
return null;
|
|
1468
|
+
}
|
|
1469
|
+
console.error("Error fetching archived email:", error);
|
|
1470
|
+
throw error;
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
var init_email_archive = __esm({
|
|
1474
|
+
"src/console/services/email-archive.ts"() {
|
|
1475
|
+
"use strict";
|
|
1476
|
+
init_esm_shims();
|
|
1477
|
+
init_archive();
|
|
1478
|
+
}
|
|
1479
|
+
});
|
|
1480
|
+
|
|
1187
1481
|
// src/console/services/dynamodb-metrics.ts
|
|
1188
1482
|
var dynamodb_metrics_exports = {};
|
|
1189
1483
|
__export(dynamodb_metrics_exports, {
|
|
@@ -1582,6 +1876,19 @@ async function createIAMRole(config) {
|
|
|
1582
1876
|
Resource: "arn:aws:sqs:*:*:wraps-email-*"
|
|
1583
1877
|
});
|
|
1584
1878
|
}
|
|
1879
|
+
if (config.emailConfig.emailArchiving?.enabled) {
|
|
1880
|
+
statements.push({
|
|
1881
|
+
Effect: "Allow",
|
|
1882
|
+
Action: [
|
|
1883
|
+
"ses:GetArchive",
|
|
1884
|
+
"ses:GetArchiveMessage",
|
|
1885
|
+
"ses:GetArchiveMessageContent",
|
|
1886
|
+
"ses:SearchArchive",
|
|
1887
|
+
"ses:StartArchiveExport"
|
|
1888
|
+
],
|
|
1889
|
+
Resource: "arn:aws:ses:*:*:mailmanager-archive/*"
|
|
1890
|
+
});
|
|
1891
|
+
}
|
|
1585
1892
|
new aws3.iam.RolePolicy("wraps-email-policy", {
|
|
1586
1893
|
role: role.name,
|
|
1587
1894
|
policy: JSON.stringify({
|
|
@@ -1764,8 +2071,9 @@ async function createSESResources(config) {
|
|
|
1764
2071
|
if (config.trackingConfig?.customRedirectDomain) {
|
|
1765
2072
|
configSetOptions.trackingOptions = {
|
|
1766
2073
|
customRedirectDomain: config.trackingConfig.customRedirectDomain,
|
|
1767
|
-
|
|
1768
|
-
//
|
|
2074
|
+
// Use OPTIONAL because custom domains don't have SSL certificates by default
|
|
2075
|
+
// AWS's tracking domain (r.{region}.awstrack.me) doesn't have certs for custom domains
|
|
2076
|
+
httpsPolicy: "OPTIONAL"
|
|
1769
2077
|
};
|
|
1770
2078
|
}
|
|
1771
2079
|
const configSet = new aws5.sesv2.ConfigurationSet(
|
|
@@ -1957,6 +2265,15 @@ async function deployEmailStack(config) {
|
|
|
1957
2265
|
accountId
|
|
1958
2266
|
});
|
|
1959
2267
|
}
|
|
2268
|
+
let archiveResources;
|
|
2269
|
+
if (emailConfig.emailArchiving?.enabled && sesResources) {
|
|
2270
|
+
const { createMailManagerArchive: createMailManagerArchive2 } = await Promise.resolve().then(() => (init_mail_manager(), mail_manager_exports));
|
|
2271
|
+
archiveResources = await createMailManagerArchive2({
|
|
2272
|
+
name: "email",
|
|
2273
|
+
retention: emailConfig.emailArchiving.retention,
|
|
2274
|
+
configSetName: sesResources.configSet.configurationSetName
|
|
2275
|
+
});
|
|
2276
|
+
}
|
|
1960
2277
|
return {
|
|
1961
2278
|
roleArn: role.arn,
|
|
1962
2279
|
configSetName: sesResources?.configSet.configurationSetName,
|
|
@@ -1970,7 +2287,10 @@ async function deployEmailStack(config) {
|
|
|
1970
2287
|
queueUrl: sqsResources?.queue.url,
|
|
1971
2288
|
dlqUrl: sqsResources?.dlq.url,
|
|
1972
2289
|
customTrackingDomain: sesResources?.customTrackingDomain,
|
|
1973
|
-
mailFromDomain: sesResources?.mailFromDomain
|
|
2290
|
+
mailFromDomain: sesResources?.mailFromDomain,
|
|
2291
|
+
archiveArn: archiveResources?.archive.arn,
|
|
2292
|
+
archivingEnabled: emailConfig.emailArchiving?.enabled,
|
|
2293
|
+
archiveRetention: emailConfig.emailArchiving?.enabled ? emailConfig.emailArchiving.retention : void 0
|
|
1974
2294
|
};
|
|
1975
2295
|
}
|
|
1976
2296
|
|
|
@@ -2282,6 +2602,23 @@ ${domainStrings.join("\n")}`);
|
|
|
2282
2602
|
` ${pc2.dim("\u25CB")} Bounce/Complaint Handling ${pc2.dim("(run 'wraps upgrade' to enable)")}`
|
|
2283
2603
|
);
|
|
2284
2604
|
}
|
|
2605
|
+
if (status2.resources.archivingEnabled) {
|
|
2606
|
+
const retentionLabel = {
|
|
2607
|
+
"7days": "7 days",
|
|
2608
|
+
"30days": "30 days",
|
|
2609
|
+
"90days": "90 days",
|
|
2610
|
+
"6months": "6 months",
|
|
2611
|
+
"1year": "1 year",
|
|
2612
|
+
"18months": "18 months"
|
|
2613
|
+
}[status2.resources.archiveRetention || "90days"] || "90 days";
|
|
2614
|
+
featureLines.push(
|
|
2615
|
+
` ${pc2.green("\u2713")} Email Archiving ${pc2.dim(`(${retentionLabel} retention)`)}`
|
|
2616
|
+
);
|
|
2617
|
+
} else {
|
|
2618
|
+
featureLines.push(
|
|
2619
|
+
` ${pc2.dim("\u25CB")} Email Archiving ${pc2.dim("(run 'wraps upgrade' to enable)")}`
|
|
2620
|
+
);
|
|
2621
|
+
}
|
|
2285
2622
|
featureLines.push(
|
|
2286
2623
|
` ${pc2.green("\u2713")} Console Dashboard ${pc2.dim("(run 'wraps console')")}`
|
|
2287
2624
|
);
|
|
@@ -2314,6 +2651,11 @@ ${domainStrings.join("\n")}`);
|
|
|
2314
2651
|
` ${pc2.green("\u2713")} SNS Topics: ${pc2.cyan(`${status2.resources.snsTopics} configured`)}`
|
|
2315
2652
|
);
|
|
2316
2653
|
}
|
|
2654
|
+
if (status2.resources.archiveArn) {
|
|
2655
|
+
resourceLines.push(
|
|
2656
|
+
` ${pc2.green("\u2713")} Mail Manager Archive: ${pc2.cyan(status2.resources.archiveArn)}`
|
|
2657
|
+
);
|
|
2658
|
+
}
|
|
2317
2659
|
clack2.note(resourceLines.join("\n"), "Resources");
|
|
2318
2660
|
const domainsNeedingDNS = status2.domains.filter(
|
|
2319
2661
|
(d) => d.status === "pending" && d.dkimTokens || d.mailFromDomain && d.mailFromStatus !== "SUCCESS"
|
|
@@ -2336,7 +2678,9 @@ ${domainStrings.join("\n")}`);
|
|
|
2336
2678
|
);
|
|
2337
2679
|
}
|
|
2338
2680
|
if (domain.mailFromDomain && domain.mailFromStatus !== "SUCCESS") {
|
|
2339
|
-
if (dnsLines.length > 0)
|
|
2681
|
+
if (dnsLines.length > 0) {
|
|
2682
|
+
dnsLines.push("");
|
|
2683
|
+
}
|
|
2340
2684
|
dnsLines.push(
|
|
2341
2685
|
pc2.bold("MAIL FROM Domain Records (for DMARC alignment):"),
|
|
2342
2686
|
` ${pc2.cyan(domain.mailFromDomain)} ${pc2.dim("MX")} "10 feedback-smtp.${status2.region}.amazonses.com"`,
|
|
@@ -3271,6 +3615,46 @@ function createEmailsRouter(config) {
|
|
|
3271
3615
|
res.status(500).json({ error: errorMessage });
|
|
3272
3616
|
}
|
|
3273
3617
|
});
|
|
3618
|
+
router.get("/:id/archive", async (req, res) => {
|
|
3619
|
+
try {
|
|
3620
|
+
const { id } = req.params;
|
|
3621
|
+
console.log("Archived email request received for message ID:", id);
|
|
3622
|
+
if (!config.archivingEnabled) {
|
|
3623
|
+
console.log("Email archiving not enabled");
|
|
3624
|
+
return res.status(400).json({
|
|
3625
|
+
error: "Email archiving not enabled for this deployment."
|
|
3626
|
+
});
|
|
3627
|
+
}
|
|
3628
|
+
if (!config.archiveArn) {
|
|
3629
|
+
console.log("No archive ARN configured");
|
|
3630
|
+
return res.status(400).json({
|
|
3631
|
+
error: "Archive ARN not configured."
|
|
3632
|
+
});
|
|
3633
|
+
}
|
|
3634
|
+
console.log("Fetching archived email from Mail Manager...");
|
|
3635
|
+
const { fetchArchivedEmail: fetchArchivedEmail2 } = await Promise.resolve().then(() => (init_email_archive(), email_archive_exports));
|
|
3636
|
+
const archivedEmail = await fetchArchivedEmail2(id, {
|
|
3637
|
+
region: config.region,
|
|
3638
|
+
archiveArn: config.archiveArn
|
|
3639
|
+
});
|
|
3640
|
+
if (!archivedEmail) {
|
|
3641
|
+
console.log("Archived email not found for message ID:", id);
|
|
3642
|
+
return res.status(404).json({
|
|
3643
|
+
error: "Archived email not found. It may have been sent before archiving was enabled."
|
|
3644
|
+
});
|
|
3645
|
+
}
|
|
3646
|
+
console.log("Archived email found:", archivedEmail.messageId);
|
|
3647
|
+
res.json(archivedEmail);
|
|
3648
|
+
} catch (error) {
|
|
3649
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
3650
|
+
console.error("Error fetching archived email:", error);
|
|
3651
|
+
console.error(
|
|
3652
|
+
"Stack trace:",
|
|
3653
|
+
error instanceof Error ? error.stack : "N/A"
|
|
3654
|
+
);
|
|
3655
|
+
res.status(500).json({ error: errorMessage });
|
|
3656
|
+
}
|
|
3657
|
+
});
|
|
3274
3658
|
return router;
|
|
3275
3659
|
}
|
|
3276
3660
|
|
|
@@ -3557,7 +3941,9 @@ async function fetchEmailIdentity(roleArn, region, identityName) {
|
|
|
3557
3941
|
verifiedForSendingStatus: response.VerifiedForSendingStatus ?? false,
|
|
3558
3942
|
tags: response.Tags?.reduce(
|
|
3559
3943
|
(acc, tag) => {
|
|
3560
|
-
if (tag.Key)
|
|
3944
|
+
if (tag.Key) {
|
|
3945
|
+
acc[tag.Key] = tag.Value || "";
|
|
3946
|
+
}
|
|
3561
3947
|
return acc;
|
|
3562
3948
|
},
|
|
3563
3949
|
{}
|
|
@@ -3590,6 +3976,20 @@ async function fetchEmailSettings(roleArn, region, configSetName, domain) {
|
|
|
3590
3976
|
// src/console/routes/settings.ts
|
|
3591
3977
|
function createSettingsRouter(config) {
|
|
3592
3978
|
const router = createRouter4();
|
|
3979
|
+
router.get("/deployment", async (_req, res) => {
|
|
3980
|
+
try {
|
|
3981
|
+
res.json({
|
|
3982
|
+
archivingEnabled: config.archivingEnabled ?? false,
|
|
3983
|
+
archiveArn: config.archiveArn,
|
|
3984
|
+
tableName: config.tableName,
|
|
3985
|
+
region: config.region
|
|
3986
|
+
});
|
|
3987
|
+
} catch (error) {
|
|
3988
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
3989
|
+
console.error("Error fetching deployment config:", error);
|
|
3990
|
+
res.status(500).json({ error: errorMessage });
|
|
3991
|
+
}
|
|
3992
|
+
});
|
|
3593
3993
|
router.get("/", async (_req, res) => {
|
|
3594
3994
|
try {
|
|
3595
3995
|
const metadata = await loadConnectionMetadata(
|
|
@@ -3965,6 +4365,8 @@ async function runConsole(options) {
|
|
|
3965
4365
|
process.exit(1);
|
|
3966
4366
|
}
|
|
3967
4367
|
const tableName = stackOutputs.tableName?.value;
|
|
4368
|
+
const archiveArn = stackOutputs.archiveArn?.value;
|
|
4369
|
+
const archivingEnabled = stackOutputs.archivingEnabled?.value ?? false;
|
|
3968
4370
|
const port = options.port || await getPort({ port: [5555, 5556, 5557, 5558, 5559] });
|
|
3969
4371
|
progress.stop();
|
|
3970
4372
|
clack5.log.success("Starting console server...");
|
|
@@ -3978,7 +4380,9 @@ async function runConsole(options) {
|
|
|
3978
4380
|
region,
|
|
3979
4381
|
tableName,
|
|
3980
4382
|
accountId: identity.accountId,
|
|
3981
|
-
noOpen: options.noOpen ?? false
|
|
4383
|
+
noOpen: options.noOpen ?? false,
|
|
4384
|
+
archiveArn,
|
|
4385
|
+
archivingEnabled
|
|
3982
4386
|
});
|
|
3983
4387
|
console.log(`\\n${pc5.bold("Console:")} ${pc5.cyan(url)}`);
|
|
3984
4388
|
console.log(`${pc5.dim("Press Ctrl+C to stop")}\\n`);
|
|
@@ -4118,17 +4522,22 @@ async function init(options) {
|
|
|
4118
4522
|
emailConfig = await promptCustomConfig();
|
|
4119
4523
|
} else {
|
|
4120
4524
|
emailConfig = getPreset(preset);
|
|
4525
|
+
const { promptEmailArchiving: promptEmailArchiving2 } = await Promise.resolve().then(() => (init_prompts(), prompts_exports));
|
|
4526
|
+
const archivingConfig = await promptEmailArchiving2();
|
|
4527
|
+
emailConfig.emailArchiving = archivingConfig;
|
|
4121
4528
|
}
|
|
4122
4529
|
if (domain) {
|
|
4123
4530
|
emailConfig.domain = domain;
|
|
4124
4531
|
}
|
|
4125
4532
|
const estimatedVolume = await promptEstimatedVolume();
|
|
4126
|
-
progress.info(
|
|
4533
|
+
progress.info(`
|
|
4534
|
+
${pc7.bold("Cost Estimate:")}`);
|
|
4127
4535
|
const costSummary = getCostSummary(emailConfig, estimatedVolume);
|
|
4128
4536
|
clack7.log.info(costSummary);
|
|
4129
4537
|
const warnings = validateConfig(emailConfig);
|
|
4130
4538
|
if (warnings.length > 0) {
|
|
4131
|
-
progress.info(
|
|
4539
|
+
progress.info(`
|
|
4540
|
+
${pc7.yellow(pc7.bold("Configuration Warnings:"))}`);
|
|
4132
4541
|
for (const warning of warnings) {
|
|
4133
4542
|
clack7.log.warn(warning);
|
|
4134
4543
|
}
|
|
@@ -4176,7 +4585,11 @@ async function init(options) {
|
|
|
4176
4585
|
lambdaFunctions: result.lambdaFunctions,
|
|
4177
4586
|
domain: result.domain,
|
|
4178
4587
|
dkimTokens: result.dkimTokens,
|
|
4179
|
-
customTrackingDomain: result.customTrackingDomain
|
|
4588
|
+
customTrackingDomain: result.customTrackingDomain,
|
|
4589
|
+
mailFromDomain: result.mailFromDomain,
|
|
4590
|
+
archiveArn: result.archiveArn,
|
|
4591
|
+
archivingEnabled: result.archivingEnabled,
|
|
4592
|
+
archiveRetention: result.archiveRetention
|
|
4180
4593
|
};
|
|
4181
4594
|
}
|
|
4182
4595
|
},
|
|
@@ -4205,7 +4618,11 @@ async function init(options) {
|
|
|
4205
4618
|
lambdaFunctions: pulumiOutputs.lambdaFunctions?.value,
|
|
4206
4619
|
domain: pulumiOutputs.domain?.value,
|
|
4207
4620
|
dkimTokens: pulumiOutputs.dkimTokens?.value,
|
|
4208
|
-
customTrackingDomain: pulumiOutputs.customTrackingDomain?.value
|
|
4621
|
+
customTrackingDomain: pulumiOutputs.customTrackingDomain?.value,
|
|
4622
|
+
mailFromDomain: pulumiOutputs.mailFromDomain?.value,
|
|
4623
|
+
archiveArn: pulumiOutputs.archiveArn?.value,
|
|
4624
|
+
archivingEnabled: pulumiOutputs.archivingEnabled?.value,
|
|
4625
|
+
archiveRetention: pulumiOutputs.archiveRetention?.value
|
|
4209
4626
|
};
|
|
4210
4627
|
}
|
|
4211
4628
|
);
|
|
@@ -4439,7 +4856,10 @@ Run ${pc9.cyan("wraps init")} to deploy infrastructure.
|
|
|
4439
4856
|
configSetName: stackOutputs.configSetName?.value,
|
|
4440
4857
|
tableName: stackOutputs.tableName?.value,
|
|
4441
4858
|
lambdaFunctions: stackOutputs.lambdaFunctions?.value?.length || 0,
|
|
4442
|
-
snsTopics: integrationLevel === "enhanced" ? 1 : 0
|
|
4859
|
+
snsTopics: integrationLevel === "enhanced" ? 1 : 0,
|
|
4860
|
+
archiveArn: stackOutputs.archiveArn?.value,
|
|
4861
|
+
archivingEnabled: stackOutputs.archivingEnabled?.value,
|
|
4862
|
+
archiveRetention: stackOutputs.archiveRetention?.value
|
|
4443
4863
|
}
|
|
4444
4864
|
});
|
|
4445
4865
|
}
|
|
@@ -4706,6 +5126,18 @@ ${pc11.bold("Current Configuration:")}
|
|
|
4706
5126
|
if (config.dedicatedIp) {
|
|
4707
5127
|
console.log(` ${pc11.green("\u2713")} Dedicated IP Address`);
|
|
4708
5128
|
}
|
|
5129
|
+
if (config.emailArchiving?.enabled) {
|
|
5130
|
+
const retentionLabel = {
|
|
5131
|
+
"7days": "7 days",
|
|
5132
|
+
"30days": "30 days",
|
|
5133
|
+
"90days": "90 days",
|
|
5134
|
+
"6months": "6 months",
|
|
5135
|
+
"1year": "1 year",
|
|
5136
|
+
"18months": "18 months",
|
|
5137
|
+
indefinite: "indefinite"
|
|
5138
|
+
}[config.emailArchiving.retention] || "90 days";
|
|
5139
|
+
console.log(` ${pc11.green("\u2713")} Email Archiving (${retentionLabel})`);
|
|
5140
|
+
}
|
|
4709
5141
|
const currentCostData = calculateCosts(config, 5e4);
|
|
4710
5142
|
console.log(
|
|
4711
5143
|
`
|
|
@@ -4720,6 +5152,11 @@ ${pc11.bold("Current Configuration:")}
|
|
|
4720
5152
|
label: "Upgrade to a different preset",
|
|
4721
5153
|
hint: "Starter \u2192 Production \u2192 Enterprise"
|
|
4722
5154
|
},
|
|
5155
|
+
{
|
|
5156
|
+
value: "archiving",
|
|
5157
|
+
label: config.emailArchiving?.enabled ? "Change email archiving settings" : "Enable email archiving",
|
|
5158
|
+
hint: config.emailArchiving?.enabled ? "Update retention or disable" : "Store full email content with HTML"
|
|
5159
|
+
},
|
|
4723
5160
|
{
|
|
4724
5161
|
value: "tracking-domain",
|
|
4725
5162
|
label: "Add/change custom tracking domain",
|
|
@@ -4728,7 +5165,7 @@ ${pc11.bold("Current Configuration:")}
|
|
|
4728
5165
|
{
|
|
4729
5166
|
value: "retention",
|
|
4730
5167
|
label: "Change email history retention",
|
|
4731
|
-
hint: "7 days, 30 days, 90 days, 1 year,
|
|
5168
|
+
hint: "7 days, 30 days, 90 days, 6 months, 1 year, 18 months"
|
|
4732
5169
|
},
|
|
4733
5170
|
{
|
|
4734
5171
|
value: "events",
|
|
@@ -4786,6 +5223,166 @@ ${pc11.bold("Current Configuration:")}
|
|
|
4786
5223
|
newPreset = selectedPreset;
|
|
4787
5224
|
break;
|
|
4788
5225
|
}
|
|
5226
|
+
case "archiving": {
|
|
5227
|
+
if (config.emailArchiving?.enabled) {
|
|
5228
|
+
const archivingAction = await clack11.select({
|
|
5229
|
+
message: "What would you like to do with email archiving?",
|
|
5230
|
+
options: [
|
|
5231
|
+
{
|
|
5232
|
+
value: "change-retention",
|
|
5233
|
+
label: "Change retention period",
|
|
5234
|
+
hint: `Current: ${config.emailArchiving.retention}`
|
|
5235
|
+
},
|
|
5236
|
+
{
|
|
5237
|
+
value: "disable",
|
|
5238
|
+
label: "Disable email archiving",
|
|
5239
|
+
hint: "Stop storing full email content"
|
|
5240
|
+
}
|
|
5241
|
+
]
|
|
5242
|
+
});
|
|
5243
|
+
if (clack11.isCancel(archivingAction)) {
|
|
5244
|
+
clack11.cancel("Upgrade cancelled.");
|
|
5245
|
+
process.exit(0);
|
|
5246
|
+
}
|
|
5247
|
+
if (archivingAction === "disable") {
|
|
5248
|
+
const confirmDisable = await clack11.confirm({
|
|
5249
|
+
message: "Are you sure? Existing archived emails will remain, but new emails won't be archived.",
|
|
5250
|
+
initialValue: false
|
|
5251
|
+
});
|
|
5252
|
+
if (clack11.isCancel(confirmDisable) || !confirmDisable) {
|
|
5253
|
+
clack11.cancel("Archiving not disabled.");
|
|
5254
|
+
process.exit(0);
|
|
5255
|
+
}
|
|
5256
|
+
updatedConfig = {
|
|
5257
|
+
...config,
|
|
5258
|
+
emailArchiving: {
|
|
5259
|
+
enabled: false,
|
|
5260
|
+
retention: config.emailArchiving.retention
|
|
5261
|
+
}
|
|
5262
|
+
};
|
|
5263
|
+
} else {
|
|
5264
|
+
const retention = await clack11.select({
|
|
5265
|
+
message: "Email archive retention period:",
|
|
5266
|
+
options: [
|
|
5267
|
+
{
|
|
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)",
|
|
5280
|
+
hint: "~$5-10/mo for 10k emails"
|
|
5281
|
+
},
|
|
5282
|
+
{
|
|
5283
|
+
value: "6months",
|
|
5284
|
+
label: "6 months",
|
|
5285
|
+
hint: "~$15-25/mo for 10k emails"
|
|
5286
|
+
},
|
|
5287
|
+
{
|
|
5288
|
+
value: "1year",
|
|
5289
|
+
label: "1 year",
|
|
5290
|
+
hint: "~$25-40/mo for 10k emails"
|
|
5291
|
+
},
|
|
5292
|
+
{
|
|
5293
|
+
value: "18months",
|
|
5294
|
+
label: "18 months",
|
|
5295
|
+
hint: "~$35-60/mo for 10k emails"
|
|
5296
|
+
}
|
|
5297
|
+
],
|
|
5298
|
+
initialValue: config.emailArchiving.retention
|
|
5299
|
+
});
|
|
5300
|
+
if (clack11.isCancel(retention)) {
|
|
5301
|
+
clack11.cancel("Upgrade cancelled.");
|
|
5302
|
+
process.exit(0);
|
|
5303
|
+
}
|
|
5304
|
+
updatedConfig = {
|
|
5305
|
+
...config,
|
|
5306
|
+
emailArchiving: {
|
|
5307
|
+
enabled: true,
|
|
5308
|
+
retention
|
|
5309
|
+
}
|
|
5310
|
+
};
|
|
5311
|
+
}
|
|
5312
|
+
} else {
|
|
5313
|
+
const enableArchiving = await clack11.confirm({
|
|
5314
|
+
message: "Enable email archiving? (Store full email content with HTML for viewing)",
|
|
5315
|
+
initialValue: true
|
|
5316
|
+
});
|
|
5317
|
+
if (clack11.isCancel(enableArchiving)) {
|
|
5318
|
+
clack11.cancel("Upgrade cancelled.");
|
|
5319
|
+
process.exit(0);
|
|
5320
|
+
}
|
|
5321
|
+
if (!enableArchiving) {
|
|
5322
|
+
clack11.log.info("Email archiving not enabled.");
|
|
5323
|
+
process.exit(0);
|
|
5324
|
+
}
|
|
5325
|
+
const retention = await clack11.select({
|
|
5326
|
+
message: "Email archive retention period:",
|
|
5327
|
+
options: [
|
|
5328
|
+
{
|
|
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)",
|
|
5341
|
+
hint: "~$5-10/mo for 10k emails"
|
|
5342
|
+
},
|
|
5343
|
+
{
|
|
5344
|
+
value: "6months",
|
|
5345
|
+
label: "6 months",
|
|
5346
|
+
hint: "~$15-25/mo for 10k emails"
|
|
5347
|
+
},
|
|
5348
|
+
{
|
|
5349
|
+
value: "1year",
|
|
5350
|
+
label: "1 year",
|
|
5351
|
+
hint: "~$25-40/mo for 10k emails"
|
|
5352
|
+
},
|
|
5353
|
+
{
|
|
5354
|
+
value: "18months",
|
|
5355
|
+
label: "18 months",
|
|
5356
|
+
hint: "~$35-60/mo for 10k emails"
|
|
5357
|
+
}
|
|
5358
|
+
],
|
|
5359
|
+
initialValue: "90days"
|
|
5360
|
+
});
|
|
5361
|
+
if (clack11.isCancel(retention)) {
|
|
5362
|
+
clack11.cancel("Upgrade cancelled.");
|
|
5363
|
+
process.exit(0);
|
|
5364
|
+
}
|
|
5365
|
+
clack11.log.info(
|
|
5366
|
+
pc11.dim(
|
|
5367
|
+
"Archiving stores full RFC 822 emails with HTML, attachments, and headers"
|
|
5368
|
+
)
|
|
5369
|
+
);
|
|
5370
|
+
clack11.log.info(
|
|
5371
|
+
pc11.dim(
|
|
5372
|
+
"Cost: $2/GB ingestion + $0.19/GB/month storage (~50KB per email)"
|
|
5373
|
+
)
|
|
5374
|
+
);
|
|
5375
|
+
updatedConfig = {
|
|
5376
|
+
...config,
|
|
5377
|
+
emailArchiving: {
|
|
5378
|
+
enabled: true,
|
|
5379
|
+
retention
|
|
5380
|
+
}
|
|
5381
|
+
};
|
|
5382
|
+
}
|
|
5383
|
+
newPreset = void 0;
|
|
5384
|
+
break;
|
|
5385
|
+
}
|
|
4789
5386
|
case "tracking-domain": {
|
|
4790
5387
|
if (!config.domain) {
|
|
4791
5388
|
clack11.log.error(
|
|
@@ -4844,7 +5441,7 @@ ${pc11.bold("Current Configuration:")}
|
|
|
4844
5441
|
}
|
|
4845
5442
|
case "retention": {
|
|
4846
5443
|
const retention = await clack11.select({
|
|
4847
|
-
message: "Email history retention period:",
|
|
5444
|
+
message: "Email history retention period (event data in DynamoDB):",
|
|
4848
5445
|
options: [
|
|
4849
5446
|
{ value: "7days", label: "7 days", hint: "Minimal storage cost" },
|
|
4850
5447
|
{ value: "30days", label: "30 days", hint: "Development/testing" },
|
|
@@ -4853,11 +5450,16 @@ ${pc11.bold("Current Configuration:")}
|
|
|
4853
5450
|
label: "90 days (recommended)",
|
|
4854
5451
|
hint: "Standard retention"
|
|
4855
5452
|
},
|
|
5453
|
+
{
|
|
5454
|
+
value: "6months",
|
|
5455
|
+
label: "6 months",
|
|
5456
|
+
hint: "Extended retention"
|
|
5457
|
+
},
|
|
4856
5458
|
{ value: "1year", label: "1 year", hint: "Compliance requirements" },
|
|
4857
5459
|
{
|
|
4858
|
-
value: "
|
|
4859
|
-
label: "
|
|
4860
|
-
hint: "
|
|
5460
|
+
value: "18months",
|
|
5461
|
+
label: "18 months",
|
|
5462
|
+
hint: "Long-term retention"
|
|
4861
5463
|
}
|
|
4862
5464
|
],
|
|
4863
5465
|
initialValue: config.eventTracking?.archiveRetention || "90days"
|
|
@@ -4866,6 +5468,16 @@ ${pc11.bold("Current Configuration:")}
|
|
|
4866
5468
|
clack11.cancel("Upgrade cancelled.");
|
|
4867
5469
|
process.exit(0);
|
|
4868
5470
|
}
|
|
5471
|
+
clack11.log.info(
|
|
5472
|
+
pc11.dim(
|
|
5473
|
+
"Note: This is for event data (sent, delivered, opened, etc.) stored in DynamoDB."
|
|
5474
|
+
)
|
|
5475
|
+
);
|
|
5476
|
+
clack11.log.info(
|
|
5477
|
+
pc11.dim(
|
|
5478
|
+
"For full email content storage, use 'Enable email archiving' option."
|
|
5479
|
+
)
|
|
5480
|
+
);
|
|
4869
5481
|
updatedConfig = {
|
|
4870
5482
|
...config,
|
|
4871
5483
|
eventTracking: {
|
|
@@ -5029,7 +5641,10 @@ ${pc11.bold("Cost Impact:")}`);
|
|
|
5029
5641
|
lambdaFunctions: result.lambdaFunctions,
|
|
5030
5642
|
domain: result.domain,
|
|
5031
5643
|
dkimTokens: result.dkimTokens,
|
|
5032
|
-
customTrackingDomain: result.customTrackingDomain
|
|
5644
|
+
customTrackingDomain: result.customTrackingDomain,
|
|
5645
|
+
archiveArn: result.archiveArn,
|
|
5646
|
+
archivingEnabled: result.archivingEnabled,
|
|
5647
|
+
archiveRetention: result.archiveRetention
|
|
5033
5648
|
};
|
|
5034
5649
|
}
|
|
5035
5650
|
},
|
|
@@ -5056,7 +5671,10 @@ ${pc11.bold("Cost Impact:")}`);
|
|
|
5056
5671
|
lambdaFunctions: pulumiOutputs.lambdaFunctions?.value,
|
|
5057
5672
|
domain: pulumiOutputs.domain?.value,
|
|
5058
5673
|
dkimTokens: pulumiOutputs.dkimTokens?.value,
|
|
5059
|
-
customTrackingDomain: pulumiOutputs.customTrackingDomain?.value
|
|
5674
|
+
customTrackingDomain: pulumiOutputs.customTrackingDomain?.value,
|
|
5675
|
+
archiveArn: pulumiOutputs.archiveArn?.value,
|
|
5676
|
+
archivingEnabled: pulumiOutputs.archivingEnabled?.value,
|
|
5677
|
+
archiveRetention: pulumiOutputs.archiveRetention?.value
|
|
5060
5678
|
};
|
|
5061
5679
|
}
|
|
5062
5680
|
);
|
|
@@ -5095,12 +5713,12 @@ ${pc11.green("\u2713")} ${pc11.bold("Upgrade complete!")}
|
|
|
5095
5713
|
`);
|
|
5096
5714
|
if (upgradeAction === "preset" && newPreset) {
|
|
5097
5715
|
console.log(
|
|
5098
|
-
`Upgraded to ${pc11.cyan(newPreset)} preset (${pc11.green(formatCost(newCostData.total.monthly)
|
|
5716
|
+
`Upgraded to ${pc11.cyan(newPreset)} preset (${pc11.green(`${formatCost(newCostData.total.monthly)}/mo`)})
|
|
5099
5717
|
`
|
|
5100
5718
|
);
|
|
5101
5719
|
} else {
|
|
5102
5720
|
console.log(
|
|
5103
|
-
`Updated configuration (${pc11.green(formatCost(newCostData.total.monthly)
|
|
5721
|
+
`Updated configuration (${pc11.green(`${formatCost(newCostData.total.monthly)}/mo`)})
|
|
5104
5722
|
`
|
|
5105
5723
|
);
|
|
5106
5724
|
}
|