AWS Messaging & Targeting Blog

Introducing the AWS Lambda Blueprint for Filtering Emails Received Through Amazon SES

When we set out to build an email receiving service for AWS, we wanted to make receiving email easy, but we also wanted to empower you to make your own decisions about how your inbound emails should be handled. With the goal of striking a balance between ease of use and flexibility, we integrated SES with AWS Lambda, which allows you to easily define your own filter function with arbitrary logic.

Today, we’ll discuss how you can build a simple customized filter function using the new AWS Lambda blueprint for filtering inbound email.

Defining your email filter

When you log into the AWS Lambda console and select “Create a Lambda Function,” you are presented with a list of blueprints you can use. One of these blueprints is called “inbound-ses-spam-filter”—we’ll use this blueprint in our example as a springboard to jumpstart our custom filter function.

Spam filter Lambda blueprint

The blueprint is simple: it checks whether the email has passed SPF and DKIM validations, and whether the email is deemed spam or virus. If an email fails one or more of these tests, the message is bounced using a SendBounce call to SES; otherwise, the rule engine is instructed to continue processing the message.

Once you’ve selected the blueprint, you can customize it to fit your individual needs. First, you’ll need to fill in your domain as the bounce sender:

var sendBounceParams = {
  BounceSender: 'mailer-daemon@<MYDOMAIN>.com',
  OriginalMessageId: messageId,
  MessageDsn: {
    ReportingMta: 'dns; <MYDOMAIN>.com',
    ArrivalDate: new Date(),
    ExtensionFields: []
  },
  BouncedRecipientInfoList: []
};

The heart of the filtering logic, however, lies in the following if statement:

if (receipt.spfVerdict.status === 'FAIL' ||
        receipt.dkimVerdict.status === 'FAIL' ||
        receipt.spamVerdict.status === 'FAIL' ||
        receipt.virusVerdict.status === 'FAIL')

You can modify this conditional statement to fit your use case. Perhaps there’s a particular sender you want to block, or you noticed a pattern of spam with a particular header. For this example, we’ll bounce all emails with the subject line “Buy stuff!”

var mailMetadata = sesNotification.mail;

if (receipt.spfVerdict.status === 'FAIL' ||
        receipt.dkimVerdict.status === 'FAIL' ||
        receipt.spamVerdict.status === 'FAIL' ||
        receipt.virusVerdict.status === 'FAIL' ||
        mailMetadata.commonHeaders.subject === 'Buy stuff!')

The filter function will be invoked synchronously to ensure that it can return a disposition value back to SES, which will then use this value to decide whether to continue processing the message. This particular filter function returns “stop_rule_set” as the disposition after bouncing the message, effectively instructing SES to stop processing the message once the Lambda function returns.

context.succeed({
  disposition: 'stop_rule_set'
});

Check out our documentation for more information about disposition values.

Because the filter function uses SES’s SendBounce API, you need to make sure that your Lambda function’s execution IAM role is allowed to call SendBounce. You can use the following example policy document, which allows your filter function to call SendBounce for any verified domain:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ses:SendBounce"
      ],
      "Resource": "*"
    }
  ]
}

Configuring your email filter

Now that you’ve defined your filter function, you need to configure it to process your inbound email stream. To do this, you can create an SES receipt rule that synchronously invokes an AWS Lambda function, which can be done either through the SES console or by using the CreateReceiptRule API (see our documentation to find out more about creating receipt rules). For this example, we’ll use the console’s receipt rule wizard to create our filter rule.

Lambda action

Note that the invocation type must be set to RequestResponse (synchronous) so that your filter function can decide whether or not the message should be filtered out.

In order to prevent your existing rules from being evaluated for unwanted emails, you need to make sure that your filter rule is processed before any of your processing rules. You can do this by setting the position of your new rule in Step 3 of the receipt rule wizard.

Rule position

Once you’ve created your filter rule, it will be shown at position 1 of your rule set:

Rule set

Note that the rule was configured without specific recipients, which causes the rule to be evaluated for all your inbound emails regardless of the intended recipient. You can always limit the scope of your filter rule by configuring specific recipients to match.

Testing your email filter

You can now test your filter function by sending an email to the address you’re listening in on. To simulate spam or virus, you can use GTUBE or EICAR, respectively, as the message content.

Receiving email programmatically has traditionally been a complex task, due to the open nature of email and the abuse vectors that come with it. We hope that you’ll find the new AWS Lambda blueprint for filtering inbound email to be a useful starting point to help you deal with unwanted mail and manage your operating costs.

Happy hacking!