Skip to content

Commit c42967e

Browse files
committed
Update
1 parent 760267a commit c42967e

File tree

6 files changed

+357
-0
lines changed

6 files changed

+357
-0
lines changed

WebApps/Contact-Form/README.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Building a Contact Form with API Gateway and SES
2+
3+
This lesson demonstrates a web page with a typical contact form. Using API Gateway and a Lambda function as a backend for this form, we will send the form post contents via email using SES, and also write the contact data to DynamoDB.
4+
5+
## DynamoDB
6+
7+
Create table `Contact` with primary partition key `id`:
8+
9+
```sh
10+
aws dynamodb create-table --table-name Contact \
11+
--attribute-definitions AttributeName=id,AttributeType=S \
12+
--key-schema AttributeName=id,KeyType=HASH \
13+
--billing-mode=PAY_PER_REQUEST
14+
```
15+
16+
## Lambda function
17+
18+
Create function **ContactEmail**
19+
20+
## API Gateway
21+
22+
Create API **ContactEmailAPI**
23+
24+
### Create Method
25+
26+
Select **POST** and check the check mark
27+
28+
Integration Type: Lambda Function
29+
Use Lambda Proxy Integration: Checked (stores request data in `event`)
30+
Lambda region: Same region as Lambda function
31+
Lambda function: **ContactEmail**
32+
33+
### Enable CORS
34+
35+
Select the **POST** method
36+
Under **Actions** select **Enable CORS**
37+
Leave the default options and click on **Enable CORS and replace existing CORS headers**.
38+
Click **Yes, replace existing values**
39+
40+
### Deploy API
41+
42+
Under **Actions** select **Deploy API**
43+
Deployment stage: **[New stage]**
44+
Stage name: **prod**
45+
46+
Note the **Invoke URL** and update `form.js`.
47+
48+
## Test locally
49+
50+
```sh
51+
cd Contact-Form
52+
python3 -m http.server
53+
```
54+
55+
Browse <http://localhost:8000>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"Version": "2012-10-17",
3+
"Statement": [{
4+
"Effect": "Allow",
5+
"Action": [
6+
"logs:CreateLogGroup",
7+
"logs:CreateLogStream",
8+
"logs:PutLogEvents"
9+
],
10+
"Resource": "arn:aws:logs:*:*:*"
11+
},
12+
{
13+
"Effect": "Allow",
14+
"Action": [
15+
"ses:SendEmail",
16+
"ses:SendRawEmail"
17+
],
18+
"Resource": [
19+
"*"
20+
]
21+
},
22+
{
23+
"Effect": "Allow",
24+
"Action": [
25+
"dynamodb:PutItem"
26+
],
27+
"Resource": "arn:aws:dynamodb:us-east-2:123456789012:table/Contact"
28+
}
29+
]
30+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import json
2+
import os
3+
import uuid
4+
from datetime import datetime
5+
6+
import boto3
7+
from botocore.exceptions import ClientError
8+
9+
CHARSET = 'UTF-8'
10+
DYNAMODB_TABLE = os.environ['DYNAMODB_TABLE']
11+
SENDER_EMAIL = os.environ['SENDER_EMAIL'] # Must be configured in SES
12+
SES_REGION = 'us-east-1'
13+
14+
15+
dynamodb = boto3.resource('dynamodb')
16+
s3 = boto3.client('s3')
17+
ses = boto3.client('ses', region_name=SES_REGION)
18+
19+
20+
def lambda_handler(event, context):
21+
print(event)
22+
data = json.loads(event['body'])
23+
print(json.dumps(data))
24+
25+
try:
26+
27+
content = 'Message from ' + \
28+
data['first_name'] + ' ' + data['last_name'] + '\n' + \
29+
data['company'] + '\n' + \
30+
data['address1'] + '\n' + \
31+
data['address2'] + '\n' + \
32+
data['city'] + '\n' + \
33+
data['state'] + '\n' + \
34+
data['zip'] + '\n' + \
35+
data['email'] + '\n' + \
36+
data['phone'] + '\n' + \
37+
data['budget'] + '\n' + \
38+
data['message']
39+
save_to_dynamodb(data)
40+
response = send_mail_to_user(data, content)
41+
except ClientError as e:
42+
print(e.response['Error']['Message'])
43+
else:
44+
print("Email sent! Message Id:", response['MessageId'])
45+
46+
return {
47+
"statusCode": 200,
48+
"headers": {"Content-Type": "application/json"},
49+
"body": ""
50+
}
51+
52+
53+
def save_to_dynamodb(data):
54+
timestamp = datetime.utcnow().replace(microsecond=0).isoformat()
55+
table = dynamodb.Table(DYNAMODB_TABLE)
56+
item = {
57+
'id': str(uuid.uuid1()),
58+
'first_name': data['first_name'], # required
59+
'last_name': data['last_name'], # required
60+
'company': data['company'] if data['company'] else None,
61+
'address1': data['address1'] if data['address1'] else None,
62+
'address2': data['address2'] if data['address2'] else None,
63+
'city': data['city'] if data['city'] else None,
64+
'state': data['state'] if data['state'] else None,
65+
'zip': data['zip'] if data['zip'] else None,
66+
'email': data['email'], # required
67+
'phone': data['phone'], # required
68+
'budget': data['budget'], # required
69+
'message': data['message'], # required
70+
'createdAt': timestamp,
71+
'updatedAt': timestamp
72+
}
73+
table.put_item(Item=item)
74+
return
75+
76+
77+
def send_mail_to_user(data, content):
78+
return ses.send_email(
79+
Source=SENDER_EMAIL,
80+
Destination={
81+
'ToAddresses': [
82+
data['email'],
83+
],
84+
},
85+
Message={
86+
'Subject': {
87+
'Charset': CHARSET,
88+
'Data': 'Thank you for contacting us!'
89+
},
90+
'Body': {
91+
'Html': {
92+
'Charset': CHARSET,
93+
'Data': content
94+
},
95+
'Text': {
96+
'Charset': CHARSET,
97+
'Data': content
98+
}
99+
}
100+
}
101+
)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
@import url('https://fonts.googleapis.com/css?family=Open+Sans');
2+
3+
body {
4+
font-family: 'Open Sans', sans-serif;
5+
padding-top: 5rem;
6+
}
7+
8+
textarea {
9+
height: 100%;
10+
}
11+
12+
.starter-template {
13+
padding: 3rem 1.5rem;
14+
text-align: center;
15+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<!doctype html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="utf-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
7+
<title>Building a Contact Form with API Gateway and SES</title>
8+
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
9+
crossorigin="anonymous">
10+
<link rel="stylesheet" href="/css/style.css">
11+
<script src="http://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
12+
crossorigin="anonymous"></script>
13+
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"
14+
crossorigin="anonymous"></script>
15+
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
16+
crossorigin="anonymous"></script>
17+
<script src="/js/form.js"></script>
18+
</head>
19+
20+
<body>
21+
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top"></nav>
22+
23+
<main role="main" class="container">
24+
25+
<div class="starter-template">
26+
<h1>Building a Contact Form with API Gateway and SES</h1>
27+
<p class="lead">Fill out this form and submit to API Gateway!</p>
28+
29+
<form id="mainForm">
30+
<div class="form-row">
31+
<div class="col-md-4 mb-3">
32+
<label for="first_name">First name</label>
33+
<input type="text" class="form-control" id="first_name" placeholder="First name" required>
34+
</div>
35+
<div class="col-md-4 mb-3">
36+
<label for="last_name">Last name</label>
37+
<input type="text" class="form-control" id="last_name" placeholder="Last name" required>
38+
</div>
39+
</div>
40+
<div class="form-row">
41+
<div class="col-md-6 mb-3">
42+
<label for="company">Company</label>
43+
<input type="text" class="form-control" id="company" placeholder="Company">
44+
</div>
45+
<div class="col-md-6 mb-3">
46+
<label for="address1">Address 1</label>
47+
<input type="text" class="form-control" id="address1" placeholder="Address 1">
48+
</div>
49+
<div class="col-md-6 mb-3">
50+
<label for="address2">Address 2</label>
51+
<input type="text" class="form-control" id="address2" placeholder="Address 2">
52+
</div>
53+
</div>
54+
<div class="form-row">
55+
<div class="form-group col-md-6">
56+
<label for="city">City</label>
57+
<input type="text" class="form-control" id="city" placeholder="City">
58+
</div>
59+
<div class="form-group col-md-4">
60+
<label for="state">State</label>
61+
<input type="text" class="form-control" id="state" placeholder="State">
62+
</div>
63+
<div class="form-group col-md-2">
64+
<label for="zip">Zip</label>
65+
<input type="text" class="form-control" id="zip" placeholder="Zip">
66+
</div>
67+
</div>
68+
<div class="form-row">
69+
<div class="form-group col-md-4">
70+
<label for="email">Email</label>
71+
<input type="text" class="form-control" id="email" type="email" placeholder="Email" required>
72+
</div>
73+
<div class="form-group col-md-4">
74+
<label for="phone">Phone</label>
75+
<input type="text" class="form-control" id="phone" placeholder="Phone" required>
76+
</div>
77+
<div class="form-group col-md-4">
78+
<label for="validationDefault10">Budget</label>
79+
<select class="custom-select" id="budget" required>
80+
<option value="">Budget</option>
81+
<option value="Under 5000">Under $5,000</option>
82+
<option value="5000-20000">$5,000-$20,000</option>
83+
<option value="Over 20000">Over $20,000</option>
84+
</select>
85+
</div>
86+
</div>
87+
88+
<div class="form-group">
89+
<label for="message">Message</label>
90+
<input type="textarea" class="form-control" id="message" placeholder="Message" rows="5" required>
91+
</div>
92+
93+
<button id="submit" class="btn btn-primary" type="submit">Submit form</button>
94+
</form>
95+
<div id="form-response"></div>
96+
</div>
97+
98+
</main>
99+
100+
</body>
101+
102+
</html>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Set 'URL' to your API Gateway endpoint
2+
URL = 'https://abcdefghij.execute-api.us-east-2.amazonaws.com/prod/';
3+
4+
$(document).ready(function () {
5+
6+
$("#mainForm").submit(function (e) {
7+
e.preventDefault();
8+
9+
var first_name = $("#first_name").val(),
10+
last_name = $("#last_name").val(),
11+
company = $("#company").val(),
12+
address1 = $("#address1").val(),
13+
address2 = $("#address2").val(),
14+
city = $("#city").val(),
15+
state = $("#state").val(),
16+
zip = $("#zip").val(),
17+
email = $("#email").val(),
18+
phone = $("#phone").val(),
19+
budget = $("#budget").val(),
20+
message = $("#message").val();
21+
22+
$.ajax({
23+
type: "POST",
24+
url: URL,
25+
contentType: 'application/json',
26+
crossDomain: true, // remove in production environments
27+
dataType: 'json',
28+
// dataType: 'jsonp' // use JSONP for done() callback to work locally
29+
data: JSON.stringify({
30+
first_name: first_name,
31+
last_name: last_name,
32+
company: company,
33+
address1: address1,
34+
address2: address2,
35+
city: city,
36+
state: state,
37+
zip: zip,
38+
phone: phone,
39+
email: email,
40+
budget: budget,
41+
message: message
42+
})
43+
}).done(function (result) {
44+
console.log(result);
45+
}).fail(function (jqXHR, textStatus, error) {
46+
console.log("Post error: " + error);
47+
if (error != '') $('#form-response').text('Error: ' + error);
48+
}).always(function(data) {
49+
console.log(JSON.stringify(data));
50+
$('#form-response').text('Form submitted!');
51+
});
52+
53+
});
54+
});

0 commit comments

Comments
 (0)