Building a Serverless Contact Form for Static Websites

Add dynamic email functionality to static sites using AWS Lambda, API Gateway, and SES--no servers required.

Introduction

Static websites offer remarkable advantages in terms of performance, security, and hosting simplicity. Platforms like GitHub Pages, Netlify, and Cloudflare Pages enable developers to deploy HTML, CSS, and JavaScript sites at no cost, with exceptional speed and reliability. However, the inherent limitation of static sites lies in their inability to execute server-side logic--functions that require elevated permissions, such as sending emails from a contact form.

Traditional solutions for adding dynamic functionality often involved deploying a full backend server, which introduces unnecessary complexity and ongoing costs. When a contact form receives sporadic submissions, maintaining a server that runs continuously becomes economically inefficient and operationally burdensome. Serverless architecture emerges as an elegant solution, allowing developers to execute server-side code only when needed, without managing underlying infrastructure.

Serverless contact forms leverage cloud functions that activate exclusively when someone submits the form, sending the message via an email service before terminating. This approach transforms the economics of contact form implementation--organizations pay only for the compute time consumed during actual form submissions rather than maintaining an always-on server. The result is a contact form solution that scales automatically, costs pennies to operate, and requires minimal maintenance overhead.

This guide explores building a complete serverless contact form using Amazon Web Services (AWS), specifically Lambda for compute, API Gateway for HTTP endpoints, and Simple Email Service (SES) for message delivery. The techniques described apply universally to any static website, whether hosted on Netlify, Vercel, Cloudflare Pages, or traditional hosting providers. Our web development services team regularly implements these solutions for clients needing secure, scalable contact functionality without server management overhead.

Understanding Serverless Architecture for Contact Forms

The Problem with Traditional Contact Forms

Static websites excel at delivering content quickly and securely, but they cannot generate dynamic responses or perform privileged operations. A contact form requires backend logic to process submissions, validate input, and transmit messages to the website owner's inbox. Historically, implementing such functionality demanded deploying a server running PHP, Node.js, or another runtime--a significant commitment in terms of infrastructure, security patching, and operational overhead.

Consider a small business website that might receive five contact form submissions per week. Running a dedicated server to handle these occasional requests means paying for 24/7 compute capacity while utilizing perhaps 0.001% of available resources. The server requires regular security updates, monitoring for availability, and configuration management--all for functionality that executes sporadically in response to user actions.

The Serverless Solution

Serverless computing fundamentally reimagines how developers approach backend functionality. Services like AWS Lambda execute code in response to events without requiring developers to provision, manage, or scale servers. The cloud provider handles all infrastructure concerns--capacity provisioning, patching, scaling, and high availability--allowing developers to focus exclusively on writing business logic.

The architectural pattern for serverless contact forms involves three primary components working in sequence. First, a static HTML form captures user input on the client side. Second, JavaScript running in the browser sends this data to an API endpoint. Third, API Gateway receives the HTTP request and triggers a Lambda function, which processes the submission and sends an email via SES. Each component plays a specific role, and understanding these roles enables effective implementation and troubleshooting.

This event-driven model means the system scales automatically. Whether the contact form receives ten submissions per day or ten thousand, Lambda provisions additional execution environments instantaneously, processing each request independently. There is no capacity planning, no scaling configuration, and no throttling to manage under normal conditions.

AWS Services Overview

Three AWS services form the foundation of a serverless contact form implementation. Understanding each service's purpose and capabilities clarifies how the pieces connect to create a functional system.

  • AWS Lambda provides the compute runtime for processing form submissions. Lambda supports multiple programming languages, including Node.js, Python, Java, and Go, with the runtime handling all underlying server management. Developers write a handler function that receives event data containing form submission details, perform any required processing, and return a response. Lambda automatically provisions execution environments, scales capacity based on incoming requests, and charges only for actual compute time consumed.

  • Amazon API Gateway creates HTTP endpoints that trigger Lambda functions. Since Lambda functions cannot be invoked directly via HTTP, API Gateway serves as the interface between the web browser and the backend logic. API Gateway handles request routing, URL path mapping, HTTP method validation, and importantly, Cross-Origin Resource Sharing (CORS) configuration that allows browsers to submit forms from different domains.

  • Amazon Simple Email Service (SES) delivers the actual email messages containing form submission content. SES provides a cost-effective, scalable, and configurable email sending infrastructure compatible with the requirements of contact forms. After verifying ownership of an email address or domain, SES can send messages to any recipient, making it suitable for delivering contact form submissions to the website owner's inbox.

