top of page
  • Writer's pictureMaham Haroon

Migrating a Website to AWS [Part - 2 Dynamic Content - Setting up Forms and subscriptions ]

Updated: Nov 30, 2023

Part 1 was on setting up static pages from Wix to S3 + CloudFront + ACM + Route53 : or

The post provides comprehensive, step-by-step guides for implementing two distinct functionalities on the AWS S3 hosted webpage:

  1. Setting up contact forms: First part guides through the process of configuring contact forms on the S3 hosted static webpage. The contact form setup includes the utilization of AWS API Gateway, Lambda function, CloudWatch, and SES Email service for forwarding contact form details to an email address of choice upon submission of the contact form.

  2. Setting up subscription forms: Second part briefly covers the setup of subscription forms on the webpage. This involves the integration of API Gateway, Lambda functions, DynamoDB, SES mail Service, and CloudWatch.

Setting up contact forms

The html files are already hosted on S3, in order to set up a contact form we go through the following steps one by one. So let's dive in.

Step 1: Create a Lambda function and Provide Permissions

First step is to go to lambda and create a new function. You can create one in Node.js or Python. I chose python and created a function from scratch although there are blueprint available and then chose 'Create a new role with basic lambda permissions', which we'll edit in a minute.

Once the function is created, we can now edit the permissions to allow SES Email service. To do that, we navigate to configuration panel on the top and click on the Role name under Execution role: this is the role we created with basic lambda permissions.

When we click on the role, we are redirected to IAM and initially the role is only configured to allow CloudWatch logs, so we edit the role, which is in json format.

Then we add a code snippet inside the statements section of the permissions policy, that will provide lambda with permission to use SES to Send an Email. The code snippet is as follows:

    "Sid" : "SESPermission",
    "Effect" : "Allow",
    "Action" : "ses:SendEmail",
    "Resource" : '*'

After that we save changes, usually if there are any syntax errors, AWS will point to those. After the policy is updates we can see that there are 3 more permissions alongside the previous CloudWatch which was our intention.

Step 2: Editing Lambda function, Registering SES emails, Deployment and Testing

Now that the permission policy is updated we go back to our lambda function and put the required code, the code is a modified version of code taken from an AWS article.

The code gets the form details in the event and then sends an email with those details to our choice of email address.

But before testing or deploying this function we want to be sure that our email is registered with SES. If the SES is being used in the sandbox we'd need to register both the recipient and sender emails but once SES is moved to production, we only need to register the sender domain/email in SES.

To do that we go to Amazon SES and under Configuration: Verified identities we can either select a domain or an email address. If the domain is in Route53, it gets verified on its own and for email address we need to do a confirmation.

Once the registration is complete we're set. Now we can go back to our lambda function and first deploy it then test it. Once everything in lambda is working as expected and we get a test email we can move to the next step.

Step 3: Setting Up API Gateway POST Method and enabling CORS

Now that the lambda function is all set up, we need something to trigger the function when someone submits the form on webpage so we create a REST API in API Gateway.

Once we'd named the API and provided a description, we can navigate to actions on the top and create a Resource for that API.

For forms you'd want to enable CORS but enabling CORS at this step is not really necessary since we'll have to circle back to it.

Once the resource is successfully created, we again navigate to actions and create a method and then select POST. In integration type we'd select lambda function, check the use lambda proxy integration and then select our predefined lambda function from the menu.

Note that the the proxy integration check box ensures that the request details are in the event used by lambda_handler function.

Next, the API Gateway asks to add permissions to lambda function directly and we click ok to ensure that lambda allows the API to communicate with lambda.

Now for more granular control over CORS, we need to enable it from the actions menu and we specify the domain that hosts our forms so only that domain can send the form data to API Gateway.

Note that for CORS to work properly, the following return code in the above lambda function matters which is already in the code. You can also set other status codes for errors.

Once we'd enabled CORS and set up everything we again go to actions and deploy our API. That will give us a link that we'll later use in our S3 hosted HTML code.

There are some other options like enabling API cache and Logs/Tracing etc.

After saving the invoke URL, we move to the next step.

Step 4: Enabling CloudWatch Logs (OPTIONAL)

This step is not an entirely necessary step but helps with monitoring and troubleshooting any errors that are inevitable along the way.

For lambda functions CloudWatch logs are automatically configured and we have permissions as seen is setting lambda permission section but for API Gateway we need to configure permissions for CloudWatch and enable logs. So we enable logs for Errors and Info and get prompted to provide an ARN for the service role.

In order to create the service role we have to go to IAM and create a service role that has API Gateway selected.

After a few clicks as prompted, nothing much note here, the role is created. We copy this ARN and go back to out API Gateway.

In API Gateway, in the left panel at the bottom is the settings section, we navigate to settings. There we paste our copied ARN of the service role we just created in IAM and save. Now we can enable logs for the API and CloudWatch will automatically create a folder of logs for this specific API.

Initially there won't be anything to show in CloudWatch for this API but once we start submitting our forms, we'll see data in there.

Let's move to the next step

Step 5: Setting up HTML Code in the form

We move towards our html file hosted in S3 now to do add the code for API Gateway and set up the form.

The submitToAPIGateway function is where we paste our URL copied from invoke URL once we deployed our API. Note that the URL needs to be appended by the resource name i.e. contact so it'd look something like https://{api-link}.amazonaws/dev/contact/

In the form you can set the button type to "submit" and then onsubmit refer to the submitToAPIGateway function. You might also need to remove the enctype if it's causing an error with the content-type being application/json.

Step 6: Setting up reCAPTCHA (OPTIONAL)

It's also a good idea to either set up a google recaptcha or a cloudflare turnstile with the contact form. Instructions can be seen in the mentioned links.

Step 7: Uploading to S3, Caching delays, Testing and CloudWatch Monitoring

Finally after editing everything in the html file, we can upload it to the S3 bucket, from where our website it hosted and we can test our form. Note that if the webpages are being served by CloudFront and caching is enabled, there might be substantial delays after updates our made on S3 to be seen on the webpage.

Once the changes have propagated and we have tested or troubleshooted our form, we're all set.

Next we see how we can slightly modify the above architecture for subscription forms.

Setting up subscription forms

Setting up subscription forms is quit similar to the above set up except that we want to save the subscription data somewhere.

We can choose dynamoDB for the purpose of storing subscription emails. For that we need to create a DynamoDB Table and specify a required Partition Key (PK) and an optional Sort Key (SK). After the table is created, we can and modify our lambda function slightly to save the data received via API Gateway to the dynamoDB table.

Adding the following code to lambda, does the trick.

And we'd also need to add permissions in lambda for dynamoDB. To do that we again go to configurations and click on the role name under the Execution role. Once we're directed to the IAM permission's policy, we edit and add the following snippet to the permissions.

    "Sid": "AllowDynamoDBPutItem", 
    "Effect": "Allow", 
    "Action": [ 
    "Resource": "arn:aws:dynamodb:us-east-1:<AWS_ACCOUNT_ID>:table/<TABLE_NAME>" 

After the permissions are added, lambda is allowed to put objects in dynamoDB table.

For sending subscription emails to subscribers, we can set up a CloudWatch based cron job that integrates with lambda to sends out an email every week/month. If SES is in sandbox, we'd need to move it to production to send these emails to users but for now that's all.

Please feel free to let me know if there are other things I can add.

9 views0 comments


bottom of page