0

I'm running a simple function on AWS Lambda that sends emails via SMTP.

The function uses various parameters from event such as "event["reply_to_msg_id"]" and "event["sender_name"]".

import smtplib
from email.message import EmailMessage
from email.utils import make_msgid

def lambda_handler(event, context):
    msg = EmailMessage()
    if event["reply_to_msg_id"] != "":
        msg['References'] = event["reply_to_msg_id"]
        msg['In-Reply-To'] = event["reply_to_msg_id"]
        msg['subject'] = "Re: " + event["content"]["Subject Line"]
    else:
        msg['subject'] = event["content"]["Subject Line"]
    msg['from'] = event["sender_name"]
    msg['To'] = event["target_email"]
    msg['Message-ID'] = make_msgid('random_client_id')
    msg.add_header('Content-Type', 'text/html')
    msg.set_payload(event["content"]["Body"])
    server = smtplib.SMTP_SSL('smtp.gmail.com', 465)
    result = {"Message-ID": msg['Message-ID']}
    try:
        server.login(event["email_account"]["email_address"], event["email_account"]["password"])
        server.send_message(msg)
        server.quit()
        result["Status"] = "Success"

    except Exception as E:
        result["Status"] = str(E)

    return {
        'statusCode': 200,
        'headers': {
            'Content-Type': 'application/json',
            'Access-Control-Allow-Origin': '*'
        },
        'body': json.dumps(result),
        "isBase64Encoded": False
    }

When I run a Test event in AWS Lambda with the following body, everything works perfectly:

{
  "reply_to_msg_id": "",
  "content": {
    "Subject Line": "Test subject line.",
    "Body": "This is <b>a body</b>."
  },
  "sender_name": "Joe Peschi",
  "target_email": "[email protected]",
  "email_account": {
    "email_address": "***********",
    "password": "***********"
  }
}

enter image description here

The Problem:

I can't figure out how to actually reproduce this using the API Gateway. I created an API Gateway trigger for this function, but no matter what I try - I'm not able to properly pass the above parameters into Events like when I run the Lambda test.

0

2 Answers 2

2

events through API gateway have different format, and you can't change that for proxy lambda integration. For non-proxy, you can use mapping templates.

To best way to verity the event structure it is to print it out and inspect, and amend the lambda code if needed if you don't use mapping templates:

print(json.dumps(event, default=str))
Sign up to request clarification or add additional context in comments.

4 Comments

Not 100% sure about that. You could use mapping templates in API Gateway to "normalize" the input. This way, you can use the same Lambda in different context (invoked through API Gateway, Step Functions, curl, etc.) or am I missing something here?
@Jens Op does not provide any information about the use of templates. But yes, you are correct. I will update the answer. Thanks.
I guess the OP did not include them because most people don't know about them. More or less all tutorials out there use proxy integration for simplicity so this is one of those features most people don't know about. Thank you for taking the time to update your answer :)
I appreciate your answer. It helped me get to a solution that doesn't require changing the function or the test JSON. I posted my solution as an answer.
2

Took me hours of messing about, but I figured it out.

json_body = json.loads(json.loads(json.dumps(event, default=str))["body"])

Using the line above, I'm able to send this JSON body and turn it into a dict variable:

{
  "reply_to_msg_id": "",
  "content": {
    "Subject Line": "Test subject line.",
    "Body": "This is <b>a body</b>."
  },
  "sender_name": "Joe Peschi",
  "target_email": "[email protected]",
  "email_account": {
    "email_address": "***********",
    "password": "***********"
  }
}

However, I also wanted to continue using event, so that I can continue running tests in Lambda with the original JSON structure.

So I created this code:

# THIS LINE TAKES THE REQUEST'S JSON BODY, AND TURNS IT INTO A DICT:
try:
    json_body = json.loads(json.loads(json.dumps(event, default=str))["body"])
except:
    json_body = ""

if json_body != "":
    event = json_body

Now I can run a cURL request and it will work perfectly:

curl --location --request GET 'https://********.execute-api.us-east-2.amazonaws.com/default/send_email_via_smtp' \
--header 'x-api-key: ****************' \
--header 'Content-Type: text/plain' \
--data-raw '{
  "reply_to_msg_id": "",
  "content": {
    "Subject Line": "Test subject line.",
    "Body": "This is <b>a body</b>."
  },
  "sender_name": "Joe Peschi",
  "target_email": "[email protected]",
  "email_account": {
    "email_address": "**********",
    "password": "********"
  }
}'

But I can also run a request through Lambda test using the original JSON structure, and it will also work perfectly:

enter image description here

Here's the final code:

import smtplib
from email.message import EmailMessage
from email.utils import make_msgid

def lambda_handler(event, context):

    try:
        json_body = json.loads(json.loads(json.dumps(event, default=str))["body"])
    except:
        json_body = ""

    if json_body != "":
        event = json_body

    msg = EmailMessage()
    if event["reply_to_msg_id"] != "":
        msg['References'] = event["reply_to_msg_id"]
        msg['In-Reply-To'] = event["reply_to_msg_id"]
        msg['subject'] = "Re: " + event["content"]["Subject Line"]
    else:
        msg['subject'] = event["content"]["Subject Line"]
    msg['from'] = event["sender_name"]
    msg['To'] = event["target_email"]
    msg['Message-ID'] = make_msgid('random_client_id')
    msg.add_header('Content-Type', 'text/html')
    msg.set_payload(event["content"]["Body"])
    server = smtplib.SMTP_SSL('smtp.gmail.com', 465)
    result = {"Message-ID": msg['Message-ID']}
    try:
        server.login(event["email_account"]["email_address"], event["email_account"]["password"])
        server.send_message(msg)
        server.quit()
        result["Status"] = "Success"

    except Exception as E:
        result["Status"] = str(E)

    return {
        'statusCode': 200,
        'headers': {
            'Content-Type': 'application/json',
            'Access-Control-Allow-Origin': '*'
        },
        'body': json.dumps(result),
        "isBase64Encoded": False
    }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.