For organizations exploring broader automation opportunities, our AI automation services can extend serverless architectures to include intelligent routing, automated responses, and integration with CRM systems.

Benefits of Serverless Contact Forms

Why choose serverless over traditional hosting

Pay-Per-Request Pricing

Pay only for actual form submissions, not idle server time

Automatic Scaling

Handle any volume of submissions without capacity planning

No Server Management

AWS handles infrastructure, patching, and maintenance

High Reliability

Built-in redundancy and 99.99% availability SLA

Setting Up the Email Infrastructure

Verifying Email Addresses in SES

Before implementing the Lambda function, the email sending infrastructure requires configuration. SES operates in a sandbox mode for new accounts, restricting sending to verified email addresses only. This verification process confirms ownership and prevents unauthorized email sending--a critical security measure.

Navigate to the SES console and access the Email Addresses section. Click "Verify a New Email Address" and enter the address that will receive contact form submissions. SES sends a verification email containing a confirmation link; clicking this link completes the verification process. The verified address can now send and receive emails through SES.

For production environments, consider verifying an entire domain rather than individual email addresses. Domain verification enables sending from any address within that domain (such as [email protected]) without verifying each address separately. The verification process involves adding DNS records provided by SES to the domain's DNS configuration, typically through the domain registrar or DNS hosting provider.

Organizations planning to send email to recipients outside their own domain should also request removal from the SES sandbox. While verified addresses can send to any recipient, new accounts initially can only send to other verified addresses. The production access request form in the SES console asks about anticipated sending volumes, use case, and list management practices--answering these questions accurately enables broader sending capabilities.

Configuring Sender Identity

SES supports multiple sender identity configurations, each suited to different use cases. For contact forms, the sender typically appears as a dedicated address like [email protected] or [email protected], while the recipient is the verified personal or business address. Configuring these identities correctly ensures emails arrive reliably and appear professional to recipients.

The verified sender address must match the "From" address used by the Lambda function when sending emails. SES validates this match as part of its sending authorization policies, preventing spoofing of unverified addresses. Configure the Lambda function's email parameters to use verified addresses exclusively, avoiding submission failures due to authorization errors.

Consider also configuring a dedicated subdomain for email sending, such as mail.example.com, rather than using the primary domain. This separation provides several benefits: it does not affect the reputation of the primary domain if sending issues occur, allows more granular DNS configuration for email authentication records, and creates a clear boundary between website hosting and email infrastructure.

Creating the Lambda Function

Function Architecture

The Lambda function serves as the processing engine for contact form submissions. Its responsibilities include receiving form data, validating input, formatting the email, and instructing SES to deliver the message. The handler function--the entry point invoked by Lambda--receives event data containing the submission and returns a response indicating success or failure.

When API Gateway receives a POST request to the contact form endpoint, it packages the request body and headers into an event object passed to the Lambda function. The function extracts the form fields--typically name, email, subject, and message--from this event data. After performing any required validation, the function constructs an email message and calls SES to send it.

Sample Handler Code

const AWS = require('aws-sdk');
const ses = new AWS.SES({ region: 'us-east-1' });

exports.handler = async (event) => {
 try {
 const body = JSON.parse(event.body);
 const { name, email, subject, message } = body;

 const params = {
 Source: process.env.FROM_EMAIL,
 Destination: { ToAddresses: [process.env.TOO_EMAIL] },
 Message: {
 Subject: { Data: `Contact Form: ${subject}` },
 Body: {
 Text: {
 Data: `From: ${name} (${email})\n\nMessage:\n${message}`
 }
 }
 }
 };

 await ses.sendEmail(params).promise();

 return { statusCode: 200, body: JSON.stringify({ message: 'Email sent successfully' }) };
 } catch (error) {
 return { statusCode: 500, body: JSON.stringify({ message: 'Failed to send email', error: error.message }) };
 }
};

