Search code examples
pythonamazon-web-servicesamazon-s3aws-lambdaamazon-ses

PAWS SES Email Receive With S3 And Lambda Function Forward To External Email (Gmail)


I want to send the email received to my site and saved in S3 to external email service in my case Gmail, so here is the steps I did:

  1. Created S3 bucket with this policy:
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowSESPuts",
            "Effect": "Allow",
            "Principal": {
                "Service": "ses.amazonaws.com"
            },
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::<bucketName>/*",
            "Condition": {
                "StringEquals": {
                    "aws:Referer": "<awsAccountId>"
                }
            }
        }
    ]
}
  1. Created IAM user with this Policy:
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:CreateLogGroup",
                "logs:PutLogEvents"
            ],
            "Resource": "*"
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "ses:SendRawEmail"
            ],
            "Resource": [
                "arn:aws:s3:::<bucketName>/*",
                "arn:aws:ses:<region>:<awsAccountId>:identity/*"
            ]
        }
    ]
}
  1. Created this Lambda function in Python 3.7 AND set the Timeout value to 30 seconds:
import json

# Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# This file is licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License. A copy of the
# License is located at
#
# http://aws.amazon.com/apache2.0/
#
# This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
# OF ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.

import os
import boto3
import email
import re
from botocore.exceptions import ClientError
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication

MailS3Bucket = 'name-of-the-bucket'
MailS3Prefix = 'prefix of the send rule (folder where I am saving emails in s3)'
MailSender = 'Verified Email in SES emails'
MailRecipient = 'Verified Email in SES emails too (This is the email I want to send emails to)'
Region = 'my region'

region = os.environ['Region']

def get_message_from_s3(message_id):

    incoming_email_bucket = os.environ['MailS3Bucket']
    incoming_email_prefix = os.environ['MailS3Prefix']

    if incoming_email_prefix:
        object_path = (incoming_email_prefix + "/" + message_id)
    else:
        object_path = message_id

    object_http_path = (f"http://s3.console.aws.amazon.com/s3/object/{incoming_email_bucket}/{object_path}?region={region}")

    # Create a new S3 client.
    client_s3 = boto3.client("s3")

    # Get the email object from the S3 bucket.
    object_s3 = client_s3.get_object(Bucket=incoming_email_bucket, Key=object_path)
    # Read the content of the message.
    file = object_s3['Body'].read()

    file_dict = {
        "file": file,
        "path": object_http_path
    }

    return file_dict

def create_message(file_dict):

    sender = os.environ['MailSender']
    recipient = os.environ['MailRecipient']

    separator = ";"

    # Parse the email body.
    mailobject = email.message_from_string(file_dict['file'].decode('utf-8'))

    # Create a new subject line.
    subject_original = mailobject['Subject']
    subject = "FW: " + subject_original

    # The body text of the email.
    body_text = ("The attached message was received from "
              + separator.join(mailobject.get_all('From'))
              + ". This message is archived at " + file_dict['path'])

    # The file name to use for the attached message. Uses regex to remove all
    # non-alphanumeric characters, and appends a file extension.
    filename = re.sub('[^0-9a-zA-Z]+', '_', subject_original) + ".eml"

    # Create a MIME container.
    msg = MIMEMultipart()
    # Create a MIME text part.
    text_part = MIMEText(body_text, _subtype="html")
    # Attach the text part to the MIME message.
    msg.attach(text_part)

    # Add subject, from and to lines.
    msg['Subject'] = subject
    msg['From'] = sender
    msg['To'] = recipient

    # Create a new MIME object.
    att = MIMEApplication(file_dict["file"], filename)
    att.add_header("Content-Disposition", 'attachment', filename=filename)

    # Attach the file object to the message.
    msg.attach(att)

    message = {
        "Source": sender,
        "Destinations": recipient,
        "Data": msg.as_string()
    }

    return message

def send_email(message):
    aws_region = os.environ['Region']

# Create a new SES client.
    client_ses = boto3.client('ses', region)

    # Send the email.
    try:
        #Provide the contents of the email.
        response = client_ses.send_raw_email(
            Source=message['Source'],
            Destinations=[
                message['Destinations']
            ],
            RawMessage={
                'Data':message['Data']
            }
        )

    # Display an error if something goes wrong.
    except ClientError as e:
        output = e.response['Error']['Message']
    else:
        output = "Email sent! Message ID: " + response['MessageId']

    return output

def lambda_handler(event, context):
    # Get the unique ID of the message. This corresponds to the name of the file
    # in S3.
    message_id = event['Records'][0]['ses']['mail']['messageId']
    print(f"Received message ID {message_id}")

    # Retrieve the file from the S3 bucket.
    file_dict = get_message_from_s3(message_id)

    # Create the message.
    message = create_message(file_dict)

    # Send the email and print the result.
    result = send_email(message)
    print(result)

  • Created Rule in email Receive of my Verified domain with this info: Email Receive Info

PS:

  • info@my-domain.com is the prefix I set in the lambda function and the folder I am saving the email in s3.
  • email-forwarding-service is the actual name of the lambda function
  • Of-course I changed the variables in the policies of the S3 and IAM to be actual data
  • I set the rule to be active so it must be working
  • This is taken from AWS Documentation
  • I don't know anything in python everything was copied and pasted except the variables I added at the beginning of the python file

Result: I am able to save the sent emails in s3 in their right folder (info@...) but the lambda function is not working so I am not receiving the emails on my Gmail account!!

UPDATE: I changed Lambda:

os.environ['MailS3Bucket'] = 'name-of-the-bucket'
os.environ['MailS3Prefix'] = 'prefix of the send rule (folder where I am saving emails in s3)'
os.environ['MailSender'] = 'Verified Email in SES emails'
os.environ['MailRecipient'] = Verified Email in SES emails too (This is the email I want to send emails to)'
os.environ['Region'] = 'my region'

Instead of

MailS3Bucket = 'name-of-the-bucket'
MailS3Prefix = 'prefix of the send rule (folder where I am saving emails in s3)'
MailSender = 'Verified Email in SES emails'
MailRecipient = 'Verified Email in SES emails too (This is the email I want to send emails to)'
Region = 'my region'

So now I am having this error in cloud watch:

[ERROR] ClientError: An error occurred (AccessDenied) when calling the GetObject operation: Access DeniedTraceback (most recent call last):  File "/var/task/lambda_function.py", line 143, in lambda_handler    file_dict = get_message_from_s3(message_id)  File "/var/task/lambda_function.py", line 48, in get_message_from_s3    object_s3 = client_s3.get_object(Bucket=incoming_email_bucket, Key=object_path)  File "/var/runtime/botocore/client.py", line 357, in _api_call    return self._make_api_call(operation_name, kwargs)  File "/var/runtime/botocore/client.py", line 676, in _make_api_call    raise error_class(parsed_response, operation_name)

Line 48 is: object_s3 = client_s3.get_object(Bucket=incoming_email_bucket, Key=object_path)

So I thinks the lambda function is not using the created user in IAM to access the s3, And I don't know python to force the use of the IAM user; HOWEVER Note That is is getting the correct email id saved in s3 according to cloudwatch logs

Any Help, Thank you and much appreciated!


Solution

  • I was able to fix this problem by going to the settings of the lambda function and adding allowing it to access s3 and ses.