@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,465 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email Rules and Automation
|
|
3
|
+
*
|
|
4
|
+
* Provides email filtering, routing, and automation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface EmailRule {
|
|
8
|
+
id: string
|
|
9
|
+
name: string
|
|
10
|
+
enabled: boolean
|
|
11
|
+
priority: number
|
|
12
|
+
conditions: RuleCondition[]
|
|
13
|
+
conditionOperator: 'and' | 'or'
|
|
14
|
+
actions: RuleAction[]
|
|
15
|
+
createdAt: string
|
|
16
|
+
updatedAt: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface RuleCondition {
|
|
20
|
+
field: 'from' | 'to' | 'subject' | 'body' | 'headers' | 'attachments' | 'size'
|
|
21
|
+
operator: 'contains' | 'not-contains' | 'equals' | 'not-equals' | 'starts-with' | 'ends-with' | 'regex' | 'greater-than' | 'less-than'
|
|
22
|
+
value: string
|
|
23
|
+
caseSensitive?: boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface RuleAction {
|
|
27
|
+
type: 'move' | 'copy' | 'delete' | 'label' | 'forward' | 'reply' | 'mark-read' | 'mark-starred' | 'webhook' | 'lambda'
|
|
28
|
+
params: Record<string, any>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface AutomationWorkflow {
|
|
32
|
+
id: string
|
|
33
|
+
name: string
|
|
34
|
+
trigger: WorkflowTrigger
|
|
35
|
+
steps: WorkflowStep[]
|
|
36
|
+
enabled: boolean
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface WorkflowTrigger {
|
|
40
|
+
type: 'email-received' | 'email-sent' | 'schedule' | 'webhook'
|
|
41
|
+
conditions?: RuleCondition[]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface WorkflowStep {
|
|
45
|
+
id: string
|
|
46
|
+
type: 'delay' | 'condition' | 'action' | 'loop'
|
|
47
|
+
config: Record<string, any>
|
|
48
|
+
next?: string
|
|
49
|
+
onTrue?: string
|
|
50
|
+
onFalse?: string
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Email Rules Module
|
|
55
|
+
*/
|
|
56
|
+
export class EmailRules {
|
|
57
|
+
/**
|
|
58
|
+
* Lambda code for rule processing
|
|
59
|
+
*/
|
|
60
|
+
static RuleProcessorCode = `
|
|
61
|
+
const { S3Client, GetObjectCommand, PutObjectCommand, CopyObjectCommand, DeleteObjectCommand } = require('@aws-sdk/client-s3');
|
|
62
|
+
const { DynamoDBClient, QueryCommand } = require('@aws-sdk/client-dynamodb');
|
|
63
|
+
const { LambdaClient, InvokeCommand } = require('@aws-sdk/client-lambda');
|
|
64
|
+
const { SESClient, SendRawEmailCommand } = require('@aws-sdk/client-ses');
|
|
65
|
+
|
|
66
|
+
const s3 = new S3Client({});
|
|
67
|
+
const dynamodb = new DynamoDBClient({});
|
|
68
|
+
const lambda = new LambdaClient({});
|
|
69
|
+
const ses = new SESClient({});
|
|
70
|
+
|
|
71
|
+
const EMAIL_BUCKET = process.env.EMAIL_BUCKET;
|
|
72
|
+
const RULES_TABLE = process.env.RULES_TABLE;
|
|
73
|
+
|
|
74
|
+
exports.handler = async (event) => {
|
|
75
|
+
console.log('Rule processor event:', JSON.stringify(event, null, 2));
|
|
76
|
+
|
|
77
|
+
for (const record of event.Records) {
|
|
78
|
+
try {
|
|
79
|
+
const bucket = record.s3?.bucket?.name || EMAIL_BUCKET;
|
|
80
|
+
const key = decodeURIComponent(record.s3?.object?.key?.replace(/\\+/g, ' ') || '');
|
|
81
|
+
|
|
82
|
+
if (!key.endsWith('/metadata.json')) continue;
|
|
83
|
+
|
|
84
|
+
// Get email metadata
|
|
85
|
+
const result = await s3.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
|
|
86
|
+
const metadata = JSON.parse(await result.Body.transformToString());
|
|
87
|
+
|
|
88
|
+
// Get mailbox path
|
|
89
|
+
const pathParts = key.split('/');
|
|
90
|
+
const domain = pathParts[1];
|
|
91
|
+
const localPart = pathParts[2];
|
|
92
|
+
const mailbox = \`\${localPart}@\${domain}\`;
|
|
93
|
+
|
|
94
|
+
// Get rules for this mailbox
|
|
95
|
+
const rules = await getRules(mailbox);
|
|
96
|
+
|
|
97
|
+
// Process rules in priority order
|
|
98
|
+
for (const rule of rules) {
|
|
99
|
+
if (!rule.enabled) continue;
|
|
100
|
+
|
|
101
|
+
const matches = evaluateConditions(metadata, rule.conditions, rule.conditionOperator);
|
|
102
|
+
if (!matches) continue;
|
|
103
|
+
|
|
104
|
+
console.log(\`Rule matched: \${rule.name}\`);
|
|
105
|
+
|
|
106
|
+
// Execute actions
|
|
107
|
+
for (const action of rule.actions) {
|
|
108
|
+
await executeAction(action, metadata, bucket, key, mailbox);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Stop processing if rule says so
|
|
112
|
+
if (rule.stopProcessing) break;
|
|
113
|
+
}
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.error('Error processing rules:', error);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return { statusCode: 200 };
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
async function getRules(mailbox) {
|
|
123
|
+
const result = await dynamodb.send(new QueryCommand({
|
|
124
|
+
TableName: RULES_TABLE,
|
|
125
|
+
KeyConditionExpression: 'mailbox = :mailbox',
|
|
126
|
+
ExpressionAttributeValues: {
|
|
127
|
+
':mailbox': { S: mailbox },
|
|
128
|
+
},
|
|
129
|
+
}));
|
|
130
|
+
|
|
131
|
+
const rules = (result.Items || []).map(item => ({
|
|
132
|
+
id: item.id.S,
|
|
133
|
+
name: item.name.S,
|
|
134
|
+
enabled: item.enabled?.BOOL ?? true,
|
|
135
|
+
priority: parseInt(item.priority?.N || '0'),
|
|
136
|
+
conditions: JSON.parse(item.conditions?.S || '[]'),
|
|
137
|
+
conditionOperator: item.conditionOperator?.S || 'and',
|
|
138
|
+
actions: JSON.parse(item.actions?.S || '[]'),
|
|
139
|
+
stopProcessing: item.stopProcessing?.BOOL ?? false,
|
|
140
|
+
}));
|
|
141
|
+
|
|
142
|
+
// Sort by priority
|
|
143
|
+
rules.sort((a, b) => a.priority - b.priority);
|
|
144
|
+
|
|
145
|
+
return rules;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function evaluateConditions(metadata, conditions, operator) {
|
|
149
|
+
if (!conditions || conditions.length === 0) return true;
|
|
150
|
+
|
|
151
|
+
const results = conditions.map(condition => evaluateCondition(metadata, condition));
|
|
152
|
+
|
|
153
|
+
if (operator === 'or') {
|
|
154
|
+
return results.some(r => r);
|
|
155
|
+
}
|
|
156
|
+
return results.every(r => r);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function evaluateCondition(metadata, condition) {
|
|
160
|
+
let value = '';
|
|
161
|
+
|
|
162
|
+
switch (condition.field) {
|
|
163
|
+
case 'from':
|
|
164
|
+
value = metadata.from || '';
|
|
165
|
+
break;
|
|
166
|
+
case 'to':
|
|
167
|
+
value = metadata.to || '';
|
|
168
|
+
break;
|
|
169
|
+
case 'subject':
|
|
170
|
+
value = metadata.subject || '';
|
|
171
|
+
break;
|
|
172
|
+
case 'body':
|
|
173
|
+
value = metadata.preview || '';
|
|
174
|
+
break;
|
|
175
|
+
case 'size':
|
|
176
|
+
value = String(metadata.size || 0);
|
|
177
|
+
break;
|
|
178
|
+
default:
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const compareValue = condition.caseSensitive ? value : value.toLowerCase();
|
|
183
|
+
const conditionValue = condition.caseSensitive ? condition.value : condition.value.toLowerCase();
|
|
184
|
+
|
|
185
|
+
switch (condition.operator) {
|
|
186
|
+
case 'contains':
|
|
187
|
+
return compareValue.includes(conditionValue);
|
|
188
|
+
case 'not-contains':
|
|
189
|
+
return !compareValue.includes(conditionValue);
|
|
190
|
+
case 'equals':
|
|
191
|
+
return compareValue === conditionValue;
|
|
192
|
+
case 'not-equals':
|
|
193
|
+
return compareValue !== conditionValue;
|
|
194
|
+
case 'starts-with':
|
|
195
|
+
return compareValue.startsWith(conditionValue);
|
|
196
|
+
case 'ends-with':
|
|
197
|
+
return compareValue.endsWith(conditionValue);
|
|
198
|
+
case 'regex':
|
|
199
|
+
try {
|
|
200
|
+
const regex = new RegExp(condition.value, condition.caseSensitive ? '' : 'i');
|
|
201
|
+
return regex.test(value);
|
|
202
|
+
} catch {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
case 'greater-than':
|
|
206
|
+
return parseFloat(value) > parseFloat(condition.value);
|
|
207
|
+
case 'less-than':
|
|
208
|
+
return parseFloat(value) < parseFloat(condition.value);
|
|
209
|
+
default:
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function executeAction(action, metadata, bucket, key, mailbox) {
|
|
215
|
+
const basePath = key.replace('/metadata.json', '');
|
|
216
|
+
|
|
217
|
+
switch (action.type) {
|
|
218
|
+
case 'move':
|
|
219
|
+
await moveEmail(bucket, basePath, action.params.folder, mailbox);
|
|
220
|
+
break;
|
|
221
|
+
|
|
222
|
+
case 'copy':
|
|
223
|
+
await copyEmail(bucket, basePath, action.params.folder, mailbox);
|
|
224
|
+
break;
|
|
225
|
+
|
|
226
|
+
case 'delete':
|
|
227
|
+
await deleteEmail(bucket, basePath);
|
|
228
|
+
break;
|
|
229
|
+
|
|
230
|
+
case 'label':
|
|
231
|
+
await addLabel(bucket, key, metadata, action.params.label);
|
|
232
|
+
break;
|
|
233
|
+
|
|
234
|
+
case 'forward':
|
|
235
|
+
await forwardEmail(bucket, basePath, metadata, action.params.to);
|
|
236
|
+
break;
|
|
237
|
+
|
|
238
|
+
case 'mark-read':
|
|
239
|
+
await markAsRead(bucket, key, metadata);
|
|
240
|
+
break;
|
|
241
|
+
|
|
242
|
+
case 'mark-starred':
|
|
243
|
+
await markAsStarred(bucket, key, metadata);
|
|
244
|
+
break;
|
|
245
|
+
|
|
246
|
+
case 'webhook':
|
|
247
|
+
await callWebhook(action.params.url, metadata);
|
|
248
|
+
break;
|
|
249
|
+
|
|
250
|
+
case 'lambda':
|
|
251
|
+
await invokeLambda(action.params.functionName, metadata);
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function moveEmail(bucket, basePath, folder, mailbox) {
|
|
257
|
+
const [localPart, domain] = mailbox.split('@');
|
|
258
|
+
const newBasePath = \`mailboxes/\${domain}/\${localPart}/\${folder}/\${basePath.split('/').pop()}\`;
|
|
259
|
+
|
|
260
|
+
// Copy all files
|
|
261
|
+
const files = ['metadata.json', 'raw.eml', 'body.html', 'body.txt', 'preview.txt'];
|
|
262
|
+
for (const file of files) {
|
|
263
|
+
try {
|
|
264
|
+
await s3.send(new CopyObjectCommand({
|
|
265
|
+
Bucket: bucket,
|
|
266
|
+
CopySource: \`\${bucket}/\${basePath}/\${file}\`,
|
|
267
|
+
Key: \`\${newBasePath}/\${file}\`,
|
|
268
|
+
}));
|
|
269
|
+
await s3.send(new DeleteObjectCommand({
|
|
270
|
+
Bucket: bucket,
|
|
271
|
+
Key: \`\${basePath}/\${file}\`,
|
|
272
|
+
}));
|
|
273
|
+
} catch {}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async function copyEmail(bucket, basePath, folder, mailbox) {
|
|
278
|
+
const [localPart, domain] = mailbox.split('@');
|
|
279
|
+
const newBasePath = \`mailboxes/\${domain}/\${localPart}/\${folder}/\${basePath.split('/').pop()}\`;
|
|
280
|
+
|
|
281
|
+
const files = ['metadata.json', 'raw.eml', 'body.html', 'body.txt', 'preview.txt'];
|
|
282
|
+
for (const file of files) {
|
|
283
|
+
try {
|
|
284
|
+
await s3.send(new CopyObjectCommand({
|
|
285
|
+
Bucket: bucket,
|
|
286
|
+
CopySource: \`\${bucket}/\${basePath}/\${file}\`,
|
|
287
|
+
Key: \`\${newBasePath}/\${file}\`,
|
|
288
|
+
}));
|
|
289
|
+
} catch {}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async function deleteEmail(bucket, basePath) {
|
|
294
|
+
const files = ['metadata.json', 'raw.eml', 'body.html', 'body.txt', 'preview.txt'];
|
|
295
|
+
for (const file of files) {
|
|
296
|
+
try {
|
|
297
|
+
await s3.send(new DeleteObjectCommand({
|
|
298
|
+
Bucket: bucket,
|
|
299
|
+
Key: \`\${basePath}/\${file}\`,
|
|
300
|
+
}));
|
|
301
|
+
} catch {}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async function addLabel(bucket, key, metadata, label) {
|
|
306
|
+
metadata.labels = metadata.labels || [];
|
|
307
|
+
if (!metadata.labels.includes(label)) {
|
|
308
|
+
metadata.labels.push(label);
|
|
309
|
+
await s3.send(new PutObjectCommand({
|
|
310
|
+
Bucket: bucket,
|
|
311
|
+
Key: key,
|
|
312
|
+
Body: JSON.stringify(metadata, null, 2),
|
|
313
|
+
ContentType: 'application/json',
|
|
314
|
+
}));
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async function forwardEmail(bucket, basePath, metadata, to) {
|
|
319
|
+
const rawResult = await s3.send(new GetObjectCommand({
|
|
320
|
+
Bucket: bucket,
|
|
321
|
+
Key: \`\${basePath}/raw.eml\`,
|
|
322
|
+
}));
|
|
323
|
+
const rawEmail = await rawResult.Body.transformToString();
|
|
324
|
+
|
|
325
|
+
// Modify headers for forwarding
|
|
326
|
+
const forwardedEmail = \`From: \${metadata.to}\\r\\nTo: \${to}\\r\\nSubject: Fwd: \${metadata.subject}\\r\\n\` + rawEmail.split('\\r\\n\\r\\n').slice(1).join('\\r\\n\\r\\n');
|
|
327
|
+
|
|
328
|
+
await ses.send(new SendRawEmailCommand({
|
|
329
|
+
RawMessage: { Data: Buffer.from(forwardedEmail) },
|
|
330
|
+
}));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async function markAsRead(bucket, key, metadata) {
|
|
334
|
+
metadata.isRead = true;
|
|
335
|
+
await s3.send(new PutObjectCommand({
|
|
336
|
+
Bucket: bucket,
|
|
337
|
+
Key: key,
|
|
338
|
+
Body: JSON.stringify(metadata, null, 2),
|
|
339
|
+
ContentType: 'application/json',
|
|
340
|
+
}));
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async function markAsStarred(bucket, key, metadata) {
|
|
344
|
+
metadata.isStarred = true;
|
|
345
|
+
await s3.send(new PutObjectCommand({
|
|
346
|
+
Bucket: bucket,
|
|
347
|
+
Key: key,
|
|
348
|
+
Body: JSON.stringify(metadata, null, 2),
|
|
349
|
+
ContentType: 'application/json',
|
|
350
|
+
}));
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
async function callWebhook(url, metadata) {
|
|
354
|
+
await fetch(url, {
|
|
355
|
+
method: 'POST',
|
|
356
|
+
headers: { 'Content-Type': 'application/json' },
|
|
357
|
+
body: JSON.stringify({ event: 'email.rule.matched', data: metadata }),
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async function invokeLambda(functionName, metadata) {
|
|
362
|
+
await lambda.send(new InvokeCommand({
|
|
363
|
+
FunctionName: functionName,
|
|
364
|
+
InvocationType: 'Event',
|
|
365
|
+
Payload: JSON.stringify(metadata),
|
|
366
|
+
}));
|
|
367
|
+
}
|
|
368
|
+
`
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Create rules DynamoDB table
|
|
372
|
+
*/
|
|
373
|
+
static createRulesTable(config: { slug: string }): Record<string, any> {
|
|
374
|
+
return {
|
|
375
|
+
[`${config.slug}EmailRulesTable`]: {
|
|
376
|
+
Type: 'AWS::DynamoDB::Table',
|
|
377
|
+
Properties: {
|
|
378
|
+
TableName: `${config.slug}-email-rules`,
|
|
379
|
+
BillingMode: 'PAY_PER_REQUEST',
|
|
380
|
+
AttributeDefinitions: [
|
|
381
|
+
{ AttributeName: 'mailbox', AttributeType: 'S' },
|
|
382
|
+
{ AttributeName: 'id', AttributeType: 'S' },
|
|
383
|
+
],
|
|
384
|
+
KeySchema: [
|
|
385
|
+
{ AttributeName: 'mailbox', KeyType: 'HASH' },
|
|
386
|
+
{ AttributeName: 'id', KeyType: 'RANGE' },
|
|
387
|
+
],
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Create rule processor Lambda
|
|
395
|
+
*/
|
|
396
|
+
static createRuleProcessorLambda(config: {
|
|
397
|
+
slug: string
|
|
398
|
+
roleArn: string
|
|
399
|
+
emailBucket: string
|
|
400
|
+
rulesTable: string
|
|
401
|
+
}): Record<string, any> {
|
|
402
|
+
return {
|
|
403
|
+
[`${config.slug}EmailRuleProcessorLambda`]: {
|
|
404
|
+
Type: 'AWS::Lambda::Function',
|
|
405
|
+
Properties: {
|
|
406
|
+
FunctionName: `${config.slug}-email-rule-processor`,
|
|
407
|
+
Runtime: 'nodejs20.x',
|
|
408
|
+
Handler: 'index.handler',
|
|
409
|
+
Role: config.roleArn,
|
|
410
|
+
Timeout: 60,
|
|
411
|
+
MemorySize: 256,
|
|
412
|
+
Code: {
|
|
413
|
+
ZipFile: EmailRules.RuleProcessorCode,
|
|
414
|
+
},
|
|
415
|
+
Environment: {
|
|
416
|
+
Variables: {
|
|
417
|
+
EMAIL_BUCKET: config.emailBucket,
|
|
418
|
+
RULES_TABLE: config.rulesTable,
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Built-in rule templates
|
|
428
|
+
*/
|
|
429
|
+
static readonly RuleTemplates = {
|
|
430
|
+
spamFilter: {
|
|
431
|
+
name: 'Spam Filter',
|
|
432
|
+
conditions: [
|
|
433
|
+
{ field: 'subject', operator: 'regex', value: '(viagra|lottery|winner|prince|inheritance)', caseSensitive: false },
|
|
434
|
+
],
|
|
435
|
+
conditionOperator: 'or',
|
|
436
|
+
actions: [
|
|
437
|
+
{ type: 'move', params: { folder: 'spam' } },
|
|
438
|
+
{ type: 'mark-read', params: {} },
|
|
439
|
+
],
|
|
440
|
+
},
|
|
441
|
+
autoLabel: {
|
|
442
|
+
name: 'Auto Label Invoices',
|
|
443
|
+
conditions: [
|
|
444
|
+
{ field: 'subject', operator: 'contains', value: 'invoice', caseSensitive: false },
|
|
445
|
+
],
|
|
446
|
+
conditionOperator: 'and',
|
|
447
|
+
actions: [
|
|
448
|
+
{ type: 'label', params: { label: 'invoices' } },
|
|
449
|
+
],
|
|
450
|
+
},
|
|
451
|
+
forwardUrgent: {
|
|
452
|
+
name: 'Forward Urgent',
|
|
453
|
+
conditions: [
|
|
454
|
+
{ field: 'subject', operator: 'contains', value: 'urgent', caseSensitive: false },
|
|
455
|
+
],
|
|
456
|
+
conditionOperator: 'and',
|
|
457
|
+
actions: [
|
|
458
|
+
{ type: 'forward', params: { to: 'admin@example.com' } },
|
|
459
|
+
{ type: 'label', params: { label: 'urgent' } },
|
|
460
|
+
],
|
|
461
|
+
},
|
|
462
|
+
} as const
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
export default EmailRules
|