Skip to content

Commit 797a10a

Browse files
ran-isenbergRan Isenberg
and
Ran Isenberg
authored
fix(parser): Add missing fields for SESEvent (aws-powertools#1027)
Co-authored-by: Ran Isenberg <[email protected]>
1 parent 11c55b9 commit 797a10a

File tree

4 files changed

+206
-8
lines changed

4 files changed

+206
-8
lines changed

aws_lambda_powertools/utilities/parser/models/__init__.py

+8
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@
3737
SesModel,
3838
SesReceipt,
3939
SesReceiptAction,
40+
SesReceiptActionBase,
41+
SesReceiptBounceAction,
42+
SesReceiptS3Action,
4043
SesReceiptVerdict,
44+
SesReceiptWorkmailAction,
4145
SesRecordModel,
4246
)
4347
from .sns import SnsModel, SnsNotificationModel, SnsRecordModel
@@ -84,6 +88,10 @@
8488
"SesMailHeaders",
8589
"SesReceipt",
8690
"SesReceiptAction",
91+
"SesReceiptActionBase",
92+
"SesReceiptBounceAction",
93+
"SesReceiptWorkmailAction",
94+
"SesReceiptS3Action",
8795
"SesReceiptVerdict",
8896
"SnsModel",
8997
"SnsNotificationModel",

aws_lambda_powertools/utilities/parser/models/ses.py

+31-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from datetime import datetime
2-
from typing import List, Optional
2+
from typing import List, Optional, Union
33

44
from pydantic import BaseModel, Field
55
from pydantic.networks import EmailStr
@@ -12,21 +12,49 @@ class SesReceiptVerdict(BaseModel):
1212
status: Literal["PASS", "FAIL", "GRAY", "PROCESSING_FAILED"]
1313

1414

15-
class SesReceiptAction(BaseModel):
15+
class SesReceiptActionBase(BaseModel):
16+
topicArn: Optional[str]
17+
18+
19+
class SesReceiptAction(SesReceiptActionBase):
1620
type: Literal["Lambda"] # noqa A003,VNE003
1721
invocationType: Literal["Event"]
1822
functionArn: str
1923

2024

25+
class SesReceiptS3Action(SesReceiptActionBase):
26+
type: Literal["S3"] # noqa A003,VNE003
27+
topicArn: str
28+
bucketName: str
29+
objectKey: str
30+
31+
32+
class SesReceiptBounceAction(SesReceiptActionBase):
33+
type: Literal["Bounce"] # noqa A003,VNE003
34+
topicArn: str
35+
smtpReplyCode: str
36+
message: str
37+
sender: str
38+
statusCode: str
39+
40+
41+
class SesReceiptWorkmailAction(SesReceiptActionBase):
42+
type: Literal["WorkMail"] # noqa A003,VNE003
43+
topicArn: str
44+
organizationArn: str
45+
46+
2147
class SesReceipt(BaseModel):
2248
timestamp: datetime
2349
processingTimeMillis: PositiveInt
2450
recipients: List[EmailStr]
2551
spamVerdict: SesReceiptVerdict
2652
virusVerdict: SesReceiptVerdict
2753
spfVerdict: SesReceiptVerdict
54+
dkimVerdict: SesReceiptVerdict
2855
dmarcVerdict: SesReceiptVerdict
29-
action: SesReceiptAction
56+
dmarcPolicy: Optional[Literal["quarantine", "reject", "none"]]
57+
action: Union[SesReceiptAction, SesReceiptS3Action, SesReceiptBounceAction, SesReceiptWorkmailAction]
3058

3159

3260
class SesMailHeaders(BaseModel):

