@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,352 @@
1
+ /**
2
+ * Email Scheduling (Send Later)
3
+ *
4
+ * Provides scheduled email sending functionality
5
+ */
6
+
7
+ export interface ScheduledEmail {
8
+ id: string
9
+ email: {
10
+ from: string
11
+ to: string | string[]
12
+ cc?: string[]
13
+ bcc?: string[]
14
+ subject: string
15
+ html?: string
16
+ text?: string
17
+ attachments?: Array<{
18
+ filename: string
19
+ content: string
20
+ contentType?: string
21
+ }>
22
+ }
23
+ scheduledFor: string
24
+ timezone?: string
25
+ status: 'pending' | 'sent' | 'failed' | 'cancelled'
26
+ createdAt: string
27
+ sentAt?: string
28
+ error?: string
29
+ retryCount?: number
30
+ }
31
+
32
+ /**
33
+ * Email Scheduling Module
34
+ */
35
+ export class EmailScheduling {
36
+ /**
37
+ * Lambda code for processing scheduled emails
38
+ */
39
+ static SchedulerLambdaCode = `
40
+ const { S3Client, GetObjectCommand, PutObjectCommand, DeleteObjectCommand, ListObjectsV2Command } = require('@aws-sdk/client-s3');
41
+ const { SESClient, SendRawEmailCommand } = require('@aws-sdk/client-ses');
42
+
43
+ const s3 = new S3Client({});
44
+ const ses = new SESClient({});
45
+ const EMAIL_BUCKET = process.env.EMAIL_BUCKET;
46
+
47
+ exports.handler = async (event) => {
48
+ console.log('Email scheduler event:', JSON.stringify(event, null, 2));
49
+
50
+ const now = new Date();
51
+
52
+ // List scheduled emails
53
+ const listResult = await s3.send(new ListObjectsV2Command({
54
+ Bucket: EMAIL_BUCKET,
55
+ Prefix: 'scheduled/',
56
+ }));
57
+
58
+ const objects = listResult.Contents || [];
59
+
60
+ for (const obj of objects) {
61
+ if (!obj.Key.endsWith('.json')) continue;
62
+
63
+ try {
64
+ // Get scheduled email
65
+ const getResult = await s3.send(new GetObjectCommand({
66
+ Bucket: EMAIL_BUCKET,
67
+ Key: obj.Key,
68
+ }));
69
+
70
+ const scheduled = JSON.parse(await getResult.Body.transformToString());
71
+
72
+ // Skip if not pending
73
+ if (scheduled.status !== 'pending') continue;
74
+
75
+ // Check if it's time to send
76
+ const scheduledTime = new Date(scheduled.scheduledFor);
77
+ if (scheduledTime > now) continue;
78
+
79
+ console.log(\`Sending scheduled email: \${scheduled.id}\`);
80
+
81
+ try {
82
+ // Build and send email
83
+ const rawMessage = buildRawEmail(scheduled.email);
84
+ const sendResult = await ses.send(new SendRawEmailCommand({
85
+ RawMessage: { Data: Buffer.from(rawMessage) },
86
+ }));
87
+
88
+ // Update status to sent
89
+ scheduled.status = 'sent';
90
+ scheduled.sentAt = new Date().toISOString();
91
+ scheduled.messageId = sendResult.MessageId;
92
+
93
+ // Move to sent folder
94
+ await s3.send(new PutObjectCommand({
95
+ Bucket: EMAIL_BUCKET,
96
+ Key: \`scheduled-sent/\${scheduled.id}.json\`,
97
+ Body: JSON.stringify(scheduled, null, 2),
98
+ ContentType: 'application/json',
99
+ }));
100
+
101
+ // Delete from scheduled folder
102
+ await s3.send(new DeleteObjectCommand({
103
+ Bucket: EMAIL_BUCKET,
104
+ Key: obj.Key,
105
+ }));
106
+
107
+ console.log(\`Sent scheduled email: \${scheduled.id}\`);
108
+ } catch (sendError) {
109
+ console.error(\`Failed to send scheduled email: \${scheduled.id}\`, sendError);
110
+
111
+ // Update retry count
112
+ scheduled.retryCount = (scheduled.retryCount || 0) + 1;
113
+
114
+ if (scheduled.retryCount >= 3) {
115
+ scheduled.status = 'failed';
116
+ scheduled.error = sendError.message;
117
+ }
118
+
119
+ await s3.send(new PutObjectCommand({
120
+ Bucket: EMAIL_BUCKET,
121
+ Key: obj.Key,
122
+ Body: JSON.stringify(scheduled, null, 2),
123
+ ContentType: 'application/json',
124
+ }));
125
+ }
126
+ } catch (error) {
127
+ console.error(\`Error processing scheduled email: \${obj.Key}\`, error);
128
+ }
129
+ }
130
+
131
+ return { statusCode: 200 };
132
+ };
133
+
134
+ function buildRawEmail(email) {
135
+ const boundary = \`----=_Part_\${Date.now()}\`;
136
+ let raw = '';
137
+
138
+ raw += \`From: \${email.from}\\r\\n\`;
139
+ raw += \`To: \${Array.isArray(email.to) ? email.to.join(', ') : email.to}\\r\\n\`;
140
+ if (email.cc) raw += \`Cc: \${email.cc.join(', ')}\\r\\n\`;
141
+ raw += \`Subject: =?UTF-8?B?\${Buffer.from(email.subject).toString('base64')}?=\\r\\n\`;
142
+ raw += \`Date: \${new Date().toUTCString()}\\r\\n\`;
143
+ raw += 'MIME-Version: 1.0\\r\\n';
144
+
145
+ if (email.html && email.text) {
146
+ raw += \`Content-Type: multipart/alternative; boundary="\${boundary}"\\r\\n\\r\\n\`;
147
+ raw += \`--\${boundary}\\r\\n\`;
148
+ raw += 'Content-Type: text/plain; charset=UTF-8\\r\\n\\r\\n';
149
+ raw += email.text + '\\r\\n\\r\\n';
150
+ raw += \`--\${boundary}\\r\\n\`;
151
+ raw += 'Content-Type: text/html; charset=UTF-8\\r\\n\\r\\n';
152
+ raw += email.html + '\\r\\n\\r\\n';
153
+ raw += \`--\${boundary}--\\r\\n\`;
154
+ } else if (email.html) {
155
+ raw += 'Content-Type: text/html; charset=UTF-8\\r\\n\\r\\n';
156
+ raw += email.html;
157
+ } else {
158
+ raw += 'Content-Type: text/plain; charset=UTF-8\\r\\n\\r\\n';
159
+ raw += email.text || '';
160
+ }
161
+
162
+ return raw;
163
+ }
164
+ `
165
+
166
+ /**
167
+ * Create scheduler Lambda function
168
+ */
169
+ static createSchedulerLambda(config: {
170
+ slug: string
171
+ roleArn: string
172
+ emailBucket: string
173
+ }): Record<string, any> {
174
+ return {
175
+ [`${config.slug}EmailSchedulerLambda`]: {
176
+ Type: 'AWS::Lambda::Function',
177
+ Properties: {
178
+ FunctionName: `${config.slug}-email-scheduler`,
179
+ Runtime: 'nodejs20.x',
180
+ Handler: 'index.handler',
181
+ Role: config.roleArn,
182
+ Timeout: 300,
183
+ MemorySize: 256,
184
+ Code: {
185
+ ZipFile: EmailScheduling.SchedulerLambdaCode,
186
+ },
187
+ Environment: {
188
+ Variables: {
189
+ EMAIL_BUCKET: config.emailBucket,
190
+ },
191
+ },
192
+ },
193
+ },
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Create EventBridge rule to trigger scheduler
199
+ */
200
+ static createSchedulerRule(config: {
201
+ slug: string
202
+ lambdaArn: string
203
+ scheduleExpression?: string
204
+ }): Record<string, any> {
205
+ return {
206
+ [`${config.slug}EmailSchedulerRule`]: {
207
+ Type: 'AWS::Events::Rule',
208
+ Properties: {
209
+ Name: `${config.slug}-email-scheduler`,
210
+ Description: 'Trigger email scheduler every minute',
211
+ ScheduleExpression: config.scheduleExpression || 'rate(1 minute)',
212
+ State: 'ENABLED',
213
+ Targets: [
214
+ {
215
+ Id: 'EmailSchedulerTarget',
216
+ Arn: config.lambdaArn,
217
+ },
218
+ ],
219
+ },
220
+ },
221
+ [`${config.slug}EmailSchedulerPermission`]: {
222
+ Type: 'AWS::Lambda::Permission',
223
+ Properties: {
224
+ FunctionName: config.lambdaArn,
225
+ Action: 'lambda:InvokeFunction',
226
+ Principal: 'events.amazonaws.com',
227
+ SourceArn: { 'Fn::GetAtt': [`${config.slug}EmailSchedulerRule`, 'Arn'] },
228
+ },
229
+ },
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Schedule an email (SDK helper)
235
+ */
236
+ static async scheduleEmail(params: {
237
+ s3Client: any
238
+ bucket: string
239
+ email: ScheduledEmail['email']
240
+ scheduledFor: Date | string
241
+ timezone?: string
242
+ }): Promise<ScheduledEmail> {
243
+ const { s3Client, bucket, email, scheduledFor, timezone } = params
244
+
245
+ const id = `sched-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
246
+
247
+ const scheduled: ScheduledEmail = {
248
+ id,
249
+ email,
250
+ scheduledFor: typeof scheduledFor === 'string' ? scheduledFor : scheduledFor.toISOString(),
251
+ timezone,
252
+ status: 'pending',
253
+ createdAt: new Date().toISOString(),
254
+ }
255
+
256
+ await s3Client.send({
257
+ Bucket: bucket,
258
+ Key: `scheduled/${id}.json`,
259
+ Body: JSON.stringify(scheduled, null, 2),
260
+ ContentType: 'application/json',
261
+ })
262
+
263
+ return scheduled
264
+ }
265
+
266
+ /**
267
+ * Cancel a scheduled email
268
+ */
269
+ static async cancelScheduledEmail(params: {
270
+ s3Client: any
271
+ bucket: string
272
+ id: string
273
+ }): Promise<boolean> {
274
+ const { s3Client, bucket, id } = params
275
+
276
+ try {
277
+ // Get scheduled email
278
+ const result = await s3Client.send({
279
+ Bucket: bucket,
280
+ Key: `scheduled/${id}.json`,
281
+ })
282
+
283
+ const scheduled = JSON.parse(result.Body)
284
+
285
+ if (scheduled.status !== 'pending') {
286
+ return false
287
+ }
288
+
289
+ // Update status
290
+ scheduled.status = 'cancelled'
291
+
292
+ await s3Client.send({
293
+ Bucket: bucket,
294
+ Key: `scheduled/${id}.json`,
295
+ Body: JSON.stringify(scheduled, null, 2),
296
+ ContentType: 'application/json',
297
+ })
298
+
299
+ return true
300
+ }
301
+ catch {
302
+ return false
303
+ }
304
+ }
305
+
306
+ /**
307
+ * List scheduled emails
308
+ */
309
+ static async listScheduledEmails(params: {
310
+ s3Client: any
311
+ bucket: string
312
+ status?: ScheduledEmail['status']
313
+ }): Promise<ScheduledEmail[]> {
314
+ const { s3Client, bucket, status } = params
315
+
316
+ const result = await s3Client.send({
317
+ Bucket: bucket,
318
+ Prefix: 'scheduled/',
319
+ })
320
+
321
+ const emails: ScheduledEmail[] = []
322
+
323
+ for (const obj of result.Contents || []) {
324
+ if (!obj.Key.endsWith('.json')) continue
325
+
326
+ try {
327
+ const getResult = await s3Client.send({
328
+ Bucket: bucket,
329
+ Key: obj.Key,
330
+ })
331
+
332
+ const scheduled = JSON.parse(getResult.Body)
333
+
334
+ if (!status || scheduled.status === status) {
335
+ emails.push(scheduled)
336
+ }
337
+ }
338
+ catch {
339
+ // Skip invalid files
340
+ }
341
+ }
342
+
343
+ // Sort by scheduled time
344
+ emails.sort((a, b) =>
345
+ new Date(a.scheduledFor).getTime() - new Date(b.scheduledFor).getTime()
346
+ )
347
+
348
+ return emails
349
+ }
350
+ }
351
+
352
+ export default EmailScheduling