Robby Frank
Robby Frank

Reputation: 304

Parsing JSON body when triggering a Lambda function with API Gateway cURL request

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.

Upvotes: 0

Views: 1855

Answers (2)

Robby Frank
Robby Frank

Reputation: 304

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
    }

Upvotes: 2

Marcin
Marcin

Reputation: 238975

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))

Upvotes: 2

Related Questions