Skip to content

Commit ebbff89

Browse files
author
Michael Brewer
committed
fix(data_classes): Add missing fields for SESEvent
Change: - Add missing fields from SESMailCommonHeaders - Fix date type to be a str - Add missing docs for SESReceiptStatus status field - Add missing topicArn - Add missing fields for Bounce, S3 and WorkMail actions close #1025
1 parent 77f86c0 commit ebbff89

File tree

3 files changed

+266
-3
lines changed

3 files changed

+266
-3
lines changed

aws_lambda_powertools/utilities/data_classes/ses_event.py

Lines changed: 100 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Iterator, List
1+
from typing import Iterator, List, Optional
22

33
from aws_lambda_powertools.utilities.data_classes.common import DictWrapper
44

@@ -26,7 +26,7 @@ def get_from(self) -> List[str]:
2626
return self["from"]
2727

2828
@property
29-
def date(self) -> List[str]:
29+
def date(self) -> str:
3030
"""The date and time when Amazon SES received the message."""
3131
return self["date"]
3232

@@ -45,6 +45,26 @@ def subject(self) -> str:
4545
"""The value of the Subject header for the email."""
4646
return str(self["subject"])
4747

48+
@property
49+
def cc(self) -> Optional[List[str]]:
50+
"""The values in the CC header of the email."""
51+
return self.get("cc")
52+
53+
@property
54+
def bcc(self) -> Optional[List[str]]:
55+
"""The values in the BCC header of the email."""
56+
return self.get("bcc")
57+
58+
@property
59+
def sender(self) -> Optional[List[str]]:
60+
"""The values in the Sender header of the email."""
61+
return self.get("sender")
62+
63+
@property
64+
def reply_to(self) -> Optional[List[str]]:
65+
"""The values in the replyTo header of the email."""
66+
return self.get("replyTo")
67+
4868

4969
class SESMail(DictWrapper):
5070
@property
@@ -94,6 +114,10 @@ def common_headers(self) -> SESMailCommonHeaders:
94114
class SESReceiptStatus(DictWrapper):
95115
@property
96116
def status(self) -> str:
117+
"""Receipt status
118+
119+
Possible values: 'PASS', 'FAIL', 'GRAY', 'PROCESSING_FAILED', 'DISABLED'
120+
"""
97121
return str(self["status"])
98122

99123

@@ -107,18 +131,78 @@ def get_type(self) -> str:
107131
# Note: this name conflicts with existing python builtins
108132
return self["type"]
109133

134+
@property
135+
def topic_arn(self) -> Optional[str]:
136+
"""String that contains the Amazon Resource Name (ARN) of the Amazon SNS topic to which the
137+
notification was published."""
138+
return self.get("topicArn")
139+
110140
@property
111141
def function_arn(self) -> str:
112142
"""String that contains the ARN of the Lambda function that was triggered.
143+
113144
Present only for the Lambda action type."""
114145
return self["functionArn"]
115146

