@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.
Files changed (251) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +321 -0
  3. package/package.json +31 -0
  4. package/src/advanced-features.test.ts +465 -0
  5. package/src/aws/cloudformation.ts +421 -0
  6. package/src/aws/cloudfront.ts +158 -0
  7. package/src/aws/credentials.test.ts +132 -0
  8. package/src/aws/credentials.ts +545 -0
  9. package/src/aws/index.ts +87 -0
  10. package/src/aws/s3.test.ts +188 -0
  11. package/src/aws/s3.ts +1088 -0
  12. package/src/aws/signature.test.ts +670 -0
  13. package/src/aws/signature.ts +1155 -0
  14. package/src/backup/disaster-recovery.test.ts +726 -0
  15. package/src/backup/disaster-recovery.ts +500 -0
  16. package/src/backup/index.ts +34 -0
  17. package/src/backup/manager.test.ts +498 -0
  18. package/src/backup/manager.ts +432 -0
  19. package/src/cicd/circleci.ts +430 -0
  20. package/src/cicd/github-actions.ts +424 -0
  21. package/src/cicd/gitlab-ci.ts +255 -0
  22. package/src/cicd/index.ts +8 -0
  23. package/src/cli/history.ts +396 -0
  24. package/src/cli/index.ts +10 -0
  25. package/src/cli/progress.ts +458 -0
  26. package/src/cli/repl.ts +454 -0
  27. package/src/cli/suggestions.ts +327 -0
  28. package/src/cli/table.test.ts +319 -0
  29. package/src/cli/table.ts +332 -0
  30. package/src/cloudformation/builder.test.ts +327 -0
  31. package/src/cloudformation/builder.ts +378 -0
  32. package/src/cloudformation/builders/api-gateway.ts +449 -0
  33. package/src/cloudformation/builders/cache.ts +334 -0
  34. package/src/cloudformation/builders/cdn.ts +278 -0
  35. package/src/cloudformation/builders/compute.ts +485 -0
  36. package/src/cloudformation/builders/database.ts +392 -0
  37. package/src/cloudformation/builders/functions.ts +343 -0
  38. package/src/cloudformation/builders/messaging.ts +140 -0
  39. package/src/cloudformation/builders/monitoring.ts +300 -0
  40. package/src/cloudformation/builders/network.ts +264 -0
  41. package/src/cloudformation/builders/queue.ts +147 -0
  42. package/src/cloudformation/builders/security.ts +399 -0
  43. package/src/cloudformation/builders/storage.ts +285 -0
  44. package/src/cloudformation/index.ts +30 -0
  45. package/src/cloudformation/types.ts +173 -0
  46. package/src/compliance/aws-config.ts +543 -0
  47. package/src/compliance/cloudtrail.ts +376 -0
  48. package/src/compliance/compliance.test.ts +423 -0
  49. package/src/compliance/guardduty.ts +446 -0
  50. package/src/compliance/index.ts +66 -0
  51. package/src/compliance/security-hub.ts +456 -0
  52. package/src/containers/build-optimization.ts +416 -0
  53. package/src/containers/containers.test.ts +508 -0
  54. package/src/containers/image-scanning.ts +360 -0
  55. package/src/containers/index.ts +9 -0
  56. package/src/containers/registry.ts +293 -0
  57. package/src/containers/service-mesh.ts +520 -0
  58. package/src/database/database.test.ts +762 -0
  59. package/src/database/index.ts +9 -0
  60. package/src/database/migrations.ts +444 -0
  61. package/src/database/performance.ts +528 -0
  62. package/src/database/replicas.ts +534 -0
  63. package/src/database/users.ts +494 -0
  64. package/src/dependency-graph.ts +143 -0
  65. package/src/deployment/ab-testing.ts +582 -0
  66. package/src/deployment/blue-green.ts +452 -0
  67. package/src/deployment/canary.ts +500 -0
  68. package/src/deployment/deployment.test.ts +526 -0
  69. package/src/deployment/index.ts +61 -0
  70. package/src/deployment/progressive.ts +62 -0
  71. package/src/dns/dns.test.ts +641 -0
  72. package/src/dns/dnssec.ts +315 -0
  73. package/src/dns/index.ts +8 -0
  74. package/src/dns/resolver.ts +496 -0
  75. package/src/dns/routing.ts +593 -0
  76. package/src/email/advanced/analytics.ts +445 -0
  77. package/src/email/advanced/index.ts +11 -0
  78. package/src/email/advanced/rules.ts +465 -0
  79. package/src/email/advanced/scheduling.ts +352 -0
  80. package/src/email/advanced/search.ts +412 -0
  81. package/src/email/advanced/shared-mailboxes.ts +404 -0
  82. package/src/email/advanced/templates.ts +455 -0
  83. package/src/email/advanced/threading.ts +281 -0
  84. package/src/email/analytics.ts +467 -0
  85. package/src/email/bounce-handling.ts +425 -0
  86. package/src/email/email.test.ts +431 -0
  87. package/src/email/handlers/__tests__/inbound.test.ts +38 -0
  88. package/src/email/handlers/__tests__/outbound.test.ts +37 -0
  89. package/src/email/handlers/converter.ts +227 -0
  90. package/src/email/handlers/feedback.ts +228 -0
  91. package/src/email/handlers/inbound.ts +169 -0
  92. package/src/email/handlers/outbound.ts +178 -0
  93. package/src/email/index.ts +15 -0
  94. package/src/email/reputation.ts +303 -0
  95. package/src/email/templates.ts +352 -0
  96. package/src/errors/index.test.ts +434 -0
  97. package/src/errors/index.ts +416 -0
  98. package/src/health-checks/index.ts +40 -0
  99. package/src/index.ts +360 -0
  100. package/src/intrinsic-functions.ts +118 -0
  101. package/src/lambda/concurrency.ts +330 -0
  102. package/src/lambda/destinations.ts +345 -0
  103. package/src/lambda/dlq.ts +425 -0
  104. package/src/lambda/index.ts +11 -0
  105. package/src/lambda/lambda.test.ts +840 -0
  106. package/src/lambda/layers.ts +263 -0
  107. package/src/lambda/versions.ts +376 -0
  108. package/src/lambda/vpc.ts +399 -0
  109. package/src/local/config.ts +114 -0
  110. package/src/local/index.ts +6 -0
  111. package/src/local/mock-aws.ts +351 -0
  112. package/src/modules/ai.ts +340 -0
  113. package/src/modules/api.ts +478 -0
  114. package/src/modules/auth.ts +805 -0
  115. package/src/modules/cache.ts +417 -0
  116. package/src/modules/cdn.ts +1062 -0
  117. package/src/modules/communication.ts +1094 -0
  118. package/src/modules/compute.ts +3348 -0
  119. package/src/modules/database.ts +554 -0
  120. package/src/modules/deployment.ts +1079 -0
  121. package/src/modules/dns.ts +337 -0
  122. package/src/modules/email.ts +1538 -0
  123. package/src/modules/filesystem.ts +515 -0
  124. package/src/modules/index.ts +32 -0
  125. package/src/modules/messaging.ts +486 -0
  126. package/src/modules/monitoring.ts +2086 -0
  127. package/src/modules/network.ts +664 -0
  128. package/src/modules/parameter-store.ts +325 -0
  129. package/src/modules/permissions.ts +1081 -0
  130. package/src/modules/phone.ts +494 -0
  131. package/src/modules/queue.ts +1260 -0
  132. package/src/modules/redirects.ts +464 -0
  133. package/src/modules/registry.ts +699 -0
  134. package/src/modules/search.ts +401 -0
  135. package/src/modules/secrets.ts +416 -0
  136. package/src/modules/security.ts +731 -0
  137. package/src/modules/sms.ts +389 -0
  138. package/src/modules/storage.ts +1120 -0
  139. package/src/modules/workflow.ts +680 -0
  140. package/src/multi-account/config.ts +521 -0
  141. package/src/multi-account/index.ts +7 -0
  142. package/src/multi-account/manager.ts +427 -0
  143. package/src/multi-region/cross-region.ts +410 -0
  144. package/src/multi-region/index.ts +8 -0
  145. package/src/multi-region/manager.ts +483 -0
  146. package/src/multi-region/regions.ts +435 -0
  147. package/src/network-security/index.ts +48 -0
  148. package/src/observability/index.ts +9 -0
  149. package/src/observability/logs.ts +522 -0
  150. package/src/observability/metrics.ts +460 -0
  151. package/src/observability/observability.test.ts +782 -0
  152. package/src/observability/synthetics.ts +568 -0
  153. package/src/observability/xray.ts +358 -0
  154. package/src/phone/advanced/analytics.ts +349 -0
  155. package/src/phone/advanced/callbacks.ts +428 -0
  156. package/src/phone/advanced/index.ts +8 -0
  157. package/src/phone/advanced/ivr-builder.ts +504 -0
  158. package/src/phone/advanced/recording.ts +310 -0
  159. package/src/phone/handlers/__tests__/incoming-call.test.ts +40 -0
  160. package/src/phone/handlers/incoming-call.ts +117 -0
  161. package/src/phone/handlers/missed-call.ts +116 -0
  162. package/src/phone/handlers/voicemail.ts +179 -0
  163. package/src/phone/index.ts +9 -0
  164. package/src/presets/api-backend.ts +134 -0
  165. package/src/presets/data-pipeline.ts +204 -0
  166. package/src/presets/extend.test.ts +295 -0
  167. package/src/presets/extend.ts +297 -0
  168. package/src/presets/fullstack-app.ts +144 -0
  169. package/src/presets/index.ts +27 -0
  170. package/src/presets/jamstack.ts +135 -0
  171. package/src/presets/microservices.ts +167 -0
  172. package/src/presets/ml-api.ts +208 -0
  173. package/src/presets/nodejs-server.ts +104 -0
  174. package/src/presets/nodejs-serverless.ts +114 -0
  175. package/src/presets/realtime-app.ts +184 -0
  176. package/src/presets/static-site.ts +64 -0
  177. package/src/presets/traditional-web-app.ts +339 -0
  178. package/src/presets/wordpress.ts +138 -0
  179. package/src/preview/github.test.ts +249 -0
  180. package/src/preview/github.ts +297 -0
  181. package/src/preview/index.ts +37 -0
  182. package/src/preview/manager.test.ts +440 -0
  183. package/src/preview/manager.ts +326 -0
  184. package/src/preview/notifications.test.ts +582 -0
  185. package/src/preview/notifications.ts +341 -0
  186. package/src/queue/batch-processing.ts +402 -0
  187. package/src/queue/dlq-monitoring.ts +402 -0
  188. package/src/queue/fifo.ts +342 -0
  189. package/src/queue/index.ts +9 -0
  190. package/src/queue/management.ts +428 -0
  191. package/src/queue/queue.test.ts +429 -0
  192. package/src/resource-mgmt/index.ts +39 -0
  193. package/src/resource-naming.ts +62 -0
  194. package/src/s3/index.ts +523 -0
  195. package/src/schema/cloud-config.schema.json +554 -0
  196. package/src/schema/index.ts +68 -0
  197. package/src/security/certificate-manager.ts +492 -0
  198. package/src/security/index.ts +9 -0
  199. package/src/security/scanning.ts +545 -0
  200. package/src/security/secrets-manager.ts +476 -0
  201. package/src/security/secrets-rotation.ts +456 -0
  202. package/src/security/security.test.ts +738 -0
  203. package/src/sms/advanced/ab-testing.ts +389 -0
  204. package/src/sms/advanced/analytics.ts +336 -0
  205. package/src/sms/advanced/campaigns.ts +523 -0
  206. package/src/sms/advanced/chatbot.ts +224 -0
  207. package/src/sms/advanced/index.ts +10 -0
  208. package/src/sms/advanced/link-tracking.ts +248 -0
  209. package/src/sms/advanced/mms.ts +308 -0
  210. package/src/sms/handlers/__tests__/send.test.ts +40 -0
  211. package/src/sms/handlers/delivery-status.ts +133 -0
  212. package/src/sms/handlers/receive.ts +162 -0
  213. package/src/sms/handlers/send.ts +174 -0
  214. package/src/sms/index.ts +9 -0
  215. package/src/stack-diff.ts +389 -0
  216. package/src/static-site/index.ts +85 -0
  217. package/src/template-builder.ts +110 -0
  218. package/src/template-validator.ts +574 -0
  219. package/src/utils/cache.ts +291 -0
  220. package/src/utils/diff.ts +269 -0
  221. package/src/utils/hash.ts +227 -0
  222. package/src/utils/index.ts +8 -0
  223. package/src/utils/parallel.ts +294 -0
  224. package/src/validators/credentials.test.ts +274 -0
  225. package/src/validators/credentials.ts +233 -0
  226. package/src/validators/quotas.test.ts +434 -0
  227. package/src/validators/quotas.ts +217 -0
  228. package/test/ai.test.ts +327 -0
  229. package/test/api.test.ts +511 -0
  230. package/test/auth.test.ts +632 -0
  231. package/test/cache.test.ts +406 -0
  232. package/test/cdn.test.ts +247 -0
  233. package/test/compute.test.ts +861 -0
  234. package/test/database.test.ts +523 -0
  235. package/test/deployment.test.ts +499 -0
  236. package/test/dns.test.ts +270 -0
  237. package/test/email.test.ts +439 -0
  238. package/test/filesystem.test.ts +382 -0
  239. package/test/integration.test.ts +350 -0
  240. package/test/messaging.test.ts +514 -0
  241. package/test/monitoring.test.ts +634 -0
  242. package/test/network.test.ts +425 -0
  243. package/test/permissions.test.ts +488 -0
  244. package/test/queue.test.ts +484 -0
  245. package/test/registry.test.ts +306 -0
  246. package/test/security.test.ts +462 -0
  247. package/test/storage.test.ts +463 -0
  248. package/test/template-validator.test.ts +559 -0
  249. package/test/workflow.test.ts +592 -0
  250. package/tsconfig.json +16 -0
  251. 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