tests/events/sesEventS3.json

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
{
2+
"Records": [
3+
{
4+
"eventVersion": "1.0",
5+
"ses": {
6+
"receipt": {
7+
"timestamp": "2015-09-11T20:32:33.936Z",
8+
"processingTimeMillis": 406,
9+
"recipients": [
10+
11+
],
12+
"spamVerdict": {
13+
"status": "PASS"
14+
},
15+
"virusVerdict": {
16+
"status": "PASS"
17+
},
18+
"spfVerdict": {
19+
"status": "PASS"
20+
},
21+
"dkimVerdict": {
22+
"status": "PASS"
23+
},
24+
"dmarcVerdict": {
25+
"status": "PASS"
26+
},
27+
"dmarcPolicy": "reject",
28+
"action": {
29+
"type": "S3",
30+
"topicArn": "arn:aws:sns:us-east-1:012345678912:example-topic",
31+
"bucketName": "my-S3-bucket",
32+
"objectKey": "email"
33+
}
34+
},
35+
"mail": {
36+
"timestamp": "2015-09-11T20:32:33.936Z",
37+
"source": "0000014fbe1c09cf-7cb9f704-7531-4e53-89a1-5fa9744f5eb6-000000@amazonses.com",
38+
"messageId": "d6iitobk75ur44p8kdnnp7g2n800",
39+
"destination": [
40+
41+
],
42+
"headersTruncated": false,
43+
"headers": [
44+
{
45+
"name": "Return-Path",
46+
"value": "<0000014fbe1c09cf-7cb9f704-7531-4e53-89a1-5fa9744f5eb6-000000@amazonses.com>"
47+
},
48+
{
49+
"name": "Received",
50+
"value": "from a9-183.smtp-out.amazonses.com (a9-183.smtp-out.amazonses.com [54.240.9.183]) by inbound-smtp.us-east-1.amazonaws.com with SMTP id d6iitobk75ur44p8kdnnp7g2n800 for [email protected]; Fri, 11 Sep 2015 20:32:33 +0000 (UTC)"
51+
},
52+
{
53+
"name": "DKIM-Signature",
54+
"value": "v=1; a=rsa-sha256; q=dns/txt; c=relaxed/simple; s=ug7nbtf4gccmlpwj322ax3p6ow6yfsug; d=amazonses.com; t=1442003552; h=From:To:Subject:MIME-Version:Content-Type:Content-Transfer-Encoding:Date:Message-ID:Feedback-ID; bh=DWr3IOmYWoXCA9ARqGC/UaODfghffiwFNRIb2Mckyt4=; b=p4ukUDSFqhqiub+zPR0DW1kp7oJZakrzupr6LBe6sUuvqpBkig56UzUwc29rFbJF hlX3Ov7DeYVNoN38stqwsF8ivcajXpQsXRC1cW9z8x875J041rClAjV7EGbLmudVpPX 4hHst1XPyX5wmgdHIhmUuh8oZKpVqGi6bHGzzf7g="
55+
},
56+
{
57+
"name": "From",
58+
"value": "[email protected]"
59+
},
60+
{
61+
"name": "To",
62+
"value": "[email protected]"
63+
},
64+
{
65+
"name": "Subject",
66+
"value": "Example subject"
67+
},
68+
{
69+
"name": "MIME-Version",
70+
"value": "1.0"
71+
},
72+
{
73+
"name": "Content-Type",
74+
"value": "text/plain; charset=UTF-8"
75+
},
76+
{
77+
"name": "Content-Transfer-Encoding",
78+
"value": "7bit"
79+
},
80+
{
81+
"name": "Date",
82+
"value": "Fri, 11 Sep 2015 20:32:32 +0000"
83+
},
84+
{
85+
"name": "Message-ID",
86+
"value": "<[email protected]>"
87+
},
88+
{
89+
"name": "X-SES-Outgoing",
90+
"value": "2015.09.11-54.240.9.183"
91+
},
92+
{
93+
"name": "Feedback-ID",
94+
"value": "1.us-east-1.Krv2FKpFdWV+KUYw3Qd6wcpPJ4Sv/pOPpEPSHn2u2o4=:AmazonSES"
95+
}
96+
],
97+
"commonHeaders": {
98+
"returnPath": "0000014fbe1c09cf-7cb9f704-7531-4e53-89a1-5fa9744f5eb6-000000@amazonses.com",
99+
"from": [
100+
101+
],
102+
"date": "Fri, 11 Sep 2015 20:32:32 +0000",
103+
"to": [
104+
105+
],
106+
"messageId": "<[email protected]>",
107+
"subject": "Example subject"
108+
}
109+
}
110+
},
111+
"eventSource": "aws:ses"
112+
}
113+
]
114+
}