This handler parses the request body, extracts form fields, constructs SES parameters, sends the email, and returns an appropriate HTTP response. The implementation uses async/await for clean asynchronous code, with error handling that returns meaningful responses for both success and failure scenarios.

Environment Variables

Hardcoding email addresses and other configuration values into Lambda functions creates maintenance challenges and security risks. Environment variables provide a better approach, allowing configuration changes without code modifications and keeping sensitive values out of source control.

Configure the following environment variables for the Lambda function: the sender email address (FROM_EMAIL), the recipient email address (TO_EMAIL), the AWS region for SES operations (SES_REGION), and optionally a default subject line or company name for the email sender field. These values can be updated in the Lambda console or via infrastructure-as-code tools without redeploying the function code.

The function references these variables using process.env:

const ses = new AWS.SES({ region: process.env.SES_REGION });
const params = {
 Source: process.env.FROM_EMAIL,
 Destination: {
 ToAddresses: [process.env.TOO_EMAIL]
 },
 // ...
};

This approach enables different configurations for development, staging, and production environments while maintaining identical function code. The same Lambda function can serve multiple contact forms by changing environment variables, or multiple functions can share configuration values through AWS Systems Manager Parameter Store for more complex deployments.

Error Handling and Logging

Robust error handling ensures users receive appropriate feedback while maintaining operational visibility. The Lambda handler should catch errors at multiple levels: parsing errors when processing the request body, validation errors when checking input data, and AWS service errors when communicating with SES.

When errors occur, return HTTP status codes that enable the frontend to display appropriate messages. A 400 status indicates client errors (invalid input, missing fields), while 500 indicates server errors (AWS service failures, configuration problems). The response body should include error details for debugging while avoiding exposure of sensitive internal information.

Enable CloudWatch logging for the Lambda function to capture execution details. Lambda automatically streams console output to CloudWatch Logs, creating an audit trail of all form submissions. Include structured log entries that record submission timestamps, sender addresses (sanitized for privacy), processing duration, and outcome. This data proves invaluable for troubleshooting, performance optimization, and security analysis.

Configuring API Gateway

Creating the HTTP Endpoint

API Gateway exposes the Lambda function as an HTTP endpoint that browsers can invoke via standard fetch or XMLHttpRequest calls. The service handles the translation between HTTP requests and Lambda invocations, managing the protocol differences and data format conversions.

Create a new API in the API Gateway console, choosing the HTTP API option for contact form use cases. HTTP APIs provide simpler configuration and lower costs compared to REST APIs while supporting all required features including CORS, custom domains, and stage management. Name the API descriptively, such as "contact-form-api," to distinguish it from other APIs in the account.

After creating the API, configure a route for handling POST requests to the /submit path. Routes define the mapping between HTTP methods and backend integrations--in this case, POST requests to /submit trigger the contact form Lambda function. API Gateway evaluates incoming requests against defined routes and dispatches matching requests to the appropriate backend.

Integrate the route with the Lambda function created earlier. API Gateway requires permission to invoke Lambda functions; the console offers to create the necessary resource-based policy automatically during integration. Grant this permission to establish the connection between the HTTP endpoint and the backend function.

Enabling CORS for Cross-Origin Requests

Cross-Origin Resource Sharing (CORS) controls how browsers handle requests originating from different domains. Since static websites often host forms on domains different from the API endpoint, proper CORS configuration enables legitimate cross-origin requests while preventing unauthorized access.

Enable CORS on the /submit route to allow POST requests from any origin during development, with more restrictive configurations for production. The CORS configuration specifies allowed origins, HTTP methods, and headers that the API permits. For contact forms, the configuration typically allows POST method with Content-Type and Accept headers, with the origin set to the website's domain.

API Gateway provides CORS configuration options at both the route level and globally. Route-level CORS applies to specific endpoints, while API-level CORS provides default settings that routes inherit. Configure CORS on the /submit route explicitly, ensuring the browser receives the necessary headers confirming the request is permitted.

The CORS headers returned by API Gateway tell the browser that the request is authorized:

  • Access-Control-Allow-Origin: The website domain (or * for development)
  • Access-Control-Allow-Methods: POST, OPTIONS
  • Access-Control-Allow-Headers: Content-Type, Accept

