Skip to content

Bug: SES parser model and data class reject emails without To or Subject, and RequestResponse actions #8767

Open

Description

@Manishram-ai

Expected Behaviour

SesModel and the SESEvent data class accept the events SES sends to a Lambda receipt rule action, as described in Contents of notifications for Amazon SES email receiving.

Current Behaviour

Two kinds of real events fail:

  • receipt.action.invocationType only allows Event. The SES docs list RequestResponse and Event, so a receipt rule that invokes the function synchronously fails with a ValidationError.
  • Every mail.commonHeaders field except cc, bcc, sender and replyTo is required. The docs say the fields in that object "vary depending on which fields were present in the incoming email". So an email sent only to BCC recipients has no to, an email without a subject has no subject, and both fail to parse.

The SESEvent data class has the same problem on reads. common_headers.to, get_from, subject, date, message_id, return_path and receipt.action.invocation_type raise KeyError when the field isn't in the event.

The TypeScript parser fixed the same thing in aws-powertools/powertools-lambda-typescript#5772 (issue aws-powertools/powertools-lambda-typescript#5771). Its issue also has a captured receipt event with no invocationType at all. The replyTo part of that fix already landed here in #8525, and the Python model doesn't require dmarcPolicy, so only the two points above are left.

Code snippet

from aws_lambda_powertools.utilities.parser import parse
from aws_lambda_powertools.utilities.parser.models import SesModel

# Lambda action invoked synchronously, for an email sent only to BCC recipients with no subject
event = {
    "Records": [
        {
            "eventSource": "aws:ses",
            "eventVersion": "1.0",
            "ses": {
                "mail": {
                    "timestamp": "2026-11-03T12:00:00.000Z",
                    "source": "janedoe@example.com",
                    "messageId": "o3vrnil0e2ic28trm7dfhrc2v0clambda4nbp0g1x",
                    "destination": ["johndoe@example.com"],
                    "headersTruncated": False,
                    "headers": [],
                    "commonHeaders": {
                        "returnPath": "janedoe@example.com",
                        "from": ["Jane Doe <janedoe@example.com>"],
                        "date": "Mon, 3 Nov 2026 12:00:00 +0000",
                        "messageId": "<0123456789example.com>",
                    },
                },
                "receipt": {
                    "timestamp": "2026-11-03T12:00:00.000Z",
                    "processingTimeMillis": 574,
                    "recipients": ["johndoe@example.com"],
                    "spamVerdict": {"status": "PASS"},
                    "virusVerdict": {"status": "PASS"},
                    "spfVerdict": {"status": "PASS"},
                    "dkimVerdict": {"status": "PASS"},
                    "dmarcVerdict": {"status": "PASS"},
                    "action": {
                        "type": "Lambda",
                        "invocationType": "RequestResponse",
                        "functionArn": "arn:aws:lambda:us-east-1:123456789012:function:Example",
                    },
                },
            },
        }
    ]
}

parse(event=event, model=SesModel)
pydantic_core._pydantic_core.ValidationError: 3 validation errors for SesModel
Records.0.ses.mail.commonHeaders.to
  Field required [type=missing, input_value={'returnPath': 'janedoe@e...0123456789example.com>'}, input_type=dict]
Records.0.ses.mail.commonHeaders.subject
  Field required [type=missing, input_value={'returnPath': 'janedoe@e...0123456789example.com>'}, input_type=dict]
Records.0.ses.receipt.action.invocationType
  Input should be 'Event' [type=literal_error, input_value='RequestResponse', input_type=str]

Possible Solution

  • allow RequestResponse in invocationType and make it optional
  • make the commonHeaders fields optional
  • in the data class, return [] for missing list headers and None for missing string fields, the way cc and dmarc_policy already do

I have a PR ready with tests.

Steps to Reproduce

  1. pip install "aws-lambda-powertools[parser]==3.39.0"
  2. Run the snippet above.

Did you run the reproduction yourself?

Yes, I ran it and saw the failure

Powertools for AWS Lambda (Python) version

3.39.0

AWS Lambda function runtime

3.12 (ran locally)

Packaging format used

PyPi

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions