AWS Hands-On Lab — Project #3 — Serverless

Lab 03: Build a Serverless Contact Form

A visitor fills out a form on your S3 site → API Gateway receives it → Lambda processes it → SES emails you. No servers. Zero idle cost. Pure event-driven cloud.

Services: S3 · Lambda · API Gateway · SES · IAM    Cost: ~$0 Free Tier    Time: 2–4 hours    Level: Beginner+

Overview

In this lab you will build a real, working serverless contact form. A visitor fills out a form on your S3-hosted website, clicks Submit, and within seconds you receive an email in your inbox. No web servers. No monthly EC2 bill. Just cloud services working together.

Architecture Flow

  1. User visits your S3-hosted HTML page and fills in the contact form
  2. JavaScript sends the form data as a POST request to your API Gateway endpoint
  3. API Gateway receives the request and triggers your Lambda function
  4. Lambda reads the form data and calls Amazon SES to send an email
  5. SES delivers the email to your verified inbox
  6. The user sees a success message on the page

AWS Services Used

ServiceRole in This LabFree Tier
Amazon S3Hosts your static HTML contact form pageYes
AWS LambdaServerless function that processes the form submissionYes
Amazon API GatewayCreates the HTTPS endpoint your form posts data toYes
Amazon SESSimple Email Service — sends the email to your inboxYes
AWS IAMGrants Lambda permission to call SES securelyYes
Free Tier: Lambda: 1,000,000 free requests/month · API Gateway: 1,000,000 calls/month · SES: 62,000 emails/month when sent from Lambda.

Prerequisites

SES sandbox mode: SES starts in sandbox mode. In sandbox mode you can only send emails to verified addresses. For this lab, simply verify the email address you want to receive notifications on.

Step 1
Amazon Simple Email Service (SES)
Verify Your Email in Amazon SES

SES requires you to verify email addresses before you can send to them. This confirms you own the address.

  1. Search for SES in the AWS Console and click Simple Email Service
  2. In the left sidebar click Verified identities
  3. Click Create identity → Identity type: Email address
  4. Enter the email address where you want to receive contact form notifications
  5. Click Create identity
  6. Open your email inbox and click the verification link in the email from AWS
  7. Return to the SES console and refresh — status should show Verified
Keep the SES console tab open — you will need your verified email address in Step 3.
Step 2
AWS Identity & Access Management (IAM)
Create an IAM Role for Lambda

Lambda needs permission to call SES. You will create an IAM role and attach the necessary policies.

  1. Search for IAM in the AWS Console and click it
  2. Left sidebar → RolesCreate role
  3. Trusted entity type: AWS service → Use case: Lambda → Next
  4. Search for and add AmazonSESFullAccess
  5. Search for and add AWSLambdaBasicExecutionRole (allows Lambda to write logs to CloudWatch)
  6. Click Next → Role name: LambdaSESRoleCreate role
The AWSLambdaBasicExecutionRole policy is essential for debugging — it lets Lambda write execution logs to CloudWatch so you can see what happened if the function fails.
Step 3
AWS Lambda
Create the Lambda Function

Lambda is the core of this project. The function receives form data from API Gateway, formats it into an email, and uses SES to send it to you.

Create the function

  1. Search for Lambda in the AWS Console and click it
  2. Click Create functionAuthor from scratch
  3. Function name: ContactFormHandler
  4. Runtime: Python 3.12
  5. Expand Change default execution roleUse an existing role → select LambdaSESRole
  6. Click Create function

Add the function code

In the Code source section, delete all existing code and replace it with the following. Replace both email addresses with the address you verified in Step 1.

import json
import boto3

ses = boto3.client('ses', region_name='us-east-1')

SENDER_EMAIL    = 'your-verified-email@example.com'   # replace this
RECIPIENT_EMAIL = 'your-verified-email@example.com'   # replace this

def lambda_handler(event, context):
    try:
        body    = json.loads(event.get('body', '{}'))
        name    = body.get('name', 'Unknown')
        email   = body.get('email', 'Unknown')
        message = body.get('message', 'No message provided')

        ses.send_email(
            Source=SENDER_EMAIL,
            Destination={'ToAddresses': [RECIPIENT_EMAIL]},
            Message={
                'Subject': {'Data': f'New contact from {name}'},
                'Body': {
                    'Text': {
                        'Data': f'Name: {name}\nEmail: {email}\nMessage: {message}'
                    }
                }
            }
        )
        return {
            'statusCode': 200,
            'headers': {'Access-Control-Allow-Origin': '*'},
            'body': json.dumps({'message': 'Email sent successfully!'})
        }
    except Exception as e:
        print(f'Error: {str(e)}')
        return {
            'statusCode': 500,
            'headers': {'Access-Control-Allow-Origin': '*'},
            'body': json.dumps({'error': str(e)})
        }