116147
@property
117148
def invocation_type(self) -> str:
118149
"""String that contains the invocation type of the Lambda function. Possible values are RequestResponse
119-
and Event. Present only for the Lambda action type."""
150+
and Event.
151+
152+
Present only for the Lambda action type."""
120153
return self["invocationType"]
121154

155+
@property
156+
def bucket_name(self) -> str:
157+
"""String that contains the name of the Amazon S3 bucket to which the message was published.
158+
159+
Present only for the S3 action type."""
160+
return str(self["bucketName"])
161+
162+
@property
163+
def object_key(self) -> str:
164+
"""String that contains a name that uniquely identifies the email in the Amazon S3 bucket.
165+
This is the same as the messageId in the mail object.
166+
167+
Present only for the S3 action type."""
168+
return str(self["objectKey"])
169+
170+
@property
171+
def smtp_reply_code(self) -> str:
172+
"""String that contains the SMTP reply code, as defined by RFC 5321.
173+
174+
Present only for the bounce action type."""
175+
return str(self["smtpReplyCode"])
176+
177+
@property
178+
def status_code(self) -> str:
179+
"""String that contains the SMTP enhanced status code, as defined by RFC 3463.
180+
181+
Present only for the bounce action type."""
182+
return self["statusCode"]
183+
184+
@property
185+
def message(self) -> str:
186+
"""String that contains the human-readable text to include in the bounce message.
187+
188+
Present only for the bounce action type."""
189+
return str(self["message"])
190+
191+
@property
192+
def sender(self) -> str:
193+
"""String that contains the email address of the sender of the email that bounced.
194+
This is the address from which the bounce message was sent.
195+
196+
Present only for the bounce action type."""
197+
return str(self["sender"])
198+
199+
@property
200+
def organization_arn(self) -> str:
201+
"""String that contains the ARN of the Amazon WorkMail organization.
202+
203+
Present only for the WorkMail action type."""
204+
return str(self["organizationArn"])
205+
122206

123207
class SESReceipt(DictWrapper):
124208
@property
@@ -154,12 +238,25 @@ def spf_verdict(self) -> SESReceiptStatus:
154238
"""Object that indicates whether the Sender Policy Framework (SPF) check passed."""
155239
return SESReceiptStatus(self["spfVerdict"])
156240

241+
@property
242+
def dkim_verdict(self) -> SESReceiptStatus:
243+
"""Object that indicates whether the DomainKeys Identified Mail (DKIM) check passed"""
244+
return SESReceiptStatus(self["dkimVerdict"])
245+
157246
@property
158247
def dmarc_verdict(self) -> SESReceiptStatus:
159248
"""Object that indicates whether the Domain-based Message Authentication,
160249
Reporting & Conformance (DMARC) check passed."""
161250
return SESReceiptStatus(self["dmarcVerdict"])
162251

252+
@property
253+
def dmarc_policy(self) -> Optional[str]:
254+
"""Indicates the Domain-based Message Authentication, Reporting & Conformance (DMARC) settings for
255+
the sending domain. This field only appears if the message fails DMARC authentication.
256+
257+
Possible values for this field are: none, quarantine, reject"""
258+
return self.get("dmarcPolicy")
259+
163260
@property
164261
def action(self) -> SESReceiptAction:
165262
"""Object that encapsulates information about the action that was executed."""

tests/events/sesEventS3.json

Lines changed: 114 additions & 0 deletions
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+
"recipient@example.com"
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+
"recipient@example.com"
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 recipient@example.com; 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": "sender@example.com"
59+
},
60+
{
61+
"name": "To",
62+
"value": "recipient@example.com"
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": "<61967230-7A45-4A9D-BEC9-87CBCF2211C9@example.com>"
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+
"sender@example.com"
101+
],
102+
"date": "Fri, 11 Sep 2015 20:32:32 +0000",
103+
"to": [
104+
"recipient@example.com"
105+
],
106+
"messageId": "<61967230-7A45-4A9D-BEC9-87CBCF2211C9@example.com>",
107+
"subject": "Example subject"
108+
}
109+
}
110+
},
111+
"eventSource": "aws:ses"
112+
}
113+
]
114+
}

tests/functional/test_data_classes.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
)
7777
from aws_lambda_powertools.utilities.data_classes.event_source import event_source
7878
from aws_lambda_powertools.utilities.data_classes.s3_object_event import S3ObjectLambdaEvent
79+
from aws_lambda_powertools.utilities.data_classes.ses_event import SESReceiptAction
7980
from tests.functional.utils import load_event
8081

8182

@@ -691,6 +692,10 @@ def test_ses_trigger_event():
691692
assert common_headers.to == [expected_address]
692693
assert common_headers.message_id == "<0123456789example.com>"
693694
assert common_headers.subject == "Test Subject"
695+
assert common_headers.cc is None
696+
assert common_headers.bcc is None
697+
assert common_headers.sender is None
698+
assert common_headers.reply_to is None
694699
receipt = record.ses.receipt
695700
assert receipt.timestamp == "1970-01-01T00:00:00.000Z"
696701
assert receipt.processing_time_millis == 574
@@ -699,15 +704,62 @@ def test_ses_trigger_event():
699704
assert receipt.virus_verdict.status == "PASS"
700705
assert receipt.spf_verdict.status == "PASS"
701706
assert receipt.dmarc_verdict.status == "PASS"
707+
assert receipt.dkim_verdict.status == "PASS"
708+
assert receipt.dmarc_policy is None
702709
action = receipt.action
703710
assert action.get_type == action.raw_event["type"]
704711
assert action.function_arn == action.raw_event["functionArn"]
705712
assert action.invocation_type == action.raw_event["invocationType"]
713+
assert action.topic_arn is None
706714
assert event.record.raw_event == event["Records"][0]
707715
assert event.mail.raw_event == event["Records"][0]["ses"]["mail"]
708716
assert event.receipt.raw_event == event["Records"][0]["ses"]["receipt"]
709717

710718

719+
def test_ses_trigger_event_s3():
720+
event = SESEvent(load_event("sesEventS3.json"))
721+
records = list(event.records)
722+
record = records[0]
723+
receipt = record.ses.receipt
724+
assert receipt.dmarc_policy == "reject"
725+
action = record.ses.receipt.action
726+
assert action.get_type == "S3"
727+
assert action.topic_arn == "arn:aws:sns:us-east-1:012345678912:example-topic"
728+
assert action.bucket_name == "my-S3-bucket"
729+
assert action.object_key == "email"
730+
731+
732+
def test_ses_trigger_event_bounce():
733+
action = SESReceiptAction(
734+
{
735+
"type": "Bounce",
736+
"topicArn": "arn:aws:sns:us-east-1:123456789012:topic:my-topic",
737+
"smtpReplyCode": "5.1.1",
738+
"message": "message",
739+
"sender": "sender",
740+
"statusCode": "550",
741+
}
742+
)
743+
assert action.get_type == action["type"]
744+
assert action.topic_arn == action["topicArn"]
745+
assert action.smtp_reply_code == action["smtpReplyCode"]
746+
assert action.message == action["message"]
747+
assert action.sender == action["sender"]
748+
assert action.status_code == action["statusCode"]
749+
750+
751+
def test_ses_trigger_event_work_mail():
752+
action = SESReceiptAction(
753+
{
754+
"type": "WorkMail",
755+
"topicArn": "arn:aws:sns:us-east-1:123456789012:topic:my-topic",
756+
"organizationArn": "arn",
757+
}
758+
)
759+
assert action.get_type == "WorkMail"
760+
assert action.organization_arn == action["organizationArn"]
761+
762+
711763
def test_sns_trigger_event():
712764
event = SNSEvent(load_event("snsEvent.json"))
713765
records = list(event.records)

0 commit comments

Comments
 (0)