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 all commits
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
72 changes: 70 additions & 2 deletions aws_lambda_powertools/utilities/data_classes/ses_event.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import Iterator, List, Optional
from enum import Enum
from typing import Dict, Iterator, List, Optional

from aws_lambda_powertools.utilities.data_classes.common import DictWrapper

Expand Down Expand Up @@ -139,15 +140,69 @@ def topic_arn(self) -> Optional[str]:
@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 @@ -260,3 +315,16 @@ def mail(self) -> SESMail:
@property
def receipt(self) -> SESReceipt:
return self.record.ses.receipt


class Disposition(Enum):
STOP_RULE = "STOP_RULE"
"""No further actions in the current receipt rule will be processed, but further receipt rules can be processed."""
STOP_RULE_SET = "STOP_RULE_SET"
"""No further actions or receipt rules will be processed."""
CONTINUE = "CONTINUE"
"""This means that further actions and receipt rules can be processed."""


def disposition_response(disposition: Disposition) -> Dict[str, str]:
return {"disposition": disposition.value}
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": [
"[email protected]"
],
"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": [
"[email protected]"
],
"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 [email protected]; 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": "[email protected]"
},
{
"name": "To",
"value": "[email protected]"
},
{
"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": "<[email protected]>"
},
{
"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": [
"[email protected]"
],
"date": "Fri, 11 Sep 2015 20:32:32 +0000",
"to": [
"[email protected]"
],
"messageId": "<[email protected]>",
"subject": "Example subject"
}
}
},
"eventSource": "aws:ses"
}
]
}
112 changes: 112 additions & 0 deletions tests/events/sesEventSNS.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
{
"Records": [
{
"eventVersion": "1.0",
"ses": {
"receipt": {
"timestamp": "2015-09-11T20:32:33.936Z",
"processingTimeMillis": 222,
"recipients": [
"[email protected]"
],
"spamVerdict": {
"status": "PASS"
},
"virusVerdict": {
"status": "PASS"
},
"spfVerdict": {
"status": "PASS"
},
"dkimVerdict": {
"status": "PASS"
},
"dmarcVerdict": {
"status": "PASS"
},
"dmarcPolicy": "reject",
"action": {
"type": "SNS",
"topicArn": "arn:aws:sns:us-east-1:012345678912:example-topic"
}
},
"mail": {
"timestamp": "2015-09-11T20:32:33.936Z",
"source": "[email protected]",
"messageId": "d6iitobk75ur44p8kdnnp7g2n800",
"destination": [
"[email protected]"
],
"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 [email protected]; 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": "[email protected]"
},
{
"name": "To",
"value": "[email protected]"
},
{
"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": "<[email protected]>"
},
{
"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": [
"[email protected]"
],
"date": "Fri, 11 Sep 2015 20:32:32 +0000",
"to": [
"[email protected]"
],
"messageId": "<[email protected]>",
"subject": "Example subject"
}
}
},
"eventSource": "aws:ses"
}
]
}
59 changes: 59 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 Disposition, SESReceiptAction, disposition_response
from tests.functional.utils import load_event


Expand Down Expand Up @@ -715,6 +716,64 @@ def test_ses_trigger_event():
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_sns():
event = SESEvent(load_event("sesEventSNS.json"))
records = list(event.records)
record = records[0]
action = record.ses.receipt.action
assert action.get_type == "SNS"
assert action.topic_arn == "arn:aws:sns:us-east-1:012345678912:example-topic"


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_ses_disposition_response():
response = disposition_response(Disposition.STOP_RULE_SET)
assert response == {"disposition": "STOP_RULE_SET"}


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