tests/functional/parser/test_ses.py

+53-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
11
from aws_lambda_powertools.utilities.parser import event_parser
2-
from aws_lambda_powertools.utilities.parser.models import SesModel, SesRecordModel
2+
from aws_lambda_powertools.utilities.parser.models import (
3+
SesModel,
4+
SesReceiptBounceAction,
5+
SesReceiptWorkmailAction,
6+
SesRecordModel,
7+
)
38
from aws_lambda_powertools.utilities.typing import LambdaContext
49
from tests.functional.utils import load_event
510

611

712
@event_parser(model=SesModel)
8-
def handle_ses(event: SesModel, _: LambdaContext):
13+
def handle_ses(event: SesModel, _: LambdaContext) -> SesModel:
14+
return event
15+
16+
17+
def test_ses_trigger_lambda_event():
18+
event_dict = load_event("sesEvent.json")
19+
event = handle_ses(event_dict, LambdaContext())
920
expected_address = "[email protected]"
1021
records = event.Records
1122
record: SesRecordModel = records[0]
@@ -29,6 +40,10 @@ def handle_ses(event: SesModel, _: LambdaContext):
2940
assert common_headers.to == [expected_address]
3041
assert common_headers.messageId == "<0123456789example.com>"
3142
assert common_headers.subject == "Test Subject"
43+
assert common_headers.cc is None
44+
assert common_headers.bcc is None
45+
assert common_headers.sender is None
46+
assert common_headers.reply_to is None
3247
receipt = record.ses.receipt
3348
convert_time = int(round(receipt.timestamp.timestamp() * 1000))
3449
assert convert_time == 0
@@ -38,12 +53,45 @@ def handle_ses(event: SesModel, _: LambdaContext):
3853
assert receipt.virusVerdict.status == "PASS"
3954
assert receipt.spfVerdict.status == "PASS"
4055
assert receipt.dmarcVerdict.status == "PASS"
56+
assert receipt.dmarcVerdict.status == "PASS"
57+
assert receipt.dmarcPolicy is None
4158
action = receipt.action
4259
assert action.type == "Lambda"
4360
assert action.functionArn == "arn:aws:lambda:us-west-2:012345678912:function:Example"
4461
assert action.invocationType == "Event"
62+
assert action.topicArn is None
4563

4664

47-
def test_ses_trigger_event():
48-
event_dict = load_event("sesEvent.json")
49-
handle_ses(event_dict, LambdaContext())
65+
def test_ses_trigger_event_s3():
66+
event_dict = load_event("sesEventS3.json")
67+
event = handle_ses(event_dict, LambdaContext())
68+
records = list(event.Records)
69+
record = records[0]
70+
receipt = record.ses.receipt
71+
assert receipt.dmarcPolicy == "reject"
72+
action = record.ses.receipt.action
73+
assert action.type == "S3"
74+
assert action.topicArn == "arn:aws:sns:us-east-1:012345678912:example-topic"
75+
assert action.bucketName == "my-S3-bucket"
76+
assert action.objectKey == "email"
77+
78+
79+
def test_ses_trigger_event_bounce():
80+
event_dict = {
81+
"type": "Bounce",
82+
"topicArn": "arn:aws:sns:us-east-1:123456789012:topic:my-topic",
83+
"smtpReplyCode": "5.1.1",
84+
"message": "message",
85+
"sender": "sender",
86+
"statusCode": "550",
87+
}
88+
SesReceiptBounceAction(**event_dict)
89+
90+
91+
def test_ses_trigger_event_work_mail():
92+
event_dict = {
93+
"type": "WorkMail",
94+
"topicArn": "arn:aws:sns:us-east-1:123456789012:topic:my-topic",
95+
"organizationArn": "arn",
96+
}
97+
SesReceiptWorkmailAction(**event_dict)

0 commit comments

Comments
 (0)