Browsers automatically send OPTIONS preflight requests before POST requests to verify CORS permissions. API Gateway should respond to these preflight requests with appropriate headers, which the browser then uses to determine whether to proceed with the actual POST request.

Deploying and Managing Stages

After configuring routes and integrations, deploy the API to make it accessible on the internet. API Gateway uses a stages model where each deployment creates a new stage representing a specific version of the API. Stages provide distinct endpoints (such as production or development) and enable configuration of stage variables, caching, and logging.

Create a production stage when ready for public access, naming it "prod" or "production." The stage name becomes part of the invoke URL, creating endpoints like https://abc123.execute-api.us-east-1.amazonaws.com/production/submit. API Gateway generates this URL upon deployment, which the frontend configuration requires to submit form data.

Consider implementing a development stage for testing changes before production deployment. Deploy updates to the development stage first, verify functionality with test submissions, then promote the same deployment to production. This workflow reduces the risk of breaking changes reaching users and provides a rollback capability by redeploying previous stages.

Building the Frontend Form

HTML Form Structure

The frontend contact form captures user input using standard HTML form elements styled according to the website's design system. The form should include fields for name, email address, subject (optional but helpful for organization), and message content. Each field requires appropriate labels, input types, and validation attributes.

<form id="contact-form" class="contact-form">
 <div class="form-group">
 <label for="name">Name</label>
 <input type="text" id="name" name="name" required placeholder="Your name">
 </div>

 <div class="form-group">
 <label for="email">Email</label>
 <input type="email" id="email" name="email" required placeholder="[email protected]">
 </div>

 <div class="form-group">
 <label for="subject">Subject</label>
 <input type="text" id="subject" name="subject" placeholder="What is this regarding?">
 </div>

 <div class="form-group">
 <label for="message">Message</label>
 <textarea id="message" name="message" required rows="5" placeholder="Your message..."></textarea>
 </div>

 <button type="submit" class="submit-button">Send Message</button>
</form>

The form uses semantic HTML elements that provide accessibility benefits and browser-native validation. The required attribute on name, email, and message fields triggers browser validation before submission, improving user experience without custom JavaScript. Placeholder text provides hints about expected input without replacing visible labels.

JavaScript Submission Handler

JavaScript intercepts form submissions to send data to the serverless backend rather than performing a traditional form POST. The submission handler collects field values, validates input, sends the request via the Fetch API, and provides user feedback based on the outcome.

document.getElementById('contact-form').addEventListener('submit', async function(e) {
 e.preventDefault();

 const submitButton = this.querySelector('.submit-button');
 const originalButtonText = submitButton.textContent;
 submitButton.textContent = 'Sending...';
 submitButton.disabled = true;

 const formData = {
 name: document.getElementById('name').value,
 email: document.getElementById('email').value,
 subject: document.getElementById('subject').value || 'Contact Form Submission',
 message: document.getElementById('message').value
 };

 try {
 const response = await fetch('https://YOUR-API-ID.execute-api.REGION.amazonaws.com/STAGE/submit', {
 method: 'POST',
 headers: {
 'Content-Type': 'application/json'
 },
 body: JSON.stringify(formData)
 });

 if (response.ok) {
 showMessage('Thank you! Your message has been sent.', 'success');
 this.reset();
 } else {
 const error = await response.json();
 showMessage(error.message || 'Failed to send message. Please try again.', 'error');
 }
 } catch (error) {
 showMessage('Network error. Please check your connection and try again.', 'error');
 } finally {
 submitButton.textContent = originalButtonText;
 submitButton.disabled = false;
 }
});

This handler disables the submit button during submission to prevent duplicate requests, provides visual feedback through button text changes, and displays success or error messages based on the API response. The error handling covers both HTTP errors (non-200 status codes) and network failures (DNS issues, connection timeouts).

User Experience Enhancements

Beyond basic submission handling, several enhancements improve the form's user experience. Visual feedback during submission reassures users that their action is processing. Success confirmation after submission acknowledges receipt and sets expectations for follow-up timing. Error messages should be specific and actionable, guiding users toward resolution.

Loading indicators communicate system activity during the submission process. Replace the submit button text with "Sending..." or display a spinner icon during the API call. Disable the button to prevent accidental duplicate submissions, which could result in duplicate messages to the recipient.

