I’m trying use JavaScript, Lambda and SES to generate an email from a contact form. The code works when I test it from the Lambda console, but when I invoke it with HTTP it doesn’t work, even though I have confirmed the data is the same and the appropriate headers are allowed.
Here are the console errors in the browser:
Here’s my Lambda function:
import boto3
import json
ses = boto3.client('ses')
def lambda_handler(event, context):
name = event['name']
email = event['email']
phone_number = event['phone_number']
message = event['message']
# Construct the email message
subject = f"New message from {name}"
body = f"From: {name}nEmail: {email}nPhone: {phone_number}nMessage: {message}"
sender = '[email protected]'
recipient = '[email protected]'
# Send the email using AWS SES
response = ses.send_email(
Source=sender,
Destination={
'ToAddresses': [
recipient,
]
},
Message={
'Subject': {
'Data': subject,
'Charset': 'UTF-8'
},
'Body': {
'Text': {
'Data': body,
'Charset': 'UTF-8'
}
}
}
)
return {
'statusCode': 200,
'headers': {
'Access-Control-Allow-Headers': '*',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': '*'
},
'body': json.dumps('Email sent successfully!')
}
Here’s my JavaScript:
const formEl = document.querySelector(".contact_form");
formEl.addEventListener('submit', event => {
event.preventDefault()
const formData = new FormData(formEl);
const data = Object.fromEntries(formData);
fetch('https://myapigatewayuri/prod/contact-form', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
.then( response => {
console.log('Here is the response:')
console.log(response);
if (response.status === 200) {
formEl.reset();
window.alert("Your message was sent successfully. Thank you for reaching out!")
} else if (response.status !== 200) {
window.alert("There was a problem sending your message.")
}
})
});
Here’s the data that is getting sent in the request:
{
'name': 'test name',
'phone_number': '12345678',
'email': '[email protected]',
'message': 'test message'
}
CloudWatch Logs shows this error:
"errorMessage": "'name'",
"errorType": "KeyError",
"stackTrace": [
" File "/var/task/lambda_function.py", line 16, in lambda_handlern name = event['name']n"
]
This error does not occur when testing in the console with the same data.
If I change the variable assignment to use the get() method like this:
name = event.get('name')
email = event.get('email')
phone_number = event.get('phone_number')
message = event.get('message')
The CORS error goes away and the email generates but all the values are ‘none’.
I would have thought this indicates that the data isn’t making it to the function at all, however when running this version of the function I get the following output in CloudWatch logs:
(e5f7444e-a6a5-4b8f-8f47-8f586a342a4a) Method request body before transformations: {
"name": "test name",
"phone_number": "12345678",
"email": "[email protected]",
"message": "test message"
}
I can’t figure out what is going on here and I’ve run out of things to do to try and narrow down the problem. Any help appreciated.
2
Answers
I figured it out. It might have been a rookie mistake, but I'll share what I have learned anyway just in case it's helpful to someone.
Firstly, the event is the whole http request passed to the event handler function by the API, and within that the 'body' key/value pair is where the form data is. In my code above I tried to access the name, email, etc keys directly, from the event, but you need to access the body first.
Secondly, API Gateway converts JSON strings to a valid dictionary before passing it to the Lambda function, but it only converts the top level key/value pairs. The value of 'body' is still a string. So once you've accessed the value of 'body' you need to parse that. Here's the code that I got to work:
I had actually tried this concept, but I tried to parse the whole event rather than just the body, like this:
But this causes an error, because the event is already converted into a dictionary by the API Gateway before it gets to the function.
Finally, the fact that I misunderstood what the event was meant that using the Lambda console UI to test my function was giving me misleading results, so be aware of this if you're using the test UI to troubleshoot issues.
Thanks for everyone's replies anyway. Hope this is useful for someone.
You you need to add handling for OPTIONS method for lambda. Something like this: