@wraps.dev/cli 0.1.5 → 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 +784 -48
- 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, {
|
|
@@ -1285,7 +1579,7 @@ async function findHostedZone(domain, region) {
|
|
|
1285
1579
|
return null;
|
|
1286
1580
|
}
|
|
1287
1581
|
}
|
|
1288
|
-
async function createDNSRecords(hostedZoneId, domain, dkimTokens, region, customTrackingDomain) {
|
|
1582
|
+
async function createDNSRecords(hostedZoneId, domain, dkimTokens, region, customTrackingDomain, mailFromDomain) {
|
|
1289
1583
|
const client = new Route53Client({ region });
|
|
1290
1584
|
const changes = [];
|
|
1291
1585
|
for (const token of dkimTokens) {
|
|
@@ -1330,6 +1624,28 @@ async function createDNSRecords(hostedZoneId, domain, dkimTokens, region, custom
|
|
|
1330
1624
|
}
|
|
1331
1625
|
});
|
|
1332
1626
|
}
|
|
1627
|
+
if (mailFromDomain) {
|
|
1628
|
+
changes.push({
|
|
1629
|
+
Action: "UPSERT",
|
|
1630
|
+
ResourceRecordSet: {
|
|
1631
|
+
Name: mailFromDomain,
|
|
1632
|
+
Type: "MX",
|
|
1633
|
+
TTL: 1800,
|
|
1634
|
+
ResourceRecords: [
|
|
1635
|
+
{ Value: `10 feedback-smtp.${region}.amazonses.com` }
|
|
1636
|
+
]
|
|
1637
|
+
}
|
|
1638
|
+
});
|
|
1639
|
+
changes.push({
|
|
1640
|
+
Action: "UPSERT",
|
|
1641
|
+
ResourceRecordSet: {
|
|
1642
|
+
Name: mailFromDomain,
|
|
1643
|
+
Type: "TXT",
|
|
1644
|
+
TTL: 1800,
|
|
1645
|
+
ResourceRecords: [{ Value: '"v=spf1 include:amazonses.com ~all"' }]
|
|
1646
|
+
}
|
|
1647
|
+
});
|
|
1648
|
+
}
|
|
1333
1649
|
await client.send(
|
|
1334
1650
|
new ChangeResourceRecordSetsCommand({
|
|
1335
1651
|
HostedZoneId: hostedZoneId,
|
|
@@ -1560,6 +1876,19 @@ async function createIAMRole(config) {
|
|
|
1560
1876
|
Resource: "arn:aws:sqs:*:*:wraps-email-*"
|
|
1561
1877
|
});
|
|
1562
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
|
+
}
|
|
1563
1892
|
new aws3.iam.RolePolicy("wraps-email-policy", {
|
|
1564
1893
|
role: role.name,
|
|
1565
1894
|
policy: JSON.stringify({
|
|
@@ -1742,8 +2071,9 @@ async function createSESResources(config) {
|
|
|
1742
2071
|
if (config.trackingConfig?.customRedirectDomain) {
|
|
1743
2072
|
configSetOptions.trackingOptions = {
|
|
1744
2073
|
customRedirectDomain: config.trackingConfig.customRedirectDomain,
|
|
1745
|
-
|
|
1746
|
-
//
|
|
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"
|
|
1747
2077
|
};
|
|
1748
2078
|
}
|
|
1749
2079
|
const configSet = new aws5.sesv2.ConfigurationSet(
|
|
@@ -1778,6 +2108,7 @@ async function createSESResources(config) {
|
|
|
1778
2108
|
});
|
|
1779
2109
|
let domainIdentity;
|
|
1780
2110
|
let dkimTokens;
|
|
2111
|
+
let mailFromDomain;
|
|
1781
2112
|
if (config.domain) {
|
|
1782
2113
|
domainIdentity = new aws5.sesv2.EmailIdentity("wraps-email-domain", {
|
|
1783
2114
|
emailIdentity: config.domain,
|
|
@@ -1793,6 +2124,20 @@ async function createSESResources(config) {
|
|
|
1793
2124
|
dkimTokens = domainIdentity.dkimSigningAttributes.apply(
|
|
1794
2125
|
(attrs) => attrs?.tokens || []
|
|
1795
2126
|
);
|
|
2127
|
+
mailFromDomain = config.mailFromDomain || `mail.${config.domain}`;
|
|
2128
|
+
new aws5.sesv2.EmailIdentityMailFromAttributes(
|
|
2129
|
+
"wraps-email-mail-from",
|
|
2130
|
+
{
|
|
2131
|
+
emailIdentity: config.domain,
|
|
2132
|
+
mailFromDomain,
|
|
2133
|
+
behaviorOnMxFailure: "USE_DEFAULT_VALUE"
|
|
2134
|
+
// Fallback to amazonses.com if MX record fails
|
|
2135
|
+
},
|
|
2136
|
+
{
|
|
2137
|
+
dependsOn: [domainIdentity]
|
|
2138
|
+
// Ensure domain identity exists first
|
|
2139
|
+
}
|
|
2140
|
+
);
|
|
1796
2141
|
}
|
|
1797
2142
|
return {
|
|
1798
2143
|
configSet,
|
|
@@ -1802,7 +2147,8 @@ async function createSESResources(config) {
|
|
|
1802
2147
|
dkimTokens,
|
|
1803
2148
|
dnsAutoCreated: false,
|
|
1804
2149
|
// Will be set after deployment
|
|
1805
|
-
customTrackingDomain: config.trackingConfig?.customRedirectDomain
|
|
2150
|
+
customTrackingDomain: config.trackingConfig?.customRedirectDomain,
|
|
2151
|
+
mailFromDomain
|
|
1806
2152
|
};
|
|
1807
2153
|
}
|
|
1808
2154
|
|
|
@@ -1887,6 +2233,7 @@ async function deployEmailStack(config) {
|
|
|
1887
2233
|
if (emailConfig.tracking?.enabled || emailConfig.eventTracking?.enabled) {
|
|
1888
2234
|
sesResources = await createSESResources({
|
|
1889
2235
|
domain: emailConfig.domain,
|
|
2236
|
+
mailFromDomain: emailConfig.mailFromDomain,
|
|
1890
2237
|
region: config.region,
|
|
1891
2238
|
trackingConfig: emailConfig.tracking,
|
|
1892
2239
|
eventTypes: emailConfig.eventTracking?.events
|
|
@@ -1918,6 +2265,15 @@ async function deployEmailStack(config) {
|
|
|
1918
2265
|
accountId
|
|
1919
2266
|
});
|
|
1920
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
|
+
}
|
|
1921
2277
|
return {
|
|
1922
2278
|
roleArn: role.arn,
|
|
1923
2279
|
configSetName: sesResources?.configSet.configurationSetName,
|
|
@@ -1930,7 +2286,11 @@ async function deployEmailStack(config) {
|
|
|
1930
2286
|
eventBusName: sesResources?.eventBus.name,
|
|
1931
2287
|
queueUrl: sqsResources?.queue.url,
|
|
1932
2288
|
dlqUrl: sqsResources?.dlq.url,
|
|
1933
|
-
customTrackingDomain: sesResources?.customTrackingDomain
|
|
2289
|
+
customTrackingDomain: sesResources?.customTrackingDomain,
|
|
2290
|
+
mailFromDomain: sesResources?.mailFromDomain,
|
|
2291
|
+
archiveArn: archiveResources?.archive.arn,
|
|
2292
|
+
archivingEnabled: emailConfig.emailArchiving?.enabled,
|
|
2293
|
+
archiveRetention: emailConfig.emailArchiving?.enabled ? emailConfig.emailArchiving.retention : void 0
|
|
1934
2294
|
};
|
|
1935
2295
|
}
|
|
1936
2296
|
|
|
@@ -2149,6 +2509,14 @@ Verification should complete within a few minutes.`,
|
|
|
2149
2509
|
pc2.bold("DMARC Record (TXT):"),
|
|
2150
2510
|
` ${pc2.cyan(`_dmarc.${domain}`)} ${pc2.dim("TXT")} "v=DMARC1; p=quarantine; rua=mailto:postmaster@${domain}"`
|
|
2151
2511
|
);
|
|
2512
|
+
if (outputs.mailFromDomain) {
|
|
2513
|
+
dnsLines.push(
|
|
2514
|
+
"",
|
|
2515
|
+
pc2.bold("MAIL FROM Domain Records (for DMARC alignment):"),
|
|
2516
|
+
` ${pc2.cyan(outputs.mailFromDomain)} ${pc2.dim("MX")} "10 feedback-smtp.${outputs.region}.amazonses.com"`,
|
|
2517
|
+
` ${pc2.cyan(outputs.mailFromDomain)} ${pc2.dim("TXT")} "v=spf1 include:amazonses.com ~all"`
|
|
2518
|
+
);
|
|
2519
|
+
}
|
|
2152
2520
|
}
|
|
2153
2521
|
clack2.note(dnsLines.join("\n"), "DNS Records to add:");
|
|
2154
2522
|
}
|
|
@@ -2201,7 +2569,14 @@ function displayStatus(status2) {
|
|
|
2201
2569
|
const domainStrings = status2.domains.map((d) => {
|
|
2202
2570
|
const statusIcon = d.status === "verified" ? "\u2713" : d.status === "pending" ? "\u23F1" : "\u2717";
|
|
2203
2571
|
const statusColor = d.status === "verified" ? pc2.green : d.status === "pending" ? pc2.yellow : pc2.red;
|
|
2204
|
-
|
|
2572
|
+
let domainLine = ` ${d.domain} ${statusColor(`${statusIcon} ${d.status}`)}`;
|
|
2573
|
+
if (d.mailFromDomain) {
|
|
2574
|
+
const mailFromStatusIcon = d.mailFromStatus === "SUCCESS" ? "\u2713" : "\u23F1";
|
|
2575
|
+
const mailFromColor = d.mailFromStatus === "SUCCESS" ? pc2.green : pc2.yellow;
|
|
2576
|
+
domainLine += `
|
|
2577
|
+
${pc2.dim("MAIL FROM:")} ${d.mailFromDomain} ${mailFromColor(mailFromStatusIcon)}`;
|
|
2578
|
+
}
|
|
2579
|
+
return domainLine;
|
|
2205
2580
|
});
|
|
2206
2581
|
infoLines.push(`${pc2.bold("Domains:")}
|
|
2207
2582
|
${domainStrings.join("\n")}`);
|
|
@@ -2227,6 +2602,23 @@ ${domainStrings.join("\n")}`);
|
|
|
2227
2602
|
` ${pc2.dim("\u25CB")} Bounce/Complaint Handling ${pc2.dim("(run 'wraps upgrade' to enable)")}`
|
|
2228
2603
|
);
|
|
2229
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
|
+
}
|
|
2230
2622
|
featureLines.push(
|
|
2231
2623
|
` ${pc2.green("\u2713")} Console Dashboard ${pc2.dim("(run 'wraps console')")}`
|
|
2232
2624
|
);
|
|
@@ -2259,14 +2651,20 @@ ${domainStrings.join("\n")}`);
|
|
|
2259
2651
|
` ${pc2.green("\u2713")} SNS Topics: ${pc2.cyan(`${status2.resources.snsTopics} configured`)}`
|
|
2260
2652
|
);
|
|
2261
2653
|
}
|
|
2654
|
+
if (status2.resources.archiveArn) {
|
|
2655
|
+
resourceLines.push(
|
|
2656
|
+
` ${pc2.green("\u2713")} Mail Manager Archive: ${pc2.cyan(status2.resources.archiveArn)}`
|
|
2657
|
+
);
|
|
2658
|
+
}
|
|
2262
2659
|
clack2.note(resourceLines.join("\n"), "Resources");
|
|
2263
|
-
const
|
|
2264
|
-
(d) => d.status === "pending" && d.dkimTokens
|
|
2660
|
+
const domainsNeedingDNS = status2.domains.filter(
|
|
2661
|
+
(d) => d.status === "pending" && d.dkimTokens || d.mailFromDomain && d.mailFromStatus !== "SUCCESS"
|
|
2265
2662
|
);
|
|
2266
|
-
if (
|
|
2267
|
-
for (const domain of
|
|
2268
|
-
|
|
2269
|
-
|
|
2663
|
+
if (domainsNeedingDNS.length > 0) {
|
|
2664
|
+
for (const domain of domainsNeedingDNS) {
|
|
2665
|
+
const dnsLines = [];
|
|
2666
|
+
if (domain.status === "pending" && domain.dkimTokens && domain.dkimTokens.length > 0) {
|
|
2667
|
+
dnsLines.push(
|
|
2270
2668
|
pc2.bold("DKIM Records (CNAME):"),
|
|
2271
2669
|
...domain.dkimTokens.map(
|
|
2272
2670
|
(token) => ` ${pc2.cyan(`${token}._domainkey.${domain.domain}`)} ${pc2.dim("CNAME")} "${token}.dkim.amazonses.com"`
|
|
@@ -2277,11 +2675,23 @@ ${domainStrings.join("\n")}`);
|
|
|
2277
2675
|
"",
|
|
2278
2676
|
pc2.bold("DMARC Record (TXT):"),
|
|
2279
2677
|
` ${pc2.cyan(`_dmarc.${domain.domain}`)} ${pc2.dim("TXT")} "v=DMARC1; p=quarantine; rua=mailto:postmaster@${domain.domain}"`
|
|
2280
|
-
|
|
2678
|
+
);
|
|
2679
|
+
}
|
|
2680
|
+
if (domain.mailFromDomain && domain.mailFromStatus !== "SUCCESS") {
|
|
2681
|
+
if (dnsLines.length > 0) {
|
|
2682
|
+
dnsLines.push("");
|
|
2683
|
+
}
|
|
2684
|
+
dnsLines.push(
|
|
2685
|
+
pc2.bold("MAIL FROM Domain Records (for DMARC alignment):"),
|
|
2686
|
+
` ${pc2.cyan(domain.mailFromDomain)} ${pc2.dim("MX")} "10 feedback-smtp.${status2.region}.amazonses.com"`,
|
|
2687
|
+
` ${pc2.cyan(domain.mailFromDomain)} ${pc2.dim("TXT")} "v=spf1 include:amazonses.com ~all"`
|
|
2688
|
+
);
|
|
2689
|
+
}
|
|
2690
|
+
if (dnsLines.length > 0) {
|
|
2281
2691
|
clack2.note(dnsLines.join("\n"), `DNS Records for ${domain.domain}`);
|
|
2282
2692
|
}
|
|
2283
2693
|
}
|
|
2284
|
-
const exampleDomain =
|
|
2694
|
+
const exampleDomain = domainsNeedingDNS[0].domain;
|
|
2285
2695
|
console.log(
|
|
2286
2696
|
`
|
|
2287
2697
|
${pc2.dim("Run:")} ${pc2.yellow(`wraps verify --domain ${exampleDomain}`)} ${pc2.dim(
|
|
@@ -3205,6 +3615,46 @@ function createEmailsRouter(config) {
|
|
|
3205
3615
|
res.status(500).json({ error: errorMessage });
|
|
3206
3616
|
}
|
|
3207
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
|
+
});
|
|
3208
3658
|
return router;
|
|
3209
3659
|
}
|
|
3210
3660
|
|
|
@@ -3491,7 +3941,9 @@ async function fetchEmailIdentity(roleArn, region, identityName) {
|
|
|
3491
3941
|
verifiedForSendingStatus: response.VerifiedForSendingStatus ?? false,
|
|
3492
3942
|
tags: response.Tags?.reduce(
|
|
3493
3943
|
(acc, tag) => {
|
|
3494
|
-
if (tag.Key)
|
|
3944
|
+
if (tag.Key) {
|
|
3945
|
+
acc[tag.Key] = tag.Value || "";
|
|
3946
|
+
}
|
|
3495
3947
|
return acc;
|
|
3496
3948
|
},
|
|
3497
3949
|
{}
|
|
@@ -3524,6 +3976,20 @@ async function fetchEmailSettings(roleArn, region, configSetName, domain) {
|
|
|
3524
3976
|
// src/console/routes/settings.ts
|
|
3525
3977
|
function createSettingsRouter(config) {
|
|
3526
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
|
+
});
|
|
3527
3993
|
router.get("/", async (_req, res) => {
|
|
3528
3994
|
try {
|
|
3529
3995
|
const metadata = await loadConnectionMetadata(
|
|
@@ -3899,6 +4365,8 @@ async function runConsole(options) {
|
|
|
3899
4365
|
process.exit(1);
|
|
3900
4366
|
}
|
|
3901
4367
|
const tableName = stackOutputs.tableName?.value;
|
|
4368
|
+
const archiveArn = stackOutputs.archiveArn?.value;
|
|
4369
|
+
const archivingEnabled = stackOutputs.archivingEnabled?.value ?? false;
|
|
3902
4370
|
const port = options.port || await getPort({ port: [5555, 5556, 5557, 5558, 5559] });
|
|
3903
4371
|
progress.stop();
|
|
3904
4372
|
clack5.log.success("Starting console server...");
|
|
@@ -3912,7 +4380,9 @@ async function runConsole(options) {
|
|
|
3912
4380
|
region,
|
|
3913
4381
|
tableName,
|
|
3914
4382
|
accountId: identity.accountId,
|
|
3915
|
-
noOpen: options.noOpen ?? false
|
|
4383
|
+
noOpen: options.noOpen ?? false,
|
|
4384
|
+
archiveArn,
|
|
4385
|
+
archivingEnabled
|
|
3916
4386
|
});
|
|
3917
4387
|
console.log(`\\n${pc5.bold("Console:")} ${pc5.cyan(url)}`);
|
|
3918
4388
|
console.log(`${pc5.dim("Press Ctrl+C to stop")}\\n`);
|
|
@@ -4052,17 +4522,22 @@ async function init(options) {
|
|
|
4052
4522
|
emailConfig = await promptCustomConfig();
|
|
4053
4523
|
} else {
|
|
4054
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;
|
|
4055
4528
|
}
|
|
4056
4529
|
if (domain) {
|
|
4057
4530
|
emailConfig.domain = domain;
|
|
4058
4531
|
}
|
|
4059
4532
|
const estimatedVolume = await promptEstimatedVolume();
|
|
4060
|
-
progress.info(
|
|
4533
|
+
progress.info(`
|
|
4534
|
+
${pc7.bold("Cost Estimate:")}`);
|
|
4061
4535
|
const costSummary = getCostSummary(emailConfig, estimatedVolume);
|
|
4062
4536
|
clack7.log.info(costSummary);
|
|
4063
4537
|
const warnings = validateConfig(emailConfig);
|
|
4064
4538
|
if (warnings.length > 0) {
|
|
4065
|
-
progress.info(
|
|
4539
|
+
progress.info(`
|
|
4540
|
+
${pc7.yellow(pc7.bold("Configuration Warnings:"))}`);
|
|
4066
4541
|
for (const warning of warnings) {
|
|
4067
4542
|
clack7.log.warn(warning);
|
|
4068
4543
|
}
|
|
@@ -4110,7 +4585,11 @@ async function init(options) {
|
|
|
4110
4585
|
lambdaFunctions: result.lambdaFunctions,
|
|
4111
4586
|
domain: result.domain,
|
|
4112
4587
|
dkimTokens: result.dkimTokens,
|
|
4113
|
-
customTrackingDomain: result.customTrackingDomain
|
|
4588
|
+
customTrackingDomain: result.customTrackingDomain,
|
|
4589
|
+
mailFromDomain: result.mailFromDomain,
|
|
4590
|
+
archiveArn: result.archiveArn,
|
|
4591
|
+
archivingEnabled: result.archivingEnabled,
|
|
4592
|
+
archiveRetention: result.archiveRetention
|
|
4114
4593
|
};
|
|
4115
4594
|
}
|
|
4116
4595
|
},
|
|
@@ -4139,7 +4618,11 @@ async function init(options) {
|
|
|
4139
4618
|
lambdaFunctions: pulumiOutputs.lambdaFunctions?.value,
|
|
4140
4619
|
domain: pulumiOutputs.domain?.value,
|
|
4141
4620
|
dkimTokens: pulumiOutputs.dkimTokens?.value,
|
|
4142
|
-
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
|
|
4143
4626
|
};
|
|
4144
4627
|
}
|
|
4145
4628
|
);
|
|
@@ -4168,7 +4651,8 @@ async function init(options) {
|
|
|
4168
4651
|
outputs.domain,
|
|
4169
4652
|
outputs.dkimTokens,
|
|
4170
4653
|
region,
|
|
4171
|
-
outputs.customTrackingDomain
|
|
4654
|
+
outputs.customTrackingDomain,
|
|
4655
|
+
outputs.mailFromDomain
|
|
4172
4656
|
);
|
|
4173
4657
|
progress.succeed("DNS records created in Route53");
|
|
4174
4658
|
dnsAutoCreated = true;
|
|
@@ -4195,7 +4679,8 @@ async function init(options) {
|
|
|
4195
4679
|
tableName: outputs.tableName,
|
|
4196
4680
|
dnsRecords: dnsRecords.length > 0 ? dnsRecords : void 0,
|
|
4197
4681
|
dnsAutoCreated,
|
|
4198
|
-
domain: outputs.domain
|
|
4682
|
+
domain: outputs.domain,
|
|
4683
|
+
mailFromDomain: outputs.mailFromDomain
|
|
4199
4684
|
});
|
|
4200
4685
|
}
|
|
4201
4686
|
|
|
@@ -4345,13 +4830,17 @@ Run ${pc9.cyan("wraps init")} to deploy infrastructure.
|
|
|
4345
4830
|
return {
|
|
4346
4831
|
domain: d.domain,
|
|
4347
4832
|
status: d.verified ? "verified" : "pending",
|
|
4348
|
-
dkimTokens: identity2.DkimAttributes?.Tokens || []
|
|
4833
|
+
dkimTokens: identity2.DkimAttributes?.Tokens || [],
|
|
4834
|
+
mailFromDomain: identity2.MailFromAttributes?.MailFromDomain,
|
|
4835
|
+
mailFromStatus: identity2.MailFromAttributes?.MailFromDomainStatus
|
|
4349
4836
|
};
|
|
4350
4837
|
} catch (_error) {
|
|
4351
4838
|
return {
|
|
4352
4839
|
domain: d.domain,
|
|
4353
4840
|
status: d.verified ? "verified" : "pending",
|
|
4354
|
-
dkimTokens: void 0
|
|
4841
|
+
dkimTokens: void 0,
|
|
4842
|
+
mailFromDomain: void 0,
|
|
4843
|
+
mailFromStatus: void 0
|
|
4355
4844
|
};
|
|
4356
4845
|
}
|
|
4357
4846
|
})
|
|
@@ -4367,7 +4856,10 @@ Run ${pc9.cyan("wraps init")} to deploy infrastructure.
|
|
|
4367
4856
|
configSetName: stackOutputs.configSetName?.value,
|
|
4368
4857
|
tableName: stackOutputs.tableName?.value,
|
|
4369
4858
|
lambdaFunctions: stackOutputs.lambdaFunctions?.value?.length || 0,
|
|
4370
|
-
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
|
|
4371
4863
|
}
|
|
4372
4864
|
});
|
|
4373
4865
|
}
|
|
@@ -4634,6 +5126,18 @@ ${pc11.bold("Current Configuration:")}
|
|
|
4634
5126
|
if (config.dedicatedIp) {
|
|
4635
5127
|
console.log(` ${pc11.green("\u2713")} Dedicated IP Address`);
|
|
4636
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
|
+
}
|
|
4637
5141
|
const currentCostData = calculateCosts(config, 5e4);
|
|
4638
5142
|
console.log(
|
|
4639
5143
|
`
|
|
@@ -4648,6 +5152,11 @@ ${pc11.bold("Current Configuration:")}
|
|
|
4648
5152
|
label: "Upgrade to a different preset",
|
|
4649
5153
|
hint: "Starter \u2192 Production \u2192 Enterprise"
|
|
4650
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
|
+
},
|
|
4651
5160
|
{
|
|
4652
5161
|
value: "tracking-domain",
|
|
4653
5162
|
label: "Add/change custom tracking domain",
|
|
@@ -4656,7 +5165,7 @@ ${pc11.bold("Current Configuration:")}
|
|
|
4656
5165
|
{
|
|
4657
5166
|
value: "retention",
|
|
4658
5167
|
label: "Change email history retention",
|
|
4659
|
-
hint: "7 days, 30 days, 90 days, 1 year,
|
|
5168
|
+
hint: "7 days, 30 days, 90 days, 6 months, 1 year, 18 months"
|
|
4660
5169
|
},
|
|
4661
5170
|
{
|
|
4662
5171
|
value: "events",
|
|
@@ -4714,6 +5223,166 @@ ${pc11.bold("Current Configuration:")}
|
|
|
4714
5223
|
newPreset = selectedPreset;
|
|
4715
5224
|
break;
|
|
4716
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
|
+
}
|
|
4717
5386
|
case "tracking-domain": {
|
|
4718
5387
|
if (!config.domain) {
|
|
4719
5388
|
clack11.log.error(
|
|
@@ -4772,7 +5441,7 @@ ${pc11.bold("Current Configuration:")}
|
|
|
4772
5441
|
}
|
|
4773
5442
|
case "retention": {
|
|
4774
5443
|
const retention = await clack11.select({
|
|
4775
|
-
message: "Email history retention period:",
|
|
5444
|
+
message: "Email history retention period (event data in DynamoDB):",
|
|
4776
5445
|
options: [
|
|
4777
5446
|
{ value: "7days", label: "7 days", hint: "Minimal storage cost" },
|
|
4778
5447
|
{ value: "30days", label: "30 days", hint: "Development/testing" },
|
|
@@ -4781,11 +5450,16 @@ ${pc11.bold("Current Configuration:")}
|
|
|
4781
5450
|
label: "90 days (recommended)",
|
|
4782
5451
|
hint: "Standard retention"
|
|
4783
5452
|
},
|
|
5453
|
+
{
|
|
5454
|
+
value: "6months",
|
|
5455
|
+
label: "6 months",
|
|
5456
|
+
hint: "Extended retention"
|
|
5457
|
+
},
|
|
4784
5458
|
{ value: "1year", label: "1 year", hint: "Compliance requirements" },
|
|
4785
5459
|
{
|
|
4786
|
-
value: "
|
|
4787
|
-
label: "
|
|
4788
|
-
hint: "
|
|
5460
|
+
value: "18months",
|
|
5461
|
+
label: "18 months",
|
|
5462
|
+
hint: "Long-term retention"
|
|
4789
5463
|
}
|
|
4790
5464
|
],
|
|
4791
5465
|
initialValue: config.eventTracking?.archiveRetention || "90days"
|
|
@@ -4794,6 +5468,16 @@ ${pc11.bold("Current Configuration:")}
|
|
|
4794
5468
|
clack11.cancel("Upgrade cancelled.");
|
|
4795
5469
|
process.exit(0);
|
|
4796
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
|
+
);
|
|
4797
5481
|
updatedConfig = {
|
|
4798
5482
|
...config,
|
|
4799
5483
|
eventTracking: {
|
|
@@ -4957,7 +5641,10 @@ ${pc11.bold("Cost Impact:")}`);
|
|
|
4957
5641
|
lambdaFunctions: result.lambdaFunctions,
|
|
4958
5642
|
domain: result.domain,
|
|
4959
5643
|
dkimTokens: result.dkimTokens,
|
|
4960
|
-
customTrackingDomain: result.customTrackingDomain
|
|
5644
|
+
customTrackingDomain: result.customTrackingDomain,
|
|
5645
|
+
archiveArn: result.archiveArn,
|
|
5646
|
+
archivingEnabled: result.archivingEnabled,
|
|
5647
|
+
archiveRetention: result.archiveRetention
|
|
4961
5648
|
};
|
|
4962
5649
|
}
|
|
4963
5650
|
},
|
|
@@ -4984,7 +5671,10 @@ ${pc11.bold("Cost Impact:")}`);
|
|
|
4984
5671
|
lambdaFunctions: pulumiOutputs.lambdaFunctions?.value,
|
|
4985
5672
|
domain: pulumiOutputs.domain?.value,
|
|
4986
5673
|
dkimTokens: pulumiOutputs.dkimTokens?.value,
|
|
4987
|
-
customTrackingDomain: pulumiOutputs.customTrackingDomain?.value
|
|
5674
|
+
customTrackingDomain: pulumiOutputs.customTrackingDomain?.value,
|
|
5675
|
+
archiveArn: pulumiOutputs.archiveArn?.value,
|
|
5676
|
+
archivingEnabled: pulumiOutputs.archivingEnabled?.value,
|
|
5677
|
+
archiveRetention: pulumiOutputs.archiveRetention?.value
|
|
4988
5678
|
};
|
|
4989
5679
|
}
|
|
4990
5680
|
);
|
|
@@ -5023,12 +5713,12 @@ ${pc11.green("\u2713")} ${pc11.bold("Upgrade complete!")}
|
|
|
5023
5713
|
`);
|
|
5024
5714
|
if (upgradeAction === "preset" && newPreset) {
|
|
5025
5715
|
console.log(
|
|
5026
|
-
`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`)})
|
|
5027
5717
|
`
|
|
5028
5718
|
);
|
|
5029
5719
|
} else {
|
|
5030
5720
|
console.log(
|
|
5031
|
-
`Updated configuration (${pc11.green(formatCost(newCostData.total.monthly)
|
|
5721
|
+
`Updated configuration (${pc11.green(`${formatCost(newCostData.total.monthly)}/mo`)})
|
|
5032
5722
|
`
|
|
5033
5723
|
);
|
|
5034
5724
|
}
|
|
@@ -5048,6 +5738,7 @@ async function verify(options) {
|
|
|
5048
5738
|
const sesClient = new SESv2Client3({ region });
|
|
5049
5739
|
let identity;
|
|
5050
5740
|
let dkimTokens = [];
|
|
5741
|
+
let mailFromDomain;
|
|
5051
5742
|
try {
|
|
5052
5743
|
identity = await progress.execute(
|
|
5053
5744
|
"Checking SES verification status",
|
|
@@ -5059,6 +5750,7 @@ async function verify(options) {
|
|
|
5059
5750
|
}
|
|
5060
5751
|
);
|
|
5061
5752
|
dkimTokens = identity.DkimAttributes?.Tokens || [];
|
|
5753
|
+
mailFromDomain = identity.MailFromAttributes?.MailFromDomain;
|
|
5062
5754
|
} catch (_error) {
|
|
5063
5755
|
progress.stop();
|
|
5064
5756
|
clack12.log.error(`Domain ${options.domain} not found in SES`);
|
|
@@ -5070,6 +5762,7 @@ Run ${pc12.cyan(`wraps init --domain ${options.domain}`)} to add this domain.
|
|
|
5070
5762
|
process.exit(1);
|
|
5071
5763
|
}
|
|
5072
5764
|
const resolver = new Resolver();
|
|
5765
|
+
resolver.setServers(["8.8.8.8", "1.1.1.1"]);
|
|
5073
5766
|
const dnsResults = [];
|
|
5074
5767
|
for (const token of dkimTokens) {
|
|
5075
5768
|
const dkimRecord = `${token}._domainkey.${options.domain}`;
|
|
@@ -5124,17 +5817,60 @@ Run ${pc12.cyan(`wraps init --domain ${options.domain}`)} to add this domain.
|
|
|
5124
5817
|
status: "missing"
|
|
5125
5818
|
});
|
|
5126
5819
|
}
|
|
5820
|
+
if (mailFromDomain) {
|
|
5821
|
+
try {
|
|
5822
|
+
const mxRecords = await resolver.resolveMx(mailFromDomain);
|
|
5823
|
+
const expectedMx = `feedback-smtp.${region}.amazonses.com`;
|
|
5824
|
+
const hasMx = mxRecords.some(
|
|
5825
|
+
(r) => r.exchange === expectedMx || r.exchange === `${expectedMx}.`
|
|
5826
|
+
);
|
|
5827
|
+
dnsResults.push({
|
|
5828
|
+
name: mailFromDomain,
|
|
5829
|
+
type: "MX",
|
|
5830
|
+
status: hasMx ? "verified" : mxRecords.length > 0 ? "incorrect" : "missing",
|
|
5831
|
+
records: mxRecords.map((r) => `${r.priority} ${r.exchange}`)
|
|
5832
|
+
});
|
|
5833
|
+
} catch (_error) {
|
|
5834
|
+
dnsResults.push({
|
|
5835
|
+
name: mailFromDomain,
|
|
5836
|
+
type: "MX",
|
|
5837
|
+
status: "missing"
|
|
5838
|
+
});
|
|
5839
|
+
}
|
|
5840
|
+
try {
|
|
5841
|
+
const records = await resolver.resolveTxt(mailFromDomain);
|
|
5842
|
+
const spfRecord = records.flat().find((r) => r.startsWith("v=spf1"));
|
|
5843
|
+
const hasAmazonSES = spfRecord?.includes("include:amazonses.com");
|
|
5844
|
+
dnsResults.push({
|
|
5845
|
+
name: mailFromDomain,
|
|
5846
|
+
type: "TXT (SPF)",
|
|
5847
|
+
status: hasAmazonSES ? "verified" : spfRecord ? "incorrect" : "missing",
|
|
5848
|
+
records: spfRecord ? [spfRecord] : void 0
|
|
5849
|
+
});
|
|
5850
|
+
} catch (_error) {
|
|
5851
|
+
dnsResults.push({
|
|
5852
|
+
name: mailFromDomain,
|
|
5853
|
+
type: "TXT (SPF)",
|
|
5854
|
+
status: "missing"
|
|
5855
|
+
});
|
|
5856
|
+
}
|
|
5857
|
+
}
|
|
5127
5858
|
progress.stop();
|
|
5128
5859
|
const verificationStatus = identity.VerifiedForSendingStatus ? "verified" : "pending";
|
|
5129
5860
|
const dkimStatus = identity.DkimAttributes?.Status || "PENDING";
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5861
|
+
const mailFromStatus = identity.MailFromAttributes?.MailFromDomainStatus || "NOT_CONFIGURED";
|
|
5862
|
+
const statusLines = [
|
|
5863
|
+
`${pc12.bold("Domain:")} ${options.domain}`,
|
|
5864
|
+
`${pc12.bold("Verification Status:")} ${verificationStatus === "verified" ? pc12.green("\u2713 Verified") : pc12.yellow("\u23F1 Pending")}`,
|
|
5865
|
+
`${pc12.bold("DKIM Status:")} ${dkimStatus === "SUCCESS" ? pc12.green("\u2713 Success") : pc12.yellow(`\u23F1 ${dkimStatus}`)}`
|
|
5866
|
+
];
|
|
5867
|
+
if (mailFromDomain) {
|
|
5868
|
+
statusLines.push(
|
|
5869
|
+
`${pc12.bold("MAIL FROM Domain:")} ${mailFromDomain}`,
|
|
5870
|
+
`${pc12.bold("MAIL FROM Status:")} ${mailFromStatus === "SUCCESS" ? pc12.green("\u2713 Success") : mailFromStatus === "NOT_CONFIGURED" ? pc12.yellow("\u23F1 Not Configured") : pc12.yellow(`\u23F1 ${mailFromStatus}`)}`
|
|
5871
|
+
);
|
|
5872
|
+
}
|
|
5873
|
+
clack12.note(statusLines.join("\n"), "SES Status");
|
|
5138
5874
|
const dnsLines = dnsResults.map((record) => {
|
|
5139
5875
|
let statusIcon;
|
|
5140
5876
|
let statusColor;
|