@stacksjs/ts-cloud-core 0.1.1
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/LICENSE.md +21 -0
- package/README.md +321 -0
- package/package.json +31 -0
- package/src/advanced-features.test.ts +465 -0
- package/src/aws/cloudformation.ts +421 -0
- package/src/aws/cloudfront.ts +158 -0
- package/src/aws/credentials.test.ts +132 -0
- package/src/aws/credentials.ts +545 -0
- package/src/aws/index.ts +87 -0
- package/src/aws/s3.test.ts +188 -0
- package/src/aws/s3.ts +1088 -0
- package/src/aws/signature.test.ts +670 -0
- package/src/aws/signature.ts +1155 -0
- package/src/backup/disaster-recovery.test.ts +726 -0
- package/src/backup/disaster-recovery.ts +500 -0
- package/src/backup/index.ts +34 -0
- package/src/backup/manager.test.ts +498 -0
- package/src/backup/manager.ts +432 -0
- package/src/cicd/circleci.ts +430 -0
- package/src/cicd/github-actions.ts +424 -0
- package/src/cicd/gitlab-ci.ts +255 -0
- package/src/cicd/index.ts +8 -0
- package/src/cli/history.ts +396 -0
- package/src/cli/index.ts +10 -0
- package/src/cli/progress.ts +458 -0
- package/src/cli/repl.ts +454 -0
- package/src/cli/suggestions.ts +327 -0
- package/src/cli/table.test.ts +319 -0
- package/src/cli/table.ts +332 -0
- package/src/cloudformation/builder.test.ts +327 -0
- package/src/cloudformation/builder.ts +378 -0
- package/src/cloudformation/builders/api-gateway.ts +449 -0
- package/src/cloudformation/builders/cache.ts +334 -0
- package/src/cloudformation/builders/cdn.ts +278 -0
- package/src/cloudformation/builders/compute.ts +485 -0
- package/src/cloudformation/builders/database.ts +392 -0
- package/src/cloudformation/builders/functions.ts +343 -0
- package/src/cloudformation/builders/messaging.ts +140 -0
- package/src/cloudformation/builders/monitoring.ts +300 -0
- package/src/cloudformation/builders/network.ts +264 -0
- package/src/cloudformation/builders/queue.ts +147 -0
- package/src/cloudformation/builders/security.ts +399 -0
- package/src/cloudformation/builders/storage.ts +285 -0
- package/src/cloudformation/index.ts +30 -0
- package/src/cloudformation/types.ts +173 -0
- package/src/compliance/aws-config.ts +543 -0
- package/src/compliance/cloudtrail.ts +376 -0
- package/src/compliance/compliance.test.ts +423 -0
- package/src/compliance/guardduty.ts +446 -0
- package/src/compliance/index.ts +66 -0
- package/src/compliance/security-hub.ts +456 -0
- package/src/containers/build-optimization.ts +416 -0
- package/src/containers/containers.test.ts +508 -0
- package/src/containers/image-scanning.ts +360 -0
- package/src/containers/index.ts +9 -0
- package/src/containers/registry.ts +293 -0
- package/src/containers/service-mesh.ts +520 -0
- package/src/database/database.test.ts +762 -0
- package/src/database/index.ts +9 -0
- package/src/database/migrations.ts +444 -0
- package/src/database/performance.ts +528 -0
- package/src/database/replicas.ts +534 -0
- package/src/database/users.ts +494 -0
- package/src/dependency-graph.ts +143 -0
- package/src/deployment/ab-testing.ts +582 -0
- package/src/deployment/blue-green.ts +452 -0
- package/src/deployment/canary.ts +500 -0
- package/src/deployment/deployment.test.ts +526 -0
- package/src/deployment/index.ts +61 -0
- package/src/deployment/progressive.ts +62 -0
- package/src/dns/dns.test.ts +641 -0
- package/src/dns/dnssec.ts +315 -0
- package/src/dns/index.ts +8 -0
- package/src/dns/resolver.ts +496 -0
- package/src/dns/routing.ts +593 -0
- package/src/email/advanced/analytics.ts +445 -0
- package/src/email/advanced/index.ts +11 -0
- package/src/email/advanced/rules.ts +465 -0
- package/src/email/advanced/scheduling.ts +352 -0
- package/src/email/advanced/search.ts +412 -0
- package/src/email/advanced/shared-mailboxes.ts +404 -0
- package/src/email/advanced/templates.ts +455 -0
- package/src/email/advanced/threading.ts +281 -0
- package/src/email/analytics.ts +467 -0
- package/src/email/bounce-handling.ts +425 -0
- package/src/email/email.test.ts +431 -0
- package/src/email/handlers/__tests__/inbound.test.ts +38 -0
- package/src/email/handlers/__tests__/outbound.test.ts +37 -0
- package/src/email/handlers/converter.ts +227 -0
- package/src/email/handlers/feedback.ts +228 -0
- package/src/email/handlers/inbound.ts +169 -0
- package/src/email/handlers/outbound.ts +178 -0
- package/src/email/index.ts +15 -0
- package/src/email/reputation.ts +303 -0
- package/src/email/templates.ts +352 -0
- package/src/errors/index.test.ts +434 -0
- package/src/errors/index.ts +416 -0
- package/src/health-checks/index.ts +40 -0
- package/src/index.ts +360 -0
- package/src/intrinsic-functions.ts +118 -0
- package/src/lambda/concurrency.ts +330 -0
- package/src/lambda/destinations.ts +345 -0
- package/src/lambda/dlq.ts +425 -0
- package/src/lambda/index.ts +11 -0
- package/src/lambda/lambda.test.ts +840 -0
- package/src/lambda/layers.ts +263 -0
- package/src/lambda/versions.ts +376 -0
- package/src/lambda/vpc.ts +399 -0
- package/src/local/config.ts +114 -0
- package/src/local/index.ts +6 -0
- package/src/local/mock-aws.ts +351 -0
- package/src/modules/ai.ts +340 -0
- package/src/modules/api.ts +478 -0
- package/src/modules/auth.ts +805 -0
- package/src/modules/cache.ts +417 -0
- package/src/modules/cdn.ts +1062 -0
- package/src/modules/communication.ts +1094 -0
- package/src/modules/compute.ts +3348 -0
- package/src/modules/database.ts +554 -0
- package/src/modules/deployment.ts +1079 -0
- package/src/modules/dns.ts +337 -0
- package/src/modules/email.ts +1538 -0
- package/src/modules/filesystem.ts +515 -0
- package/src/modules/index.ts +32 -0
- package/src/modules/messaging.ts +486 -0
- package/src/modules/monitoring.ts +2086 -0
- package/src/modules/network.ts +664 -0
- package/src/modules/parameter-store.ts +325 -0
- package/src/modules/permissions.ts +1081 -0
- package/src/modules/phone.ts +494 -0
- package/src/modules/queue.ts +1260 -0
- package/src/modules/redirects.ts +464 -0
- package/src/modules/registry.ts +699 -0
- package/src/modules/search.ts +401 -0
- package/src/modules/secrets.ts +416 -0
- package/src/modules/security.ts +731 -0
- package/src/modules/sms.ts +389 -0
- package/src/modules/storage.ts +1120 -0
- package/src/modules/workflow.ts +680 -0
- package/src/multi-account/config.ts +521 -0
- package/src/multi-account/index.ts +7 -0
- package/src/multi-account/manager.ts +427 -0
- package/src/multi-region/cross-region.ts +410 -0
- package/src/multi-region/index.ts +8 -0
- package/src/multi-region/manager.ts +483 -0
- package/src/multi-region/regions.ts +435 -0
- package/src/network-security/index.ts +48 -0
- package/src/observability/index.ts +9 -0
- package/src/observability/logs.ts +522 -0
- package/src/observability/metrics.ts +460 -0
- package/src/observability/observability.test.ts +782 -0
- package/src/observability/synthetics.ts +568 -0
- package/src/observability/xray.ts +358 -0
- package/src/phone/advanced/analytics.ts +349 -0
- package/src/phone/advanced/callbacks.ts +428 -0
- package/src/phone/advanced/index.ts +8 -0
- package/src/phone/advanced/ivr-builder.ts +504 -0
- package/src/phone/advanced/recording.ts +310 -0
- package/src/phone/handlers/__tests__/incoming-call.test.ts +40 -0
- package/src/phone/handlers/incoming-call.ts +117 -0
- package/src/phone/handlers/missed-call.ts +116 -0
- package/src/phone/handlers/voicemail.ts +179 -0
- package/src/phone/index.ts +9 -0
- package/src/presets/api-backend.ts +134 -0
- package/src/presets/data-pipeline.ts +204 -0
- package/src/presets/extend.test.ts +295 -0
- package/src/presets/extend.ts +297 -0
- package/src/presets/fullstack-app.ts +144 -0
- package/src/presets/index.ts +27 -0
- package/src/presets/jamstack.ts +135 -0
- package/src/presets/microservices.ts +167 -0
- package/src/presets/ml-api.ts +208 -0
- package/src/presets/nodejs-server.ts +104 -0
- package/src/presets/nodejs-serverless.ts +114 -0
- package/src/presets/realtime-app.ts +184 -0
- package/src/presets/static-site.ts +64 -0
- package/src/presets/traditional-web-app.ts +339 -0
- package/src/presets/wordpress.ts +138 -0
- package/src/preview/github.test.ts +249 -0
- package/src/preview/github.ts +297 -0
- package/src/preview/index.ts +37 -0
- package/src/preview/manager.test.ts +440 -0
- package/src/preview/manager.ts +326 -0
- package/src/preview/notifications.test.ts +582 -0
- package/src/preview/notifications.ts +341 -0
- package/src/queue/batch-processing.ts +402 -0
- package/src/queue/dlq-monitoring.ts +402 -0
- package/src/queue/fifo.ts +342 -0
- package/src/queue/index.ts +9 -0
- package/src/queue/management.ts +428 -0
- package/src/queue/queue.test.ts +429 -0
- package/src/resource-mgmt/index.ts +39 -0
- package/src/resource-naming.ts +62 -0
- package/src/s3/index.ts +523 -0
- package/src/schema/cloud-config.schema.json +554 -0
- package/src/schema/index.ts +68 -0
- package/src/security/certificate-manager.ts +492 -0
- package/src/security/index.ts +9 -0
- package/src/security/scanning.ts +545 -0
- package/src/security/secrets-manager.ts +476 -0
- package/src/security/secrets-rotation.ts +456 -0
- package/src/security/security.test.ts +738 -0
- package/src/sms/advanced/ab-testing.ts +389 -0
- package/src/sms/advanced/analytics.ts +336 -0
- package/src/sms/advanced/campaigns.ts +523 -0
- package/src/sms/advanced/chatbot.ts +224 -0
- package/src/sms/advanced/index.ts +10 -0
- package/src/sms/advanced/link-tracking.ts +248 -0
- package/src/sms/advanced/mms.ts +308 -0
- package/src/sms/handlers/__tests__/send.test.ts +40 -0
- package/src/sms/handlers/delivery-status.ts +133 -0
- package/src/sms/handlers/receive.ts +162 -0
- package/src/sms/handlers/send.ts +174 -0
- package/src/sms/index.ts +9 -0
- package/src/stack-diff.ts +389 -0
- package/src/static-site/index.ts +85 -0
- package/src/template-builder.ts +110 -0
- package/src/template-validator.ts +574 -0
- package/src/utils/cache.ts +291 -0
- package/src/utils/diff.ts +269 -0
- package/src/utils/hash.ts +227 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/parallel.ts +294 -0
- package/src/validators/credentials.test.ts +274 -0
- package/src/validators/credentials.ts +233 -0
- package/src/validators/quotas.test.ts +434 -0
- package/src/validators/quotas.ts +217 -0
- package/test/ai.test.ts +327 -0
- package/test/api.test.ts +511 -0
- package/test/auth.test.ts +632 -0
- package/test/cache.test.ts +406 -0
- package/test/cdn.test.ts +247 -0
- package/test/compute.test.ts +861 -0
- package/test/database.test.ts +523 -0
- package/test/deployment.test.ts +499 -0
- package/test/dns.test.ts +270 -0
- package/test/email.test.ts +439 -0
- package/test/filesystem.test.ts +382 -0
- package/test/integration.test.ts +350 -0
- package/test/messaging.test.ts +514 -0
- package/test/monitoring.test.ts +634 -0
- package/test/network.test.ts +425 -0
- package/test/permissions.test.ts +488 -0
- package/test/queue.test.ts +484 -0
- package/test/registry.test.ts +306 -0
- package/test/security.test.ts +462 -0
- package/test/storage.test.ts +463 -0
- package/test/template-validator.test.ts +559 -0
- package/test/workflow.test.ts +592 -0
- package/tsconfig.json +16 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SES Bounce and Complaint Handling
|
|
3
|
+
* Automated bounce processing and reputation management
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface BounceEvent {
|
|
7
|
+
id: string
|
|
8
|
+
timestamp: Date
|
|
9
|
+
messageId: string
|
|
10
|
+
recipient: string
|
|
11
|
+
bounceType: 'Permanent' | 'Transient' | 'Undetermined'
|
|
12
|
+
bounceSubType: string
|
|
13
|
+
diagnosticCode?: string
|
|
14
|
+
feedbackId: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ComplaintEvent {
|
|
18
|
+
id: string
|
|
19
|
+
timestamp: Date
|
|
20
|
+
messageId: string
|
|
21
|
+
recipients: string[]
|
|
22
|
+
complaintFeedbackType?: string
|
|
23
|
+
userAgent?: string
|
|
24
|
+
feedbackId: string
|
|
25
|
+
arrivalDate?: Date
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface BounceHandler {
|
|
29
|
+
id: string
|
|
30
|
+
name: string
|
|
31
|
+
bounceThreshold: number
|
|
32
|
+
complaintThreshold: number
|
|
33
|
+
suppressionDuration: number // seconds
|
|
34
|
+
autoSuppress: boolean
|
|
35
|
+
notificationTopicArn?: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface SuppressionListEntry {
|
|
39
|
+
id: string
|
|
40
|
+
emailAddress: string
|
|
41
|
+
reason: 'BOUNCE' | 'COMPLAINT'
|
|
42
|
+
suppressedAt: Date
|
|
43
|
+
expiresAt?: Date
|
|
44
|
+
bounceCount?: number
|
|
45
|
+
complaintCount?: number
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface ReputationMetrics {
|
|
49
|
+
id: string
|
|
50
|
+
timestamp: Date
|
|
51
|
+
bounceRate: number
|
|
52
|
+
complaintRate: number
|
|
53
|
+
sendingQuota: number
|
|
54
|
+
maxSendRate: number
|
|
55
|
+
sentLast24Hours: number
|
|
56
|
+
reputationStatus: 'Good' | 'Warning' | 'Probation' | 'Shutdown'
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Bounce and complaint handler
|
|
61
|
+
*/
|
|
62
|
+
export class BounceComplaintHandler {
|
|
63
|
+
private bounces: Map<string, BounceEvent> = new Map()
|
|
64
|
+
private complaints: Map<string, ComplaintEvent> = new Map()
|
|
65
|
+
private handlers: Map<string, BounceHandler> = new Map()
|
|
66
|
+
private suppressionList: Map<string, SuppressionListEntry> = new Map()
|
|
67
|
+
private metrics: Map<string, ReputationMetrics> = new Map()
|
|
68
|
+
private bounceCounter = 0
|
|
69
|
+
private complaintCounter = 0
|
|
70
|
+
private handlerCounter = 0
|
|
71
|
+
private suppressionCounter = 0
|
|
72
|
+
private metricsCounter = 0
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Record bounce event
|
|
76
|
+
*/
|
|
77
|
+
recordBounce(bounce: Omit<BounceEvent, 'id'>): BounceEvent {
|
|
78
|
+
const id = `bounce-${Date.now()}-${this.bounceCounter++}`
|
|
79
|
+
|
|
80
|
+
const bounceEvent: BounceEvent = {
|
|
81
|
+
id,
|
|
82
|
+
...bounce,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
this.bounces.set(id, bounceEvent)
|
|
86
|
+
|
|
87
|
+
// Auto-suppress if permanent bounce
|
|
88
|
+
if (bounce.bounceType === 'Permanent') {
|
|
89
|
+
this.addToSuppressionList({
|
|
90
|
+
emailAddress: bounce.recipient,
|
|
91
|
+
reason: 'BOUNCE',
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return bounceEvent
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Record complaint event
|
|
100
|
+
*/
|
|
101
|
+
recordComplaint(complaint: Omit<ComplaintEvent, 'id'>): ComplaintEvent {
|
|
102
|
+
const id = `complaint-${Date.now()}-${this.complaintCounter++}`
|
|
103
|
+
|
|
104
|
+
const complaintEvent: ComplaintEvent = {
|
|
105
|
+
id,
|
|
106
|
+
...complaint,
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
this.complaints.set(id, complaintEvent)
|
|
110
|
+
|
|
111
|
+
// Auto-suppress all recipients
|
|
112
|
+
for (const recipient of complaint.recipients) {
|
|
113
|
+
this.addToSuppressionList({
|
|
114
|
+
emailAddress: recipient,
|
|
115
|
+
reason: 'COMPLAINT',
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return complaintEvent
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Create bounce handler
|
|
124
|
+
*/
|
|
125
|
+
createBounceHandler(handler: Omit<BounceHandler, 'id'>): BounceHandler {
|
|
126
|
+
const id = `handler-${Date.now()}-${this.handlerCounter++}`
|
|
127
|
+
|
|
128
|
+
const bounceHandler: BounceHandler = {
|
|
129
|
+
id,
|
|
130
|
+
...handler,
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
this.handlers.set(id, bounceHandler)
|
|
134
|
+
|
|
135
|
+
return bounceHandler
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Create automatic bounce handler
|
|
140
|
+
*/
|
|
141
|
+
createAutomaticBounceHandler(options: {
|
|
142
|
+
name: string
|
|
143
|
+
notificationTopicArn: string
|
|
144
|
+
}): BounceHandler {
|
|
145
|
+
return this.createBounceHandler({
|
|
146
|
+
name: options.name,
|
|
147
|
+
bounceThreshold: 5, // 5% bounce rate
|
|
148
|
+
complaintThreshold: 0.1, // 0.1% complaint rate
|
|
149
|
+
suppressionDuration: 2592000, // 30 days
|
|
150
|
+
autoSuppress: true,
|
|
151
|
+
notificationTopicArn: options.notificationTopicArn,
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Add to suppression list
|
|
157
|
+
*/
|
|
158
|
+
addToSuppressionList(entry: {
|
|
159
|
+
emailAddress: string
|
|
160
|
+
reason: 'BOUNCE' | 'COMPLAINT'
|
|
161
|
+
expirationDays?: number
|
|
162
|
+
}): SuppressionListEntry {
|
|
163
|
+
const id = `suppression-${Date.now()}-${this.suppressionCounter++}`
|
|
164
|
+
|
|
165
|
+
const expiresAt = entry.expirationDays
|
|
166
|
+
? new Date(Date.now() + entry.expirationDays * 24 * 60 * 60 * 1000)
|
|
167
|
+
: undefined
|
|
168
|
+
|
|
169
|
+
const suppressionEntry: SuppressionListEntry = {
|
|
170
|
+
id,
|
|
171
|
+
emailAddress: entry.emailAddress,
|
|
172
|
+
reason: entry.reason,
|
|
173
|
+
suppressedAt: new Date(),
|
|
174
|
+
expiresAt,
|
|
175
|
+
bounceCount: entry.reason === 'BOUNCE' ? 1 : 0,
|
|
176
|
+
complaintCount: entry.reason === 'COMPLAINT' ? 1 : 0,
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
this.suppressionList.set(entry.emailAddress, suppressionEntry)
|
|
180
|
+
|
|
181
|
+
return suppressionEntry
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Remove from suppression list
|
|
186
|
+
*/
|
|
187
|
+
removeFromSuppressionList(emailAddress: string): boolean {
|
|
188
|
+
return this.suppressionList.delete(emailAddress)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Check if email is suppressed
|
|
193
|
+
*/
|
|
194
|
+
isSuppressed(emailAddress: string): boolean {
|
|
195
|
+
const entry = this.suppressionList.get(emailAddress)
|
|
196
|
+
|
|
197
|
+
if (!entry) {
|
|
198
|
+
return false
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Check if expired
|
|
202
|
+
if (entry.expiresAt && entry.expiresAt < new Date()) {
|
|
203
|
+
this.suppressionList.delete(emailAddress)
|
|
204
|
+
return false
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return true
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Get bounce statistics
|
|
212
|
+
*/
|
|
213
|
+
getBounceStatistics(startDate: Date, endDate: Date): {
|
|
214
|
+
totalBounces: number
|
|
215
|
+
permanentBounces: number
|
|
216
|
+
transientBounces: number
|
|
217
|
+
bounceRate: number
|
|
218
|
+
topRecipients: Array<{ email: string; count: number }>
|
|
219
|
+
} {
|
|
220
|
+
const bounces = Array.from(this.bounces.values()).filter(
|
|
221
|
+
b => b.timestamp >= startDate && b.timestamp <= endDate
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
const totalBounces = bounces.length
|
|
225
|
+
const permanentBounces = bounces.filter(b => b.bounceType === 'Permanent').length
|
|
226
|
+
const transientBounces = bounces.filter(b => b.bounceType === 'Transient').length
|
|
227
|
+
|
|
228
|
+
// Calculate bounce rate (would need sent count in real implementation)
|
|
229
|
+
const estimatedSent = totalBounces * 10 // Placeholder
|
|
230
|
+
const bounceRate = (totalBounces / estimatedSent) * 100
|
|
231
|
+
|
|
232
|
+
// Top recipients by bounce count
|
|
233
|
+
const recipientCounts = new Map<string, number>()
|
|
234
|
+
for (const bounce of bounces) {
|
|
235
|
+
recipientCounts.set(bounce.recipient, (recipientCounts.get(bounce.recipient) || 0) + 1)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const topRecipients = Array.from(recipientCounts.entries())
|
|
239
|
+
.map(([email, count]) => ({ email, count }))
|
|
240
|
+
.sort((a, b) => b.count - a.count)
|
|
241
|
+
.slice(0, 10)
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
totalBounces,
|
|
245
|
+
permanentBounces,
|
|
246
|
+
transientBounces,
|
|
247
|
+
bounceRate,
|
|
248
|
+
topRecipients,
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Get complaint statistics
|
|
254
|
+
*/
|
|
255
|
+
getComplaintStatistics(startDate: Date, endDate: Date): {
|
|
256
|
+
totalComplaints: number
|
|
257
|
+
uniqueComplainters: number
|
|
258
|
+
complaintRate: number
|
|
259
|
+
} {
|
|
260
|
+
const complaints = Array.from(this.complaints.values()).filter(
|
|
261
|
+
c => c.timestamp >= startDate && c.timestamp <= endDate
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
const totalComplaints = complaints.length
|
|
265
|
+
const uniqueEmails = new Set<string>()
|
|
266
|
+
|
|
267
|
+
for (const complaint of complaints) {
|
|
268
|
+
for (const recipient of complaint.recipients) {
|
|
269
|
+
uniqueEmails.add(recipient)
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const uniqueComplainters = uniqueEmails.size
|
|
274
|
+
|
|
275
|
+
// Calculate complaint rate (would need sent count in real implementation)
|
|
276
|
+
const estimatedSent = totalComplaints * 1000 // Placeholder
|
|
277
|
+
const complaintRate = (totalComplaints / estimatedSent) * 100
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
totalComplaints,
|
|
281
|
+
uniqueComplainters,
|
|
282
|
+
complaintRate,
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Calculate reputation metrics
|
|
288
|
+
*/
|
|
289
|
+
calculateReputationMetrics(): ReputationMetrics {
|
|
290
|
+
const id = `metrics-${Date.now()}-${this.metricsCounter++}`
|
|
291
|
+
|
|
292
|
+
const now = new Date()
|
|
293
|
+
const last24Hours = new Date(now.getTime() - 24 * 60 * 60 * 1000)
|
|
294
|
+
|
|
295
|
+
const stats = this.getBounceStatistics(last24Hours, now)
|
|
296
|
+
const complaintStats = this.getComplaintStatistics(last24Hours, now)
|
|
297
|
+
|
|
298
|
+
let reputationStatus: 'Good' | 'Warning' | 'Probation' | 'Shutdown'
|
|
299
|
+
|
|
300
|
+
if (stats.bounceRate > 10 || complaintStats.complaintRate > 0.5) {
|
|
301
|
+
reputationStatus = 'Shutdown'
|
|
302
|
+
} else if (stats.bounceRate > 5 || complaintStats.complaintRate > 0.1) {
|
|
303
|
+
reputationStatus = 'Probation'
|
|
304
|
+
} else if (stats.bounceRate > 2 || complaintStats.complaintRate > 0.05) {
|
|
305
|
+
reputationStatus = 'Warning'
|
|
306
|
+
} else {
|
|
307
|
+
reputationStatus = 'Good'
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const metrics: ReputationMetrics = {
|
|
311
|
+
id,
|
|
312
|
+
timestamp: now,
|
|
313
|
+
bounceRate: stats.bounceRate,
|
|
314
|
+
complaintRate: complaintStats.complaintRate,
|
|
315
|
+
sendingQuota: 50000,
|
|
316
|
+
maxSendRate: 14,
|
|
317
|
+
sentLast24Hours: stats.totalBounces * 10, // Placeholder
|
|
318
|
+
reputationStatus,
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
this.metrics.set(id, metrics)
|
|
322
|
+
|
|
323
|
+
return metrics
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Get bounce events
|
|
328
|
+
*/
|
|
329
|
+
getBounces(messageId?: string): BounceEvent[] {
|
|
330
|
+
const bounces = Array.from(this.bounces.values())
|
|
331
|
+
return messageId ? bounces.filter(b => b.messageId === messageId) : bounces
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Get complaint events
|
|
336
|
+
*/
|
|
337
|
+
getComplaints(messageId?: string): ComplaintEvent[] {
|
|
338
|
+
const complaints = Array.from(this.complaints.values())
|
|
339
|
+
return messageId ? complaints.filter(c => c.messageId === messageId) : complaints
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* List suppression list
|
|
344
|
+
*/
|
|
345
|
+
listSuppressionList(reason?: 'BOUNCE' | 'COMPLAINT'): SuppressionListEntry[] {
|
|
346
|
+
const entries = Array.from(this.suppressionList.values())
|
|
347
|
+
return reason ? entries.filter(e => e.reason === reason) : entries
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Generate CloudFormation for SNS topic subscription
|
|
352
|
+
*/
|
|
353
|
+
generateSNSSubscriptionCF(options: {
|
|
354
|
+
topicArn: string
|
|
355
|
+
endpoint: string
|
|
356
|
+
protocol: 'email' | 'email-json' | 'http' | 'https' | 'sqs' | 'lambda'
|
|
357
|
+
}): any {
|
|
358
|
+
return {
|
|
359
|
+
Type: 'AWS::SNS::Subscription',
|
|
360
|
+
Properties: {
|
|
361
|
+
TopicArn: options.topicArn,
|
|
362
|
+
Endpoint: options.endpoint,
|
|
363
|
+
Protocol: options.protocol,
|
|
364
|
+
},
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Generate CloudFormation for SES configuration set event destination
|
|
370
|
+
*/
|
|
371
|
+
generateEventDestinationCF(options: {
|
|
372
|
+
configurationSetName: string
|
|
373
|
+
eventDestinationName: string
|
|
374
|
+
eventTypes: Array<'send' | 'reject' | 'bounce' | 'complaint' | 'delivery' | 'open' | 'click' | 'renderingFailure'>
|
|
375
|
+
snsTopicArn?: string
|
|
376
|
+
cloudWatchDestination?: {
|
|
377
|
+
dimensionConfigurations: Array<{
|
|
378
|
+
dimensionName: string
|
|
379
|
+
dimensionValueSource: 'messageTag' | 'emailHeader' | 'linkTag'
|
|
380
|
+
defaultDimensionValue: string
|
|
381
|
+
}>
|
|
382
|
+
}
|
|
383
|
+
}): any {
|
|
384
|
+
return {
|
|
385
|
+
Type: 'AWS::SES::ConfigurationSetEventDestination',
|
|
386
|
+
Properties: {
|
|
387
|
+
ConfigurationSetName: options.configurationSetName,
|
|
388
|
+
EventDestination: {
|
|
389
|
+
Name: options.eventDestinationName,
|
|
390
|
+
Enabled: true,
|
|
391
|
+
MatchingEventTypes: options.eventTypes,
|
|
392
|
+
...(options.snsTopicArn && {
|
|
393
|
+
SnsDestination: {
|
|
394
|
+
TopicARN: options.snsTopicArn,
|
|
395
|
+
},
|
|
396
|
+
}),
|
|
397
|
+
...(options.cloudWatchDestination && {
|
|
398
|
+
CloudWatchDestination: options.cloudWatchDestination,
|
|
399
|
+
}),
|
|
400
|
+
},
|
|
401
|
+
},
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Clear all data
|
|
407
|
+
*/
|
|
408
|
+
clear(): void {
|
|
409
|
+
this.bounces.clear()
|
|
410
|
+
this.complaints.clear()
|
|
411
|
+
this.handlers.clear()
|
|
412
|
+
this.suppressionList.clear()
|
|
413
|
+
this.metrics.clear()
|
|
414
|
+
this.bounceCounter = 0
|
|
415
|
+
this.complaintCounter = 0
|
|
416
|
+
this.handlerCounter = 0
|
|
417
|
+
this.suppressionCounter = 0
|
|
418
|
+
this.metricsCounter = 0
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Global bounce and complaint handler instance
|
|
424
|
+
*/
|
|
425
|
+
export const bounceComplaintHandler: BounceComplaintHandler = new BounceComplaintHandler()
|