Skip to content

feat(data_classes): Add missing Bounce, S3 and WorkMail for SESEvent #1026

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 100 additions & 3 deletions aws_lambda_powertools/utilities/data_classes/ses_event.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Iterator, List
from typing import Iterator, List, Optional

from aws_lambda_powertools.utilities.data_classes.common import DictWrapper

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

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

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

@property
def cc(self) -> Optional[List[str]]:
"""The values in the CC header of the email."""
return self.get("cc")

@property
def bcc(self) -> Optional[List[str]]:
"""The values in the BCC header of the email."""
return self.get("bcc")

@property
def sender(self) -> Optional[List[str]]:
"""The values in the Sender header of the email."""
return self.get("sender")

@property
def reply_to(self) -> Optional[List[str]]:
"""The values in the replyTo header of the email."""
return self.get("replyTo")


class SESMail(DictWrapper):
@property
Expand Down Expand Up @@ -94,6 +114,10 @@ def common_headers(self) -> SESMailCommonHeaders:
class SESReceiptStatus(DictWrapper):
@property
def status(self) -> str:
"""Receipt status

Possible values: 'PASS', 'FAIL', 'GRAY', 'PROCESSING_FAILED', 'DISABLED'
"""
return str(self["status"])


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

@property
def topic_arn(self) -> Optional[str]:
"""String that contains the Amazon Resource Name (ARN) of the Amazon SNS topic to which the
notification was published."""
return self.get("topicArn")

@property
def function_arn(self) -> str:
"""String that contains the ARN of the Lambda function that was triggered.

Present only for the Lambda action type."""
return self["functionArn"]

@property
def invocation_type(self) -> str:
"""String that contains the invocation type of the Lambda function. Possible values are RequestResponse
and Event. Present only for the Lambda action type."""
and Event.

Present only for the Lambda action type."""
return self["invocationType"]

@property
def bucket_name(self) -> str:
"""String that contains the name of the Amazon S3 bucket to which the message was published.

Present only for the S3 action type."""
return str(self["bucketName"])

@property
def object_key(self) -> str:
"""String that contains a name that uniquely identifies the email in the Amazon S3 bucket.
This is the same as the messageId in the mail object.

Present only for the S3 action type."""
return str(self["objectKey"])

@property
def smtp_reply_code(self) -> str:
"""String that contains the SMTP reply code, as defined by RFC 5321.

Present only for the bounce action type."""
return str(self["smtpReplyCode"])

@property
def status_code(self) -> str:
"""String that contains the SMTP enhanced status code, as defined by RFC 3463.

Present only for the bounce action type."""
return self["statusCode"]

@property
def message(self) -> str:
"""String that contains the human-readable text to include in the bounce message.

Present only for the bounce action type."""
return str(self["message"])

@property
def sender(self) -> str:
"""String that contains the email address of the sender of the email that bounced.
This is the address from which the bounce message was sent.

Present only for the bounce action type."""
return str(self["sender"])

@property
def organization_arn(self) -> str:
"""String that contains the ARN of the Amazon WorkMail organization.

Present only for the WorkMail action type."""
return str(self["organizationArn"])


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

@property
def dkim_verdict(self) -> SESReceiptStatus:
"""Object that indicates whether the DomainKeys Identified Mail (DKIM) check passed"""
return SESReceiptStatus(self["dkimVerdict"])

@property
def dmarc_verdict(self) -> SESReceiptStatus:
"""Object that indicates whether the Domain-based Message Authentication,
Reporting & Conformance (DMARC) check passed."""
return SESReceiptStatus(self["dmarcVerdict"])

@property
def dmarc_policy(self) -> Optional[str]:
"""Indicates the Domain-based Message Authentication, Reporting & Conformance (DMARC) settings for
the sending domain. This field only appears if the message fails DMARC authentication.

Possible values for this field are: none, quarantine, reject"""
return self.get("dmarcPolicy")

@property
def action(self) -> SESReceiptAction:
"""Object that encapsulates information about the action that was executed."""
Expand Down
114 changes: 114 additions & 0 deletions tests/events/sesEventS3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
{
"Records": [
{
"eventVersion": "1.0",
"ses": {
"receipt": {
"timestamp": "2015-09-11T20:32:33.936Z",
"processingTimeMillis": 406,
"recipients": [
"recipient@example.com"
],
"spamVerdict": {
"status": "PASS"
},
"virusVerdict": {
"status": "PASS"
},
"spfVerdict": {
"status": "PASS"
},
"dkimVerdict": {
"status": "PASS"
},
"dmarcVerdict": {
"status": "PASS"
},
"dmarcPolicy": "reject",
"action": {
"type": "S3",
"topicArn": "arn:aws:sns:us-east-1:012345678912:example-topic",
"bucketName": "my-S3-bucket",
"objectKey": "email"
}
},
"mail": {
"timestamp": "2015-09-11T20:32:33.936Z",
"source": "0000014fbe1c09cf-7cb9f704-7531-4e53-89a1-5fa9744f5eb6-000000@amazonses.com",
"messageId": "d6iitobk75ur44p8kdnnp7g2n800",
"destination": [
"recipient@example.com"
],
"headersTruncated": false,
"headers": [
{
"name": "Return-Path",
"value": "<0000014fbe1c09cf-7cb9f704-7531-4e53-89a1-5fa9744f5eb6-000000@amazonses.com>"
},
{
"name": "Received",
"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)"
},
{
"name": "DKIM-Signature",
"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="
},
{
"name": "From",
"value": "sender@example.com"
},
{
"name": "To",
"value": "recipient@example.com"
},
{
"name": "Subject",
"value": "Example subject"
},
{
"name": "MIME-Version",
"value": "1.0"
},
{
"name": "Content-Type",
"value": "text/plain; charset=UTF-8"
},
{
"name": "Content-Transfer-Encoding",
"value": "7bit"
},
{
"name": "Date",
"value": "Fri, 11 Sep 2015 20:32:32 +0000"
},
{
"name": "Message-ID",
"value": "<61967230-7A45-4A9D-BEC9-87CBCF2211C9@example.com>"
},
{
"name": "X-SES-Outgoing",
"value": "2015.09.11-54.240.9.183"
},
{
"name": "Feedback-ID",
"value": "1.us-east-1.Krv2FKpFdWV+KUYw3Qd6wcpPJ4Sv/pOPpEPSHn2u2o4=:AmazonSES"
}
],
"commonHeaders": {
"returnPath": "0000014fbe1c09cf-7cb9f704-7531-4e53-89a1-5fa9744f5eb6-000000@amazonses.com",
"from": [
"sender@example.com"
],
"date": "Fri, 11 Sep 2015 20:32:32 +0000",
"to": [
"recipient@example.com"
],
"messageId": "<61967230-7A45-4A9D-BEC9-87CBCF2211C9@example.com>",
"subject": "Example subject"
}
}
},
"eventSource": "aws:ses"
}
]
}
52 changes: 52 additions & 0 deletions tests/functional/test_data_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
)
from aws_lambda_powertools.utilities.data_classes.event_source import event_source
from aws_lambda_powertools.utilities.data_classes.s3_object_event import S3ObjectLambdaEvent
from aws_lambda_powertools.utilities.data_classes.ses_event import SESReceiptAction
from tests.functional.utils import load_event


Expand Down Expand Up @@ -691,6 +692,10 @@ def test_ses_trigger_event():
assert common_headers.to == [expected_address]
assert common_headers.message_id == "<0123456789example.com>"
assert common_headers.subject == "Test Subject"
assert common_headers.cc is None
assert common_headers.bcc is None
assert common_headers.sender is None
assert common_headers.reply_to is None
receipt = record.ses.receipt
assert receipt.timestamp == "1970-01-01T00:00:00.000Z"
assert receipt.processing_time_millis == 574
Expand All @@ -699,15 +704,62 @@ def test_ses_trigger_event():
assert receipt.virus_verdict.status == "PASS"
assert receipt.spf_verdict.status == "PASS"
assert receipt.dmarc_verdict.status == "PASS"
assert receipt.dkim_verdict.status == "PASS"
assert receipt.dmarc_policy is None
action = receipt.action
assert action.get_type == action.raw_event["type"]
assert action.function_arn == action.raw_event["functionArn"]
assert action.invocation_type == action.raw_event["invocationType"]
assert action.topic_arn is None
assert event.record.raw_event == event["Records"][0]
assert event.mail.raw_event == event["Records"][0]["ses"]["mail"]
assert event.receipt.raw_event == event["Records"][0]["ses"]["receipt"]


def test_ses_trigger_event_s3():
event = SESEvent(load_event("sesEventS3.json"))
records = list(event.records)
record = records[0]
receipt = record.ses.receipt
assert receipt.dmarc_policy == "reject"
action = record.ses.receipt.action
assert action.get_type == "S3"
assert action.topic_arn == "arn:aws:sns:us-east-1:012345678912:example-topic"
assert action.bucket_name == "my-S3-bucket"
assert action.object_key == "email"


def test_ses_trigger_event_bounce():
action = SESReceiptAction(
{
"type": "Bounce",
"topicArn": "arn:aws:sns:us-east-1:123456789012:topic:my-topic",
"smtpReplyCode": "5.1.1",
"message": "message",
"sender": "sender",
"statusCode": "550",
}
)
assert action.get_type == action["type"]
assert action.topic_arn == action["topicArn"]
assert action.smtp_reply_code == action["smtpReplyCode"]
assert action.message == action["message"]
assert action.sender == action["sender"]
assert action.status_code == action["statusCode"]


def test_ses_trigger_event_work_mail():
action = SESReceiptAction(
{
"type": "WorkMail",
"topicArn": "arn:aws:sns:us-east-1:123456789012:topic:my-topic",
"organizationArn": "arn",
}
)
assert action.get_type == "WorkMail"
assert action.organization_arn == action["organizationArn"]


def test_sns_trigger_event():
event = SNSEvent(load_event("snsEvent.json"))
records = list(event.records)
Expand Down