@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,1094 @@
1
+ /**
2
+ * Unified Communication Module
3
+ *
4
+ * Generates all CloudFormation resources for Email, Phone, and SMS services
5
+ * including advanced features like analytics, scheduling, and campaigns.
6
+ *
7
+ * This module is the main entry point for `buddy deploy` communication services.
8
+ */
9
+
10
+ import type { EnvironmentType } from '@stacksjs/ts-cloud-types'
11
+ import { Fn } from '../intrinsic-functions'
12
+ import { generateLogicalId, generateResourceName } from '../resource-naming'
13
+
14
+ export interface CommunicationConfig {
15
+ slug: string
16
+ environment: EnvironmentType
17
+ region?: string
18
+
19
+ email?: EmailServiceConfig
20
+ phone?: PhoneServiceConfig
21
+ sms?: SmsServiceConfig
22
+ }
23
+
24
+ export interface EmailServiceConfig {
25
+ enabled: boolean
26
+ domain: string
27
+ mailboxes?: string[]
28
+
29
+ server?: {
30
+ enabled: boolean
31
+ scan?: boolean
32
+ storage?: {
33
+ retentionDays?: number
34
+ archiveAfterDays?: number
35
+ }
36
+ }
37
+
38
+ advanced?: {
39
+ search?: boolean // OpenSearch integration
40
+ threading?: boolean
41
+ scheduling?: boolean
42
+ analytics?: boolean
43
+ templates?: boolean
44
+ sharedMailboxes?: boolean
45
+ rules?: boolean
46
+ }
47
+
48
+ notifications?: {
49
+ newEmail?: boolean
50
+ bounces?: boolean
51
+ complaints?: boolean
52
+ }
53
+ }
54
+
55
+ export interface PhoneServiceConfig {
56
+ enabled: boolean
57
+ instanceAlias?: string
58
+
59
+ voicemail?: {
60
+ enabled: boolean
61
+ transcription?: boolean
62
+ maxDurationSeconds?: number
63
+ }
64
+
65
+ advanced?: {
66
+ recording?: boolean
67
+ analytics?: boolean
68
+ callbacks?: boolean
69
+ }
70
+
71
+ notifications?: {
72
+ incomingCall?: boolean
73
+ missedCall?: boolean
74
+ voicemail?: boolean
75
+ }
76
+ }
77
+
78
+ export interface SmsServiceConfig {
79
+ enabled: boolean
80
+ senderId?: string
81
+ originationNumber?: string
82
+ messageType?: 'TRANSACTIONAL' | 'PROMOTIONAL'
83
+
84
+ advanced?: {
85
+ campaigns?: boolean
86
+ analytics?: boolean
87
+ mms?: boolean
88
+ chatbot?: boolean
89
+ linkTracking?: boolean
90
+ abTesting?: boolean
91
+ }
92
+
93
+ optOut?: {
94
+ enabled: boolean
95
+ keywords?: string[]
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Communication Module
101
+ *
102
+ * Generates complete CloudFormation stack for communication services
103
+ */
104
+ export class Communication {
105
+ /**
106
+ * Generate all communication resources
107
+ */
108
+ static generate(config: CommunicationConfig): Record<string, any> {
109
+ const resources: Record<string, any> = {}
110
+ const { slug, environment, region = 'us-east-1' } = config
111
+
112
+ // Generate IAM role for all Lambda functions
113
+ const lambdaRole = Communication.createLambdaExecutionRole(slug, environment)
114
+ Object.assign(resources, lambdaRole.resources)
115
+
116
+ // Email resources
117
+ if (config.email?.enabled) {
118
+ const emailResources = Communication.generateEmailResources(config, lambdaRole.roleArn)
119
+ Object.assign(resources, emailResources)
120
+ }
121
+
122
+ // Phone resources
123
+ if (config.phone?.enabled) {
124
+ const phoneResources = Communication.generatePhoneResources(config, lambdaRole.roleArn)
125
+ Object.assign(resources, phoneResources)
126
+ }
127
+
128
+ // SMS resources
129
+ if (config.sms?.enabled) {
130
+ const smsResources = Communication.generateSmsResources(config, lambdaRole.roleArn)
131
+ Object.assign(resources, smsResources)
132
+ }
133
+
134
+ return resources
135
+ }
136
+
137
+ /**
138
+ * Create Lambda execution role with all necessary permissions
139
+ */
140
+ private static createLambdaExecutionRole(slug: string, environment: EnvironmentType): {
141
+ resources: Record<string, any>
142
+ roleArn: any
143
+ roleName: string
144
+ } {
145
+ const roleName = `${slug}-${environment}-communication-role`
146
+ const roleLogicalId = generateLogicalId(roleName)
147
+
148
+ const resources: Record<string, any> = {
149
+ [roleLogicalId]: {
150
+ Type: 'AWS::IAM::Role',
151
+ Properties: {
152
+ RoleName: roleName,
153
+ AssumeRolePolicyDocument: {
154
+ Version: '2012-10-17',
155
+ Statement: [{
156
+ Effect: 'Allow',
157
+ Principal: { Service: 'lambda.amazonaws.com' },
158
+ Action: 'sts:AssumeRole',
159
+ }],
160
+ },
161
+ ManagedPolicyArns: [
162
+ 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole',
163
+ ],
164
+ Policies: [{
165
+ PolicyName: 'CommunicationPolicy',
166
+ PolicyDocument: {
167
+ Version: '2012-10-17',
168
+ Statement: [
169
+ {
170
+ Effect: 'Allow',
171
+ Action: [
172
+ 's3:GetObject',
173
+ 's3:PutObject',
174
+ 's3:DeleteObject',
175
+ 's3:ListBucket',
176
+ ],
177
+ Resource: '*',
178
+ },
179
+ {
180
+ Effect: 'Allow',
181
+ Action: [
182
+ 'ses:SendEmail',
183
+ 'ses:SendRawEmail',
184
+ 'ses:GetIdentityVerificationAttributes',
185
+ ],
186
+ Resource: '*',
187
+ },
188
+ {
189
+ Effect: 'Allow',
190
+ Action: [
191
+ 'sns:Publish',
192
+ 'sns:Subscribe',
193
+ ],
194
+ Resource: '*',
195
+ },
196
+ {
197
+ Effect: 'Allow',
198
+ Action: [
199
+ 'dynamodb:GetItem',
200
+ 'dynamodb:PutItem',
201
+ 'dynamodb:UpdateItem',
202
+ 'dynamodb:DeleteItem',
203
+ 'dynamodb:Query',
204
+ 'dynamodb:Scan',
205
+ 'dynamodb:BatchWriteItem',
206
+ 'dynamodb:BatchGetItem',
207
+ ],
208
+ Resource: '*',
209
+ },
210
+ {
211
+ Effect: 'Allow',
212
+ Action: [
213
+ 'connect:*',
214
+ ],
215
+ Resource: '*',
216
+ },
217
+ {
218
+ Effect: 'Allow',
219
+ Action: [
220
+ 'mobiletargeting:SendMessages',
221
+ 'mobiletargeting:GetEndpoint',
222
+ 'mobiletargeting:UpdateEndpoint',
223
+ ],
224
+ Resource: '*',
225
+ },
226
+ {
227
+ Effect: 'Allow',
228
+ Action: [
229
+ 'transcribe:StartTranscriptionJob',
230
+ 'transcribe:GetTranscriptionJob',
231
+ ],
232
+ Resource: '*',
233
+ },
234
+ {
235
+ Effect: 'Allow',
236
+ Action: [
237
+ 'bedrock:InvokeModel',
238
+ ],
239
+ Resource: '*',
240
+ },
241
+ {
242
+ Effect: 'Allow',
243
+ Action: [
244
+ 'es:ESHttpGet',
245
+ 'es:ESHttpPost',
246
+ 'es:ESHttpPut',
247
+ 'es:ESHttpDelete',
248
+ ],
249
+ Resource: '*',
250
+ },
251
+ ],
252
+ },
253
+ }],
254
+ },
255
+ },
256
+ }
257
+
258
+ return {
259
+ resources,
260
+ roleArn: Fn.GetAtt(roleLogicalId, 'Arn'),
261
+ roleName,
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Generate Email service resources
267
+ */
268
+ private static generateEmailResources(config: CommunicationConfig, roleArn: any): Record<string, any> {
269
+ const { slug, environment, email } = config
270
+ if (!email) return {}
271
+
272
+ const resources: Record<string, any> = {}
273
+ const prefix = `${slug}-${environment}`
274
+
275
+ // Email storage bucket
276
+ const bucketName = `${prefix}-email-storage`
277
+ const bucketLogicalId = generateLogicalId(bucketName)
278
+ resources[bucketLogicalId] = {
279
+ Type: 'AWS::S3::Bucket',
280
+ Properties: {
281
+ BucketName: bucketName,
282
+ LifecycleConfiguration: {
283
+ Rules: [
284
+ {
285
+ Id: 'ArchiveOldEmails',
286
+ Status: 'Enabled',
287
+ Transitions: email.server?.storage?.archiveAfterDays ? [{
288
+ StorageClass: 'GLACIER',
289
+ TransitionInDays: email.server.storage.archiveAfterDays,
290
+ }] : [],
291
+ ExpirationInDays: email.server?.storage?.retentionDays || 365,
292
+ },
293
+ ],
294
+ },
295
+ NotificationConfiguration: {
296
+ LambdaConfigurations: [],
297
+ },
298
+ },
299
+ }
300
+
301
+ // SES Domain Identity
302
+ const identityLogicalId = generateLogicalId(`${prefix}-ses-identity`)
303
+ resources[identityLogicalId] = {
304
+ Type: 'AWS::SES::EmailIdentity',
305
+ Properties: {
306
+ EmailIdentity: email.domain,
307
+ DkimSigningAttributes: {
308
+ NextSigningKeyLength: 'RSA_2048_BIT',
309
+ },
310
+ FeedbackAttributes: {
311
+ EmailForwardingEnabled: true,
312
+ },
313
+ },
314
+ }
315
+
316
+ // SNS Topics for notifications
317
+ if (email.notifications?.bounces || email.notifications?.complaints) {
318
+ const bounceTopicLogicalId = generateLogicalId(`${prefix}-bounce-topic`)
319
+ resources[bounceTopicLogicalId] = {
320
+ Type: 'AWS::SNS::Topic',
321
+ Properties: {
322
+ TopicName: `${prefix}-email-bounces`,
323
+ },
324
+ }
325
+
326
+ const complaintTopicLogicalId = generateLogicalId(`${prefix}-complaint-topic`)
327
+ resources[complaintTopicLogicalId] = {
328
+ Type: 'AWS::SNS::Topic',
329
+ Properties: {
330
+ TopicName: `${prefix}-email-complaints`,
331
+ },
332
+ }
333
+ }
334
+
335
+ // Inbound email Lambda
336
+ if (email.server?.enabled) {
337
+ const inboundLambdaLogicalId = generateLogicalId(`${prefix}-inbound-email`)
338
+ resources[inboundLambdaLogicalId] = {
339
+ Type: 'AWS::Lambda::Function',
340
+ Properties: {
341
+ FunctionName: `${prefix}-inbound-email`,
342
+ Runtime: 'nodejs20.x',
343
+ Handler: 'index.handler',
344
+ Role: roleArn,
345
+ Timeout: 60,
346
+ MemorySize: 256,
347
+ Code: {
348
+ ZipFile: Communication.InboundEmailCode,
349
+ },
350
+ Environment: {
351
+ Variables: {
352
+ EMAIL_BUCKET: bucketName,
353
+ DOMAIN: email.domain,
354
+ },
355
+ },
356
+ },
357
+ }
358
+
359
+ // Outbound email Lambda
360
+ const outboundLambdaLogicalId = generateLogicalId(`${prefix}-outbound-email`)
361
+ resources[outboundLambdaLogicalId] = {
362
+ Type: 'AWS::Lambda::Function',
363
+ Properties: {
364
+ FunctionName: `${prefix}-outbound-email`,
365
+ Runtime: 'nodejs20.x',
366
+ Handler: 'index.handler',
367
+ Role: roleArn,
368
+ Timeout: 30,
369
+ MemorySize: 256,
370
+ Code: {
371
+ ZipFile: Communication.OutboundEmailCode,
372
+ },
373
+ Environment: {
374
+ Variables: {
375
+ EMAIL_BUCKET: bucketName,
376
+ DOMAIN: email.domain,
377
+ },
378
+ },
379
+ },
380
+ }
381
+ }
382
+
383
+ // Advanced: Analytics
384
+ if (email.advanced?.analytics) {
385
+ const analyticsTableLogicalId = generateLogicalId(`${prefix}-email-analytics`)
386
+ resources[analyticsTableLogicalId] = {
387
+ Type: 'AWS::DynamoDB::Table',
388
+ Properties: {
389
+ TableName: `${prefix}-email-analytics`,
390
+ BillingMode: 'PAY_PER_REQUEST',
391
+ AttributeDefinitions: [
392
+ { AttributeName: 'messageId', AttributeType: 'S' },
393
+ ],
394
+ KeySchema: [
395
+ { AttributeName: 'messageId', KeyType: 'HASH' },
396
+ ],
397
+ TimeToLiveSpecification: {
398
+ AttributeName: 'ttl',
399
+ Enabled: true,
400
+ },
401
+ },
402
+ }
403
+ }
404
+
405
+ // Advanced: Scheduling
406
+ if (email.advanced?.scheduling) {
407
+ const schedulerLambdaLogicalId = generateLogicalId(`${prefix}-email-scheduler`)
408
+ resources[schedulerLambdaLogicalId] = {
409
+ Type: 'AWS::Lambda::Function',
410
+ Properties: {
411
+ FunctionName: `${prefix}-email-scheduler`,
412
+ Runtime: 'nodejs20.x',
413
+ Handler: 'index.handler',
414
+ Role: roleArn,
415
+ Timeout: 300,
416
+ MemorySize: 256,
417
+ Code: {
418
+ ZipFile: Communication.EmailSchedulerCode,
419
+ },
420
+ Environment: {
421
+ Variables: {
422
+ EMAIL_BUCKET: bucketName,
423
+ },
424
+ },
425
+ },
426
+ }
427
+
428
+ // EventBridge rule to trigger scheduler
429
+ const schedulerRuleLogicalId = generateLogicalId(`${prefix}-email-scheduler-rule`)
430
+ resources[schedulerRuleLogicalId] = {
431
+ Type: 'AWS::Events::Rule',
432
+ Properties: {
433
+ Name: `${prefix}-email-scheduler`,
434
+ ScheduleExpression: 'rate(1 minute)',
435
+ State: 'ENABLED',
436
+ Targets: [{
437
+ Id: 'EmailSchedulerTarget',
438
+ Arn: Fn.GetAtt(schedulerLambdaLogicalId, 'Arn'),
439
+ }],
440
+ },
441
+ }
442
+ }
443
+
444
+ // Advanced: Threading
445
+ if (email.advanced?.threading) {
446
+ const threadingLambdaLogicalId = generateLogicalId(`${prefix}-email-threading`)
447
+ resources[threadingLambdaLogicalId] = {
448
+ Type: 'AWS::Lambda::Function',
449
+ Properties: {
450
+ FunctionName: `${prefix}-email-threading`,
451
+ Runtime: 'nodejs20.x',
452
+ Handler: 'index.handler',
453
+ Role: roleArn,
454
+ Timeout: 60,
455
+ MemorySize: 256,
456
+ Code: {
457
+ ZipFile: Communication.EmailThreadingCode,
458
+ },
459
+ Environment: {
460
+ Variables: {
461
+ EMAIL_BUCKET: bucketName,
462
+ },
463
+ },
464
+ },
465
+ }
466
+ }
467
+
468
+ // Advanced: Rules
469
+ if (email.advanced?.rules) {
470
+ const rulesTableLogicalId = generateLogicalId(`${prefix}-email-rules`)
471
+ resources[rulesTableLogicalId] = {
472
+ Type: 'AWS::DynamoDB::Table',
473
+ Properties: {
474
+ TableName: `${prefix}-email-rules`,
475
+ BillingMode: 'PAY_PER_REQUEST',
476
+ AttributeDefinitions: [
477
+ { AttributeName: 'mailbox', AttributeType: 'S' },
478
+ { AttributeName: 'id', AttributeType: 'S' },
479
+ ],
480
+ KeySchema: [
481
+ { AttributeName: 'mailbox', KeyType: 'HASH' },
482
+ { AttributeName: 'id', KeyType: 'RANGE' },
483
+ ],
484
+ },
485
+ }
486
+ }
487
+
488
+ // Advanced: Shared Mailboxes
489
+ if (email.advanced?.sharedMailboxes) {
490
+ const sharedMailboxTableLogicalId = generateLogicalId(`${prefix}-shared-mailboxes`)
491
+ resources[sharedMailboxTableLogicalId] = {
492
+ Type: 'AWS::DynamoDB::Table',
493
+ Properties: {
494
+ TableName: `${prefix}-shared-mailboxes`,
495
+ BillingMode: 'PAY_PER_REQUEST',
496
+ AttributeDefinitions: [
497
+ { AttributeName: 'id', AttributeType: 'S' },
498
+ { AttributeName: 'type', AttributeType: 'S' },
499
+ ],
500
+ KeySchema: [
501
+ { AttributeName: 'id', KeyType: 'HASH' },
502
+ ],
503
+ GlobalSecondaryIndexes: [{
504
+ IndexName: 'type-index',
505
+ KeySchema: [{ AttributeName: 'type', KeyType: 'HASH' }],
506
+ Projection: { ProjectionType: 'ALL' },
507
+ }],
508
+ },
509
+ }
510
+ }
511
+
512
+ // Advanced: Search (OpenSearch)
513
+ if (email.advanced?.search) {
514
+ const searchDomainLogicalId = generateLogicalId(`${prefix}-email-search`)
515
+ resources[searchDomainLogicalId] = {
516
+ Type: 'AWS::OpenSearchService::Domain',
517
+ Properties: {
518
+ DomainName: `${prefix}-email-search`.toLowerCase().replace(/[^a-z0-9-]/g, '-').substring(0, 28),
519
+ EngineVersion: 'OpenSearch_2.11',
520
+ ClusterConfig: {
521
+ InstanceType: 't3.small.search',
522
+ InstanceCount: 1,
523
+ },
524
+ EBSOptions: {
525
+ EBSEnabled: true,
526
+ VolumeType: 'gp3',
527
+ VolumeSize: 10,
528
+ },
529
+ NodeToNodeEncryptionOptions: { Enabled: true },
530
+ EncryptionAtRestOptions: { Enabled: true },
531
+ DomainEndpointOptions: { EnforceHTTPS: true },
532
+ },
533
+ }
534
+ }
535
+
536
+ return resources
537
+ }
538
+
539
+ /**
540
+ * Generate Phone service resources
541
+ */
542
+ private static generatePhoneResources(config: CommunicationConfig, roleArn: any): Record<string, any> {
543
+ const { slug, environment, phone } = config
544
+ if (!phone) return {}
545
+
546
+ const resources: Record<string, any> = {}
547
+ const prefix = `${slug}-${environment}`
548
+
549
+ // Call log DynamoDB table
550
+ const callLogTableLogicalId = generateLogicalId(`${prefix}-call-log`)
551
+ resources[callLogTableLogicalId] = {
552
+ Type: 'AWS::DynamoDB::Table',
553
+ Properties: {
554
+ TableName: `${prefix}-call-log`,
555
+ BillingMode: 'PAY_PER_REQUEST',
556
+ AttributeDefinitions: [
557
+ { AttributeName: 'contactId', AttributeType: 'S' },
558
+ ],
559
+ KeySchema: [
560
+ { AttributeName: 'contactId', KeyType: 'HASH' },
561
+ ],
562
+ TimeToLiveSpecification: {
563
+ AttributeName: 'ttl',
564
+ Enabled: true,
565
+ },
566
+ },
567
+ }
568
+
569
+ // Voicemail storage bucket
570
+ if (phone.voicemail?.enabled) {
571
+ const voicemailBucketLogicalId = generateLogicalId(`${prefix}-voicemail`)
572
+ resources[voicemailBucketLogicalId] = {
573
+ Type: 'AWS::S3::Bucket',
574
+ Properties: {
575
+ BucketName: `${prefix}-voicemail`,
576
+ LifecycleConfiguration: {
577
+ Rules: [{
578
+ Id: 'DeleteOldVoicemails',
579
+ Status: 'Enabled',
580
+ ExpirationInDays: 90,
581
+ }],
582
+ },
583
+ },
584
+ }
585
+ }
586
+
587
+ // SNS topic for phone notifications
588
+ const phoneTopicLogicalId = generateLogicalId(`${prefix}-phone-notifications`)
589
+ resources[phoneTopicLogicalId] = {
590
+ Type: 'AWS::SNS::Topic',
591
+ Properties: {
592
+ TopicName: `${prefix}-phone-notifications`,
593
+ },
594
+ }
595
+
596
+ // Incoming call Lambda
597
+ const incomingCallLambdaLogicalId = generateLogicalId(`${prefix}-incoming-call`)
598
+ resources[incomingCallLambdaLogicalId] = {
599
+ Type: 'AWS::Lambda::Function',
600
+ Properties: {
601
+ FunctionName: `${prefix}-incoming-call`,
602
+ Runtime: 'nodejs20.x',
603
+ Handler: 'index.handler',
604
+ Role: roleArn,
605
+ Timeout: 30,
606
+ MemorySize: 256,
607
+ Code: {
608
+ ZipFile: Communication.IncomingCallCode,
609
+ },
610
+ Environment: {
611
+ Variables: {
612
+ CALL_LOG_TABLE: `${prefix}-call-log`,
613
+ NOTIFICATION_TOPIC_ARN: Fn.Ref(phoneTopicLogicalId),
614
+ },
615
+ },
616
+ },
617
+ }
618
+
619
+ // Voicemail Lambda
620
+ if (phone.voicemail?.enabled) {
621
+ const voicemailLambdaLogicalId = generateLogicalId(`${prefix}-voicemail-handler`)
622
+ resources[voicemailLambdaLogicalId] = {
623
+ Type: 'AWS::Lambda::Function',
624
+ Properties: {
625
+ FunctionName: `${prefix}-voicemail-handler`,
626
+ Runtime: 'nodejs20.x',
627
+ Handler: 'index.handler',
628
+ Role: roleArn,
629
+ Timeout: phone.voicemail.transcription ? 300 : 60,
630
+ MemorySize: 256,
631
+ Code: {
632
+ ZipFile: Communication.VoicemailCode,
633
+ },
634
+ Environment: {
635
+ Variables: {
636
+ VOICEMAIL_BUCKET: `${prefix}-voicemail`,
637
+ TRANSCRIPTION_ENABLED: String(phone.voicemail.transcription || false),
638
+ NOTIFICATION_TOPIC_ARN: Fn.Ref(phoneTopicLogicalId),
639
+ },
640
+ },
641
+ },
642
+ }
643
+ }
644
+
645
+ // Advanced: Recording
646
+ if (phone.advanced?.recording) {
647
+ const recordingsTableLogicalId = generateLogicalId(`${prefix}-call-recordings`)
648
+ resources[recordingsTableLogicalId] = {
649
+ Type: 'AWS::DynamoDB::Table',
650
+ Properties: {
651
+ TableName: `${prefix}-call-recordings`,
652
+ BillingMode: 'PAY_PER_REQUEST',
653
+ AttributeDefinitions: [
654
+ { AttributeName: 'recordingId', AttributeType: 'S' },
655
+ ],
656
+ KeySchema: [
657
+ { AttributeName: 'recordingId', KeyType: 'HASH' },
658
+ ],
659
+ TimeToLiveSpecification: {
660
+ AttributeName: 'ttl',
661
+ Enabled: true,
662
+ },
663
+ },
664
+ }
665
+ }
666
+
667
+ // Advanced: Callbacks
668
+ if (phone.advanced?.callbacks) {
669
+ const callbacksTableLogicalId = generateLogicalId(`${prefix}-callbacks`)
670
+ resources[callbacksTableLogicalId] = {
671
+ Type: 'AWS::DynamoDB::Table',
672
+ Properties: {
673
+ TableName: `${prefix}-callbacks`,
674
+ BillingMode: 'PAY_PER_REQUEST',
675
+ AttributeDefinitions: [
676
+ { AttributeName: 'id', AttributeType: 'S' },
677
+ ],
678
+ KeySchema: [
679
+ { AttributeName: 'id', KeyType: 'HASH' },
680
+ ],
681
+ TimeToLiveSpecification: {
682
+ AttributeName: 'ttl',
683
+ Enabled: true,
684
+ },
685
+ },
686
+ }
687
+ }
688
+
689
+ // Advanced: Analytics
690
+ if (phone.advanced?.analytics) {
691
+ const metricsTableLogicalId = generateLogicalId(`${prefix}-call-metrics`)
692
+ resources[metricsTableLogicalId] = {
693
+ Type: 'AWS::DynamoDB::Table',
694
+ Properties: {
695
+ TableName: `${prefix}-call-metrics`,
696
+ BillingMode: 'PAY_PER_REQUEST',
697
+ AttributeDefinitions: [
698
+ { AttributeName: 'period', AttributeType: 'S' },
699
+ ],
700
+ KeySchema: [
701
+ { AttributeName: 'period', KeyType: 'HASH' },
702
+ ],
703
+ TimeToLiveSpecification: {
704
+ AttributeName: 'ttl',
705
+ Enabled: true,
706
+ },
707
+ },
708
+ }
709
+ }
710
+
711
+ return resources
712
+ }
713
+
714
+ /**
715
+ * Generate SMS service resources
716
+ */
717
+ private static generateSmsResources(config: CommunicationConfig, roleArn: any): Record<string, any> {
718
+ const { slug, environment, sms } = config
719
+ if (!sms) return {}
720
+
721
+ const resources: Record<string, any> = {}
722
+ const prefix = `${slug}-${environment}`
723
+
724
+ // Pinpoint Application
725
+ const pinpointAppLogicalId = generateLogicalId(`${prefix}-pinpoint-app`)
726
+ resources[pinpointAppLogicalId] = {
727
+ Type: 'AWS::Pinpoint::App',
728
+ Properties: {
729
+ Name: `${prefix}-sms`,
730
+ },
731
+ }
732
+
733
+ // SMS Channel
734
+ const smsChannelLogicalId = generateLogicalId(`${prefix}-sms-channel`)
735
+ resources[smsChannelLogicalId] = {
736
+ Type: 'AWS::Pinpoint::SMSChannel',
737
+ Properties: {
738
+ ApplicationId: Fn.Ref(pinpointAppLogicalId),
739
+ Enabled: true,
740
+ SenderId: sms.senderId,
741
+ },
742
+ }
743
+
744
+ // Message log table
745
+ const messageLogTableLogicalId = generateLogicalId(`${prefix}-sms-log`)
746
+ resources[messageLogTableLogicalId] = {
747
+ Type: 'AWS::DynamoDB::Table',
748
+ Properties: {
749
+ TableName: `${prefix}-sms-log`,
750
+ BillingMode: 'PAY_PER_REQUEST',
751
+ AttributeDefinitions: [
752
+ { AttributeName: 'messageId', AttributeType: 'S' },
753
+ ],
754
+ KeySchema: [
755
+ { AttributeName: 'messageId', KeyType: 'HASH' },
756
+ ],
757
+ TimeToLiveSpecification: {
758
+ AttributeName: 'ttl',
759
+ Enabled: true,
760
+ },
761
+ },
762
+ }
763
+
764
+ // Opt-out table
765
+ if (sms.optOut?.enabled) {
766
+ const optOutTableLogicalId = generateLogicalId(`${prefix}-sms-optout`)
767
+ resources[optOutTableLogicalId] = {
768
+ Type: 'AWS::DynamoDB::Table',
769
+ Properties: {
770
+ TableName: `${prefix}-sms-optout`,
771
+ BillingMode: 'PAY_PER_REQUEST',
772
+ AttributeDefinitions: [
773
+ { AttributeName: 'phoneNumber', AttributeType: 'S' },
774
+ ],
775
+ KeySchema: [
776
+ { AttributeName: 'phoneNumber', KeyType: 'HASH' },
777
+ ],
778
+ },
779
+ }
780
+ }
781
+
782
+ // SNS topic for SMS notifications
783
+ const smsTopicLogicalId = generateLogicalId(`${prefix}-sms-notifications`)
784
+ resources[smsTopicLogicalId] = {
785
+ Type: 'AWS::SNS::Topic',
786
+ Properties: {
787
+ TopicName: `${prefix}-sms-notifications`,
788
+ },
789
+ }
790
+
791
+ // SMS Send Lambda
792
+ const sendSmsLambdaLogicalId = generateLogicalId(`${prefix}-send-sms`)
793
+ resources[sendSmsLambdaLogicalId] = {
794
+ Type: 'AWS::Lambda::Function',
795
+ Properties: {
796
+ FunctionName: `${prefix}-send-sms`,
797
+ Runtime: 'nodejs20.x',
798
+ Handler: 'index.handler',
799
+ Role: roleArn,
800
+ Timeout: 30,
801
+ MemorySize: 256,
802
+ Code: {
803
+ ZipFile: Communication.SendSmsCode,
804
+ },
805
+ Environment: {
806
+ Variables: {
807
+ PINPOINT_APP_ID: Fn.Ref(pinpointAppLogicalId),
808
+ MESSAGE_LOG_TABLE: `${prefix}-sms-log`,
809
+ OPT_OUT_TABLE: sms.optOut?.enabled ? `${prefix}-sms-optout` : '',
810
+ MESSAGE_TYPE: sms.messageType || 'TRANSACTIONAL',
811
+ },
812
+ },
813
+ },
814
+ }
815
+
816
+ // SMS Receive Lambda
817
+ const receiveSmsLambdaLogicalId = generateLogicalId(`${prefix}-receive-sms`)
818
+ resources[receiveSmsLambdaLogicalId] = {
819
+ Type: 'AWS::Lambda::Function',
820
+ Properties: {
821
+ FunctionName: `${prefix}-receive-sms`,
822
+ Runtime: 'nodejs20.x',
823
+ Handler: 'index.handler',
824
+ Role: roleArn,
825
+ Timeout: 30,
826
+ MemorySize: 256,
827
+ Code: {
828
+ ZipFile: Communication.ReceiveSmsCode,
829
+ },
830
+ Environment: {
831
+ Variables: {
832
+ MESSAGE_LOG_TABLE: `${prefix}-sms-log`,
833
+ OPT_OUT_TABLE: sms.optOut?.enabled ? `${prefix}-sms-optout` : '',
834
+ NOTIFICATION_TOPIC_ARN: Fn.Ref(smsTopicLogicalId),
835
+ },
836
+ },
837
+ },
838
+ }
839
+
840
+ // Advanced: Campaigns
841
+ if (sms.advanced?.campaigns) {
842
+ const campaignsTableLogicalId = generateLogicalId(`${prefix}-sms-campaigns`)
843
+ resources[campaignsTableLogicalId] = {
844
+ Type: 'AWS::DynamoDB::Table',
845
+ Properties: {
846
+ TableName: `${prefix}-sms-campaigns`,
847
+ BillingMode: 'PAY_PER_REQUEST',
848
+ AttributeDefinitions: [
849
+ { AttributeName: 'id', AttributeType: 'S' },
850
+ ],
851
+ KeySchema: [
852
+ { AttributeName: 'id', KeyType: 'HASH' },
853
+ ],
854
+ },
855
+ }
856
+ }
857
+
858
+ // Advanced: Analytics
859
+ if (sms.advanced?.analytics) {
860
+ const analyticsTableLogicalId = generateLogicalId(`${prefix}-sms-analytics`)
861
+ resources[analyticsTableLogicalId] = {
862
+ Type: 'AWS::DynamoDB::Table',
863
+ Properties: {
864
+ TableName: `${prefix}-sms-analytics`,
865
+ BillingMode: 'PAY_PER_REQUEST',
866
+ AttributeDefinitions: [
867
+ { AttributeName: 'period', AttributeType: 'S' },
868
+ ],
869
+ KeySchema: [
870
+ { AttributeName: 'period', KeyType: 'HASH' },
871
+ ],
872
+ TimeToLiveSpecification: {
873
+ AttributeName: 'ttl',
874
+ Enabled: true,
875
+ },
876
+ },
877
+ }
878
+ }
879
+
880
+ // Advanced: Link Tracking
881
+ if (sms.advanced?.linkTracking) {
882
+ const linksTableLogicalId = generateLogicalId(`${prefix}-short-links`)
883
+ resources[linksTableLogicalId] = {
884
+ Type: 'AWS::DynamoDB::Table',
885
+ Properties: {
886
+ TableName: `${prefix}-short-links`,
887
+ BillingMode: 'PAY_PER_REQUEST',
888
+ AttributeDefinitions: [
889
+ { AttributeName: 'id', AttributeType: 'S' },
890
+ ],
891
+ KeySchema: [
892
+ { AttributeName: 'id', KeyType: 'HASH' },
893
+ ],
894
+ TimeToLiveSpecification: {
895
+ AttributeName: 'ttl',
896
+ Enabled: true,
897
+ },
898
+ },
899
+ }
900
+ }
901
+
902
+ // Advanced: Chatbot
903
+ if (sms.advanced?.chatbot) {
904
+ const sessionsTableLogicalId = generateLogicalId(`${prefix}-chatbot-sessions`)
905
+ resources[sessionsTableLogicalId] = {
906
+ Type: 'AWS::DynamoDB::Table',
907
+ Properties: {
908
+ TableName: `${prefix}-chatbot-sessions`,
909
+ BillingMode: 'PAY_PER_REQUEST',
910
+ AttributeDefinitions: [
911
+ { AttributeName: 'phoneNumber', AttributeType: 'S' },
912
+ ],
913
+ KeySchema: [
914
+ { AttributeName: 'phoneNumber', KeyType: 'HASH' },
915
+ ],
916
+ TimeToLiveSpecification: {
917
+ AttributeName: 'ttl',
918
+ Enabled: true,
919
+ },
920
+ },
921
+ }
922
+
923
+ const rulesTableLogicalId = generateLogicalId(`${prefix}-chatbot-rules`)
924
+ resources[rulesTableLogicalId] = {
925
+ Type: 'AWS::DynamoDB::Table',
926
+ Properties: {
927
+ TableName: `${prefix}-chatbot-rules`,
928
+ BillingMode: 'PAY_PER_REQUEST',
929
+ AttributeDefinitions: [
930
+ { AttributeName: 'id', AttributeType: 'S' },
931
+ ],
932
+ KeySchema: [
933
+ { AttributeName: 'id', KeyType: 'HASH' },
934
+ ],
935
+ },
936
+ }
937
+ }
938
+
939
+ // Advanced: A/B Testing
940
+ if (sms.advanced?.abTesting) {
941
+ const abTestsTableLogicalId = generateLogicalId(`${prefix}-ab-tests`)
942
+ resources[abTestsTableLogicalId] = {
943
+ Type: 'AWS::DynamoDB::Table',
944
+ Properties: {
945
+ TableName: `${prefix}-ab-tests`,
946
+ BillingMode: 'PAY_PER_REQUEST',
947
+ AttributeDefinitions: [
948
+ { AttributeName: 'id', AttributeType: 'S' },
949
+ ],
950
+ KeySchema: [
951
+ { AttributeName: 'id', KeyType: 'HASH' },
952
+ ],
953
+ },
954
+ }
955
+ }
956
+
957
+ // Advanced: MMS
958
+ if (sms.advanced?.mms) {
959
+ const mediaBucketLogicalId = generateLogicalId(`${prefix}-mms-media`)
960
+ resources[mediaBucketLogicalId] = {
961
+ Type: 'AWS::S3::Bucket',
962
+ Properties: {
963
+ BucketName: `${prefix}-mms-media`,
964
+ LifecycleConfiguration: {
965
+ Rules: [{
966
+ Id: 'DeleteOldMedia',
967
+ Status: 'Enabled',
968
+ ExpirationInDays: 30,
969
+ }],
970
+ },
971
+ },
972
+ }
973
+ }
974
+
975
+ return resources
976
+ }
977
+
978
+ // Lambda code snippets
979
+ static InboundEmailCode = `
980
+ const { S3Client, GetObjectCommand, PutObjectCommand } = require('@aws-sdk/client-s3');
981
+ const s3 = new S3Client({});
982
+ exports.handler = async (event) => {
983
+ console.log('Inbound email:', JSON.stringify(event, null, 2));
984
+ // Process inbound email from SES
985
+ for (const record of event.Records) {
986
+ const sesNotification = record.ses || JSON.parse(record.Sns?.Message || '{}');
987
+ const mail = sesNotification.mail || {};
988
+ const messageId = mail.messageId;
989
+ // Store email metadata
990
+ console.log('Processing email:', messageId);
991
+ }
992
+ return { statusCode: 200 };
993
+ };`
994
+
995
+ static OutboundEmailCode = `
996
+ const { SESClient, SendRawEmailCommand } = require('@aws-sdk/client-ses');
997
+ const ses = new SESClient({});
998
+ exports.handler = async (event) => {
999
+ console.log('Outbound email:', JSON.stringify(event, null, 2));
1000
+ const { to, from, subject, html, text } = JSON.parse(event.body || '{}');
1001
+ // Build and send email
1002
+ const boundary = '----=_Part_' + Date.now();
1003
+ let raw = 'From: ' + from + '\\r\\n';
1004
+ raw += 'To: ' + to + '\\r\\n';
1005
+ raw += 'Subject: ' + subject + '\\r\\n';
1006
+ raw += 'MIME-Version: 1.0\\r\\n';
1007
+ raw += 'Content-Type: text/html; charset=UTF-8\\r\\n\\r\\n';
1008
+ raw += html || text || '';
1009
+ const result = await ses.send(new SendRawEmailCommand({
1010
+ RawMessage: { Data: Buffer.from(raw) }
1011
+ }));
1012
+ return { statusCode: 200, body: JSON.stringify({ messageId: result.MessageId }) };
1013
+ };`
1014
+
1015
+ static EmailSchedulerCode = `
1016
+ const { S3Client, ListObjectsV2Command, GetObjectCommand, DeleteObjectCommand } = require('@aws-sdk/client-s3');
1017
+ const { SESClient, SendRawEmailCommand } = require('@aws-sdk/client-ses');
1018
+ const s3 = new S3Client({});
1019
+ const ses = new SESClient({});
1020
+ exports.handler = async (event) => {
1021
+ console.log('Email scheduler running');
1022
+ // Check for scheduled emails and send them
1023
+ return { statusCode: 200 };
1024
+ };`
1025
+
1026
+ static EmailThreadingCode = `
1027
+ const { S3Client, GetObjectCommand, PutObjectCommand } = require('@aws-sdk/client-s3');
1028
+ const crypto = require('crypto');
1029
+ const s3 = new S3Client({});
1030
+ exports.handler = async (event) => {
1031
+ console.log('Email threading:', JSON.stringify(event, null, 2));
1032
+ // Group emails by thread
1033
+ return { statusCode: 200 };
1034
+ };`
1035
+
1036
+ static IncomingCallCode = `
1037
+ const { DynamoDBClient, PutItemCommand } = require('@aws-sdk/client-dynamodb');
1038
+ const { SNSClient, PublishCommand } = require('@aws-sdk/client-sns');
1039
+ const dynamodb = new DynamoDBClient({});
1040
+ const sns = new SNSClient({});
1041
+ exports.handler = async (event) => {
1042
+ console.log('Incoming call:', JSON.stringify(event, null, 2));
1043
+ const contactData = event.Details?.ContactData || {};
1044
+ // Log call and send notification
1045
+ return { statusCode: 200 };
1046
+ };`
1047
+
1048
+ static VoicemailCode = `
1049
+ const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
1050
+ const { TranscribeClient, StartTranscriptionJobCommand } = require('@aws-sdk/client-transcribe');
1051
+ const { SNSClient, PublishCommand } = require('@aws-sdk/client-sns');
1052
+ const s3 = new S3Client({});
1053
+ const transcribe = new TranscribeClient({});
1054
+ const sns = new SNSClient({});
1055
+ exports.handler = async (event) => {
1056
+ console.log('Voicemail:', JSON.stringify(event, null, 2));
1057
+ // Store voicemail and optionally transcribe
1058
+ return { statusCode: 200 };
1059
+ };`
1060
+
1061
+ static SendSmsCode = `
1062
+ const { PinpointClient, SendMessagesCommand } = require('@aws-sdk/client-pinpoint');
1063
+ const { DynamoDBClient, PutItemCommand, GetItemCommand } = require('@aws-sdk/client-dynamodb');
1064
+ const pinpoint = new PinpointClient({});
1065
+ const dynamodb = new DynamoDBClient({});
1066
+ exports.handler = async (event) => {
1067
+ console.log('Send SMS:', JSON.stringify(event, null, 2));
1068
+ const { to, body } = JSON.parse(event.body || '{}');
1069
+ // Check opt-out and send SMS
1070
+ const result = await pinpoint.send(new SendMessagesCommand({
1071
+ ApplicationId: process.env.PINPOINT_APP_ID,
1072
+ MessageRequest: {
1073
+ Addresses: { [to]: { ChannelType: 'SMS' } },
1074
+ MessageConfiguration: {
1075
+ SMSMessage: { Body: body, MessageType: process.env.MESSAGE_TYPE }
1076
+ }
1077
+ }
1078
+ }));
1079
+ return { statusCode: 200, body: JSON.stringify(result) };
1080
+ };`
1081
+
1082
+ static ReceiveSmsCode = `
1083
+ const { DynamoDBClient, PutItemCommand, UpdateItemCommand } = require('@aws-sdk/client-dynamodb');
1084
+ const { SNSClient, PublishCommand } = require('@aws-sdk/client-sns');
1085
+ const dynamodb = new DynamoDBClient({});
1086
+ const sns = new SNSClient({});
1087
+ exports.handler = async (event) => {
1088
+ console.log('Receive SMS:', JSON.stringify(event, null, 2));
1089
+ // Process inbound SMS, handle opt-out keywords
1090
+ return { statusCode: 200 };
1091
+ };`
1092
+ }
1093
+
1094
+ export default Communication