Form validation should occur both client-side (for immediate feedback) and server-side (for security). Client-side validation using HTML5 attributes and JavaScript catches common errors before submission, reducing unnecessary API calls. Server-side validation in the Lambda function rejects invalid submissions, preventing malformed data from reaching the email inbox.

For comprehensive form implementations, including validation libraries and accessibility features, our web development services team can build contact forms that meet your specific requirements.

Security Best Practices

Input Validation and Sanitization

All form input requires validation and sanitization before processing or sending. Malicious actors may attempt to inject unwanted content through form fields, potentially causing issues with email rendering or exploiting vulnerabilities in email clients that display the messages.

Validate field formats at multiple levels: HTML5 attributes provide browser-side validation, JavaScript provides immediate feedback with custom logic, and Lambda function validation provides authoritative checking. Check that email addresses match expected patterns, that message content does not exceed reasonable lengths, and that required fields contain values.

Sanitize input to remove or escape potentially harmful content. For contact forms that send email, HTML entities in message content can cause rendering issues or potentially expose recipients to cross-site scripting risks if the email client executes embedded scripts. Escape special characters in the message body when constructing the email, or use text-only email format which inherently prevents HTML execution.

Consider implementing length limits on all fields to prevent resource exhaustion attacks. A Lambda function receiving a message containing megabytes of text could exceed timeout limits or incur unexpected charges. Set reasonable maximum lengths (for example, 4000 characters for the message field) and return appropriate error responses when limits are exceeded.

Spam Prevention Mechanisms

Contact forms frequently attract spam submissions, which can clutter inboxes and waste recipient time. Implementing spam prevention reduces the volume of unwanted messages while minimizing friction for legitimate visitors.

CAPTCHA challenges present tests that distinguish humans from automated submission systems. Google reCAPTCHA v3 provides invisible protection that analyzes user behavior without requiring explicit interaction, while v2 presents visible challenges when suspicious activity is detected. Integrate reCAPTCHA by adding the verification step to the form submission handler, requiring successful verification before sending data to Lambda.

Rate limiting controls how frequently the API accepts submissions from individual IP addresses or API keys. API Gateway provides built-in throttling configuration that limits requests per second, preventing automated systems from flooding the form with submissions. Configure limits appropriate for expected traffic volumes--typically 1-5 requests per minute per IP address.

Honey pot fields present hidden inputs that legitimate users leave blank but automated bots complete. Include a field with a name like "website_url" that is hidden using CSS (display: none) and ignored by real users. If the field contains a value when submitted, reject the request as spam.

Form timing analysis measures how long users spend completing the form. Real users typically spend 10-60 seconds reading and composing messages, while automated submissions occur nearly instantaneously. Reject submissions completed in less than 3-5 seconds as likely spam while allowing normal users to submit quickly without frustration.

API Security Considerations

The API endpoint represents an attack surface that requires protection beyond the form itself. Implement security measures that prevent unauthorized access while maintaining usability for legitimate submissions.

Authentication tokens can verify that form submissions originate from the legitimate website rather than malicious scripts. Generate a unique token during page rendering, include it as a hidden form field, and validate the token in the Lambda function. Tokens should expire after a short duration (15-30 minutes) to limit their utility if compromised.

IP allowlisting restricts API access to requests originating from trusted sources. For internal business applications, configure API Gateway to accept requests only from the organization's IP ranges. For public websites, this approach is less practical but can be combined with geographic restrictions if the business operates in specific regions.

Request size limits prevent denial-of-service attacks through oversized submissions. API Gateway supports maximum request body sizes that trigger 413 Payload Too Large responses when exceeded. Configure limits that accommodate legitimate messages while preventing resource exhaustion through abnormally large requests.

Alternative Approaches

Alternative Cloud Providers

While AWS provides comprehensive serverless infrastructure, several alternatives offer comparable or superior functionality for specific use cases. Evaluating alternatives helps identify solutions best suited to particular requirements, budgets, or existing cloud investments.

Google Cloud Platform (GCP) offers Cloud Functions with similar pricing and capabilities to AWS Lambda. The Firebase platform, which GCP owns, provides a particularly elegant solution for contact forms through Cloud Functions integrated with Firebase Authentication, Firestore for data storage, and Firebase Hosting for static content. Developers already using GCP or Firebase may find tighter integration and simplified billing through this ecosystem.

