Beginner

Serverless Contact Form

Build a contact form that sends emails using API Gateway, Lambda, and SES

Project Overview

Create a fully serverless contact form that processes submissions and sends email notifications. No servers to manage, and you only pay for what you use.

Difficulty: Beginner
AWS Services: API Gateway, Lambda, SES, DynamoDB
Cost: Free Tier eligible

Prerequisites

  • AWS account (Free Tier)
  • Basic JavaScript/Python knowledge
  • A verified email address for SES
  • HTML form on your website

Architecture

📄
Web Form
Frontend
🔗
API Gateway
REST API
Lambda
Process Form
SES
Send Email

Optional: Store submissions in DynamoDB for backup

Step-by-Step Instructions

1

Verify Email in SES

  • Go to Amazon SES in the AWS Console
  • Click "Verified identities" then "Create identity"
  • Choose "Email address" and enter your email
  • Check your inbox and click the verification link
  • Note: In sandbox mode, both sender and recipient must be verified
2

Create the Lambda Function

  • Go to Lambda and click "Create function"
  • Choose "Author from scratch"
  • Name it "ContactFormHandler"
  • Select Python 3.12 or Node.js 20.x runtime
  • Create a new execution role with basic Lambda permissions
3

Add Lambda Code

  • Parse the incoming JSON body (name, email, message)
  • Validate required fields
  • Use boto3 (Python) or AWS SDK to send email via SES
  • Return appropriate response with CORS headers
  • Add error handling for failed sends
4

Set Up API Gateway

  • Go to API Gateway and create a REST API
  • Create a new resource called "/contact"
  • Add a POST method
  • Connect it to your Lambda function
  • Enable Lambda Proxy integration
5

Configure CORS

  • Select the /contact resource
  • Click "Enable CORS" from the Actions menu
  • Set allowed origin to your website domain
  • Enable for POST and OPTIONS methods
  • Deploy the API to a stage (e.g., "prod")
6

Connect Your Frontend

  • Copy the API Gateway invoke URL
  • Update your HTML form to submit via fetch/AJAX
  • Send POST request with JSON body
  • Handle success and error responses
  • Test the complete flow

Tips

  • Use environment variables for email addresses - Don't hardcode emails in your Lambda code
  • Add input validation - Sanitize inputs to prevent injection attacks
  • Request SES production access - Submit a request to send to any email address
  • Add rate limiting - Use API Gateway throttling to prevent abuse

Code Examples

Lambda Function (Python)

lambda_function.py PYTHON
import json
import boto3
import os

ses = boto3.client('ses')

def lambda_handler(event, context):
    try:
        body = json.loads(event['body'])
        name = body['name']
        email = body['email']
        message = body['message']

        # Send email via SES
        ses.send_email(
            Source=os.environ['SENDER_EMAIL'],
            Destination={
                'ToAddresses': [os.environ['RECIPIENT_EMAIL']]
            },
            Message={
                'Subject': {'Data': f'Contact Form: {name}'},
                'Body': {
                    'Text': {'Data': f'From: {name} ({email})\n\n{message}'}
                }
            }
        )

        return {
            'statusCode': 200,
            'headers': {
                'Access-Control-Allow-Origin': '*',
                'Content-Type': 'application/json'
            },
            'body': json.dumps({'message': 'Email sent successfully'})
        }
    except Exception as e:
        return {
            'statusCode': 500,
            'headers': {'Access-Control-Allow-Origin': '*'},
            'body': json.dumps({'error': str(e)})
        }

Frontend JavaScript

contact-form.js JAVASCRIPT
const form = document.getElementById('contact-form');
const API_URL = 'https://abc123.execute-api.us-east-1.amazonaws.com/prod/contact';

form.addEventListener('submit', async (e) => {
    e.preventDefault();

    const data = {
        name: document.getElementById('name').value,
        email: document.getElementById('email').value,
        message: document.getElementById('message').value
    };

    try {
        const response = await fetch(API_URL, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(data)
        });

        if (response.ok) {
            alert('Message sent successfully!');
            form.reset();
        } else {
            throw new Error('Failed to send message');
        }
    } catch (error) {
        alert('Error: ' + error.message);
    }
});

IAM Policy for Lambda

lambda-ses-policy.json JSON
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ses:SendEmail",
                "ses:SendRawEmail"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        }
    ]
}

What You'll Learn

  • API Gateway REST API creation and configuration
  • Lambda function development and deployment
  • Email sending with Amazon SES
  • CORS configuration for cross-origin requests
  • IAM roles and permissions for Lambda