-
Notifications
You must be signed in to change notification settings - Fork 1
/
template.yaml
310 lines (294 loc) · 9.43 KB
/
template.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: An AWS Serverless app that stores and syncs incoming SES mail to an IMAP mailbox
Parameters:
LoftDomain:
Type: String
Description: Domain name on which to receive email in SES (e.g. 'example.com')
IMAPServer:
Type: String
Description: Host name of an IMAP server to which Loft will connect and synchronise messages
IMAPUsername:
Type: String
NoEcho: true
IMAPPassword:
Type: String
NoEcho: true
IMAPDestinationFolder:
Type: String
Default: "INBOX"
Description: Only change this if you know what you're doing. Default is 'INBOX'
IMAPMarkAsRead:
Type: String
Description: Set to 'true' to avoid unread messages once they are stored in the mailbox
AllowedValues:
- 'true'
- 'false'
Default: false
IMAPDebugLogging:
Type: String
Description: Set to 'true' to write diagnostic information about the IMAP connection to CloudWatch logs
AllowedValues:
- 'true'
- 'false'
Default: false
Metadata:
AWS::ServerlessRepo::Application:
Name: Loft
Description: |
Receive email via Amazon Simple Email Service (SES)
on a wildcard domain (<anything>@yourdomain.com)
and synchronise it to a designated IMAP mailbox.
Labels:
- mail
- email
- imap
- lambda
- ses
Author: Barry Postma
SemanticVersion: 1.3.1
ReadmeUrl: ./README.md
SpdxLicenseId: MIT
LicenseUrl: ./LICENSE
HomePageUrl: https://github.com/BAPostma/Loft
SourceCodeUrl: https://github.com/BAPostma/Loft
Resources:
EmailStorage:
Type: AWS::S3::Bucket
Properties:
BucketName: !Join ['-', ['loft', !Ref LoftDomain]]
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
AccessControl: Private
EmailStoragePermissions:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref EmailStorage
PolicyDocument:
Statement:
- Action:
- 's3:PutObject'
Effect: Allow
Resource: !Join ['', ['arn:aws:s3:::', !Ref EmailStorage, '/*']]
Principal:
Service:
- 'ses.amazonaws.com'
Condition:
StringLike:
'aws:Referer':
- !Ref AWS::AccountId
DependsOn: EmailStorage
EmailNotificationTopic:
Type: AWS::SNS::Topic
Properties:
TopicName: !Join ['-', ['sns', Fn::Join: ['_', Fn::Split: ['.', !Ref LoftDomain]], 'inbox']]
DependsOn:
- EmailNotificationQueue
EmailNotificationQueue:
Type: AWS::SQS::Queue
Properties:
QueueName: !Join ['-', ['sqs', Fn::Join: ['_', Fn::Split: ['.', !Ref LoftDomain]], 'inbox']]
MessageRetentionPeriod: 1209600
RedrivePolicy:
deadLetterTargetArn: !GetAtt EmailNotificationDeadLetterQueue.Arn
maxReceiveCount: 3
VisibilityTimeout: 60
DependsOn: EmailNotificationDeadLetterQueue
EmailNotificationQueueTopicSubscription:
Type: AWS::SNS::Subscription
Properties:
Protocol: sqs
TopicArn: !Ref EmailNotificationTopic
Endpoint: !GetAtt EmailNotificationQueue.Arn
RawMessageDelivery: true
DependsOn:
- EmailNotificationTopic
- EmailNotificationQueue
EmailNotificationDeadLetterQueue:
Type: AWS::SQS::Queue
Properties:
QueueName: !Join ['-', ['sqs', Fn::Join: ['_', Fn::Split: ['.', !Ref LoftDomain]], 'dlq']]
MessageRetentionPeriod: 1209600
CommandsQueue:
Type: AWS::SQS::Queue
Properties:
QueueName: !Join ['-', ['sqs', Fn::Join: ['_', Fn::Split: ['.', !Ref LoftDomain]], 'commands']]
MessageRetentionPeriod: 1209600
RedrivePolicy:
deadLetterTargetArn: !GetAtt EmailNotificationDeadLetterQueue.Arn
maxReceiveCount: 3
VisibilityTimeout: 180
DependsOn: EmailNotificationDeadLetterQueue
EmailNotificationQueuePermissions:
Type: AWS::SQS::QueuePolicy
Properties:
Queues:
- !Ref EmailNotificationQueue
PolicyDocument:
Statement:
- Action:
- 'SQS:SendMessage'
Effect: Allow
Resource: !GetAtt EmailNotificationQueue.Arn
Principal:
AWS: '*'
Condition:
ArnLike:
'aws:SourceArn':
- !Ref EmailNotificationTopic
DependsOn: EmailNotificationQueue
EmailReceiver:
Type: AWS::SES::ReceiptRuleSet
Properties:
RuleSetName: !Join ['-', ['loft', !Ref LoftDomain, 'rule-set']]
EmailReceiverCatchAllRule:
Type: AWS::SES::ReceiptRule
Properties:
RuleSetName: !Join ['-', ['loft', !Ref LoftDomain, 'rule-set']]
Rule:
Name: !Join ['-', [!Ref LoftDomain, 'catch-all']]
Enabled: true
ScanEnabled: true
Recipients:
- !Ref LoftDomain
Actions:
- S3Action:
BucketName: !Ref EmailStorage
ObjectKeyPrefix: 'inbox/'
TopicArn: !Ref EmailNotificationTopic
- StopAction:
Scope: RuleSet
DependsOn:
- EmailReceiver
- EmailStorage
- EmailStoragePermissions
- EmailNotificationTopic
EmailHandlerExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Join ['', ['Custom.AWS.Lambda.Loft-', !Ref LoftDomain]]
Description: Loft email handler lambda execution role
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AWSLambda_FullAccess
- arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess
- arn:aws:iam::aws:policy/AmazonS3FullAccess
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- 'sts:AssumeRole'
Policies:
- PolicyName: !Join ['', ['Custom.AWS.SQS.ReceiveMessage-', !Ref LoftDomain]]
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- sqs:DeleteMessage
- sqs:GetQueueUrl
- sqs:ListDeadLetterSourceQueues
- sqs:SendMessage
- sqs:ReceiveMessage
- sqs:GetQueueAttributes
- sqs:ListQueueTags
Resource: "*"
EmailHandler:
Type: 'AWS::Serverless::Function'
Properties:
Handler: Loft.Function::Loft.Function.LambdaFunction::FunctionHandler
Runtime: dotnet6
CodeUri: ./src/Loft.Function
Description: 'Stores and syncs incoming SES mail to an IMAP mailbox'
MemorySize: 128
Timeout: 60
Role: !GetAtt EmailHandlerExecutionRole.Arn
DeadLetterQueue:
TargetArn: !GetAtt EmailNotificationDeadLetterQueue.Arn
Type: SQS
Events:
SQSTrigger:
Type: SQS
Properties:
Queue: !GetAtt EmailNotificationQueue.Arn
Environment:
Variables:
LoftDomain: !Ref LoftDomain
DynamoDbTableName: !Ref EmailTable
IMAPServer: !Ref IMAPServer
IMAPUsername: !Ref IMAPUsername
IMAPPassword: !Ref IMAPPassword
IMAPDestinationFolder: !Ref IMAPDestinationFolder
IMAPMarkAsRead: !Ref IMAPMarkAsRead
IMAPDebugLogging: !Ref IMAPDebugLogging
DependsOn:
- EmailHandlerExecutionRole
- EmailTable
- EmailNotificationQueue
- EmailNotificationDeadLetterQueue
CommandsHandler:
Type: 'AWS::Serverless::Function'
Properties:
Handler: Loft.Function::Loft.Function.ControlFunction::CommandHandler
Runtime: dotnet6
CodeUri: ./src/Loft.Function
Description: 'Executes commands as they are submitted into a special queue'
MemorySize: 256
Timeout: 180
Role: !GetAtt EmailHandlerExecutionRole.Arn
Events:
SQSTrigger:
Type: SQS
Properties:
Queue: !GetAtt CommandsQueue.Arn
Environment:
Variables:
DynamoDbTableName: !Ref EmailTable
DependsOn:
- EmailHandlerExecutionRole
- EmailTable
- CommandsQueue
- EmailNotificationDeadLetterQueue
EmailTable:
Type: 'AWS::DynamoDB::Table'
Properties:
TableName: !Join ['-', ['loft', Fn::Join: ['_', Fn::Split: ['.', !Ref LoftDomain]]]]
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: id
AttributeType: S
- AttributeName: timestamp
AttributeType: S
- AttributeName: source
AttributeType: S
- AttributeName: destination
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
- AttributeName: timestamp
KeyType: RANGE
GlobalSecondaryIndexes:
- IndexName: source-address-index
KeySchema:
- AttributeName: source
KeyType: HASH
Projection:
ProjectionType: ALL
- IndexName: destination-address-index
KeySchema:
- AttributeName: destination
KeyType: HASH
Projection:
ProjectionType: ALL