Replace both instances of 'your-verified-email@example.com' with the email you verified in Step 1. The function will fail if these are not updated.

Deploy and test

  1. Click Deploy to save the code
  2. Click TestCreate new test event → Name: TestContact
  3. Replace the test JSON with the following and click Test:
{
  "body": "{"name": "Test User", "email": "test@example.com", "message": "Hello from Lambda!"}",
  "httpMethod": "POST"
}
  1. You should see a green Execution result: succeeded banner
  2. Check your email inbox — you should have received the test email
If you received the test email, your Lambda function is working correctly. If you see an error, confirm your email is verified in SES and the email addresses in the code are correct.
Step 4
Amazon API Gateway
Create an API Gateway Endpoint

API Gateway creates a public HTTPS URL your contact form will POST data to. It connects the internet to your Lambda function.

  1. Search for API Gateway in the AWS Console and click it
  2. Click Create API → under HTTP API click Build
  3. Click Add integration → select Lambda → select ContactFormHandler
  4. API name: ContactFormAPI → click Next
  5. Method: POST   Resource path: /contact → click Next
  6. Stage name: prod → click Next → Create
  7. On the API summary page copy the Invoke URL and save it

Enable CORS

  1. In the left sidebar click CORS
  2. Access-Control-Allow-Origin: *
  3. Access-Control-Allow-Methods: POST, OPTIONS
  4. Click Save
CORS must be enabled so your S3 website can call the API. In production you would restrict the origin to your specific domain instead of using *.
Step 5
Amazon S3
Add the Contact Form to Your Website

Create a file named contact.html on your computer with the code below. Replace YOUR_API_GATEWAY_URL with the Invoke URL from Step 4.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8"/>
  <title>Contact Us</title>
  <style>
    body { font-family: Arial, sans-serif; max-width: 600px;
           margin: 60px auto; padding: 0 20px; background: #f2f2f2; }
    h1 { color: #1e2a3a; }
    label { display: block; margin-bottom: 4px; font-size: 0.9rem; }
    input, textarea { width: 100%; padding: 10px; margin-bottom: 14px;
                      border: 1px solid #ccc; font-family: Arial, sans-serif; }
    button { background: #1e2a3a; color: #fff; border: none;
             padding: 10px 24px; font-size: 0.9rem; cursor: pointer; }
    button:hover { background: #2c3e50; }
    #status { margin-top: 12px; font-size: 0.9rem; }
  </style>
</head>
<body>
  <h1>Contact Us</h1>
  <label>Name</label>
  <input type="text" id="name" placeholder="Your name"/>
  <label>Email</label>
  <input type="email" id="email" placeholder="your@email.com"/>
  <label>Message</label>
  <textarea id="message" rows="5"></textarea>
  <button onclick="submitForm()">Send Message</button>
  <p id="status"></p>
  <script>
    const API_URL = 'YOUR_API_GATEWAY_URL/contact'; // <-- replace this
    async function submitForm() {
      const name    = document.getElementById('name').value;
      const email   = document.getElementById('email').value;
      const message = document.getElementById('message').value;
      const status  = document.getElementById('status');
      if (!name || !email || !message) {
        status.style.color = 'red';
        status.textContent = 'Please fill in all fields.';
        return;
      }
      status.style.color = 'gray';
      status.textContent = 'Sending...';
      try {
        await fetch(API_URL, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ name, email, message })
        });
        status.style.color = 'green';
        status.textContent = 'Message sent! Check your inbox.';
      } catch (e) {
        status.style.color = 'red';
        status.textContent = 'Error sending. Please try again.';
      }
    }
  </script>
</body>
</html>
Replace 'YOUR_API_GATEWAY_URL/contact' with the full Invoke URL from Step 4, e.g. https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/prod/contact

Upload to S3 and test

  1. Go to S3 → your bucket → Upload → add contact.htmlUpload
  2. Open your site URL and add /contact.html at the end
  3. Fill in the form and click Send Message
  4. Check your inbox — you should receive the email within seconds
Congratulations! You have built a fully serverless contact form. Every submission triggers Lambda and sends an email via SES — at virtually zero cost.

What You Learned


Lab Cleanup — Delete Your Resources

Always delete lab resources when done to avoid unexpected charges.
#ResourceHow to Delete
1Lambda FunctionLambda → Functions → select ContactFormHandler → Actions → Delete
2API GatewayAPI Gateway → APIs → select ContactFormAPI → Actions → Delete
3SES Verified IdentitySES → Verified identities → select email → Delete identity
4IAM RoleIAM → Roles → search LambdaSESRole → Delete
5S3 contact.htmlS3 → your bucket → select contact.html → Delete
6CloudWatch Logs (optional)CloudWatch → Log groups → delete /aws/lambda/ContactFormHandler