Microsoft Azure provides Azure Functions with extensive language support and Visual Studio integration. Azure's Logic Apps enable building contact form workflows through a visual designer, sending emails through Office 365 or Outlook.com accounts without writing code. Organizations invested in Microsoft 365 may prefer this approach for authentication simplicity and unified billing.

Serverless Framework abstracts cloud provider differences through a declarative configuration format. A single codebase can deploy to AWS, GCP, or Azure with minimal modifications, reducing vendor lock-in concerns. The framework handles infrastructure provisioning, deployment, and monitoring through standardized commands, accelerating development velocity.

Third-Party Form Services

Several services specialize in contact forms and related functionality, offering complete solutions without custom backend development. These services typically provide form builders, data storage, notification systems, and analytics dashboards.

Formspree enables forms by simply specifying a unique endpoint URL. The form's action attribute points to Formspree's servers, which handle submission processing and forward messages via email. The free tier handles limited submissions, with paid plans providing additional features like file uploads, spam filtering, and analytics.

Netlify Forms provides contact form functionality specifically for sites deployed through Netlify's hosting platform. The service automatically detects form elements in HTML, stores submissions in a dashboard, and can trigger notifications or webhooks. This integration eliminates the need for separate backend configuration when using Netlify for hosting.

Formcarry offers form handling with email forwarding, file uploads, and spam filtering. The service provides a dashboard for viewing submissions, integration with popular tools like Slack and Zapier, and webhooks for custom processing. Pricing scales with submission volumes, making it suitable for sites with predictable traffic patterns.

Self-Hosted Alternatives

For organizations preferring self-hosted solutions or operating in restricted environments, several open-source projects enable serverless-like functionality on owned infrastructure.

FormMail and similar PHP scripts provide classic server-side form processing, though they require PHP hosting rather than truly serverless execution. These solutions work with traditional shared hosting but lack the automatic scaling and pay-per-use pricing of cloud functions.

Webhooks to Slack provide a simple notification system for teams already using Slack. Create an incoming webhook in Slack, then have the form submission JavaScript post directly to the webhook URL. Messages appear in a Slack channel with full formatting, enabling quick triage and response assignment.

Self-hosted Lambda alternatives like OpenFaaS or Kubeless run on Kubernetes clusters, providing function-as-a-service capabilities on owned infrastructure. These solutions suit organizations with Kubernetes expertise and specific compliance requirements that prevent using public cloud services.

When evaluating alternatives, consider not just the immediate implementation but long-term maintenance, scalability requirements, and team expertise. Our web development services include architecture consultation to help you choose the right approach for your specific situation.

Test Lambda Function

Verify direct invocation through console with simulated events. Test edge cases including empty fields, malformed email addresses, and unusually long inputs.

Test API Gateway

Verify CORS headers and endpoint accessibility. Test preflight OPTIONS requests to confirm they return appropriate CORS headers.

Test End-to-End

Submit test forms from production website. Test from multiple browsers and devices to confirm cross-browser compatibility.

Configure Monitoring

Set up CloudWatch metrics and alarms. Create alerts that trigger when error rates exceed thresholds.

Enable Logging

Configure CloudWatch Logs for Lambda. Include structured log entries that record submission timestamps and outcomes.

Document Endpoint

Store API URL securely for frontend configuration. Document the integration process for future maintenance.

Frequently Asked Questions

Need Help Building Your Contact Form?

Our team specializes in serverless architectures and can implement a complete contact form solution tailored to your requirements. From static site hosting to backend integration, we handle the entire implementation.

Sources

  1. DEV Community: Build a Serverless Contact Form with Lambda + API Gateway + SES - Comprehensive step-by-step tutorial covering AWS Lambda, API Gateway, and SES integration with practical code examples

  2. Devas.life: How To Add A Contact Form To A Static Website With AWS Lambda - In-depth guide with architectural diagrams, IAM role configuration, and production considerations

  3. AWS Lambda Pricing - Official documentation on pay-per-request pricing model

  4. AWS Simple Email Service Documentation - Email sending service capabilities and configuration