Serverless Contact Form with API Gateway, Lambda, and SES: A Complete Guide
Static sites have become incredibly popular for portfolios, landing pages, and business websites. Services like S3 static hosting and CloudFront make deployment simple and cheap. However, there is one common challenge: handling user input.
A contact form seems straightforward until you remember that static sites have no server to process form submissions. You need somewhere to receive the data, validate it, and do something useful with it.
This article walks through building a serverless contact form backend using AWS services. By the end, you will have a solution that receives form submissions, sends email notifications, and costs nearly nothing to operate.
Architecture Overview
The serverless contact form backend uses three core AWS services working together.
Amazon API Gateway provides an HTTPS endpoint that your frontend form can call. It receives the POST request containing the form data and passes it along for processing.
AWS Lambda runs your backend logic. It receives the form data from API Gateway, validates the input, formats an email, and triggers the email delivery. You pay only when the function executes.
Amazon Simple Email Service (SES) handles email delivery. Lambda tells SES what to send and where, and SES manages the actual delivery, retries, and bounce handling.
The flow works like this. A visitor fills out your contact form and clicks submit. JavaScript on your static site sends a POST request to your API Gateway endpoint. API Gateway triggers your Lambda function with the form data. Lambda validates the input and calls SES to send an email. SES delivers the email to your inbox. Lambda returns a success response through API Gateway to your frontend. Your static site displays a thank you message.
This entire process typically completes in under one second and costs a fraction of a cent.
Setting Up Amazon SES
Start with SES because email delivery requires verification before you can use it.
Understanding the SES Sandbox
New AWS accounts start with SES in sandbox mode. This is a restricted environment that prevents abuse. In sandbox mode, you can only send emails to verified email addresses. This is fine for testing but not for production.
To exit sandbox mode, you submit a request through the AWS console. AWS reviews your use case and typically approves legitimate requests within 24 hours. You do not need production access to build and test your contact form, but you will need it before going live.
Verifying Your Email Address
Navigate to SES in the AWS console and go to verified identities. Add the email address where you want to receive contact form submissions. AWS sends a verification email with a link you must click.
For sending emails, you have two options. You can verify a specific email address that will appear in the From field. Alternatively, you can verify an entire domain, which allows sending from any address at that domain.
Domain verification requires adding DNS records to prove ownership. If you manage your domain through Route 53, AWS can add these records automatically. For external DNS providers, you manually add the TXT records AWS provides.
Choosing a Region
SES is not available in all AWS regions. Check which regions support SES and choose one close to your users or in the same region as your other resources. Common choices include us-east-1, us-west-2, and eu-west-1.
Remember that your Lambda function must call SES in the region where you verified your email addresses.
Creating the AWS Lambda Function
The Lambda function is the brain of your contact form backend. It receives form submissions, validates them, and orchestrates email delivery.
Function Design
Keep the function focused on a single responsibility. It should accept form data, validate required fields exist and contain reasonable values, format a readable email, call SES to send it, and return an appropriate response.
Resist the temptation to add features like database storage or complex analytics in this initial version. Start simple and extend later if needed.
Input Validation
Never trust user input. Your function should verify that required fields are present before processing. Check that email addresses look roughly valid. Enforce reasonable length limits on message content. Strip or escape any potentially dangerous content.
Validation protects you from malformed requests and reduces the chance of processing garbage data or abuse attempts.
Email Formatting
Consider what information you want in the notification email. Typically you include the sender name, their email address for replies, the message content, and a timestamp. You might also include metadata like which page the form was submitted from.
Format the email so it is easy to scan. A wall of text is harder to process than clearly labeled sections.
Error Handling
Your function should handle failures gracefully. SES might be temporarily unavailable. The input might be malformed. Network issues happen.
Return clear error responses that help your frontend display appropriate messages. Log errors with enough detail to debug problems later. Avoid exposing internal error details to users as this can reveal information useful to attackers.
IAM Permissions
Your Lambda function needs permission to call SES. Create an IAM role with the minimum required permissions. The function needs ses:SendEmail for the specific verified identities you are using. It does not need full SES access.
Follow the principle of least privilege. Grant only what the function needs to operate.
Configuring Amazon API Gateway
API Gateway creates the public endpoint that your frontend calls.
Choosing an API Type
API Gateway offers two main options for this use case.
REST APIs are the traditional option with more features and configuration options. They work well and have extensive documentation.
HTTP APIs are newer, cheaper, and faster. They have fewer features but everything you need for a simple contact form. For most contact form backends, HTTP APIs are the better choice.
Creating the Endpoint
Create a new API and add a single route. You need a POST method at a path like /contact or /submit. Connect this route to your Lambda function.
Configure the integration so API Gateway passes the request body to Lambda. The default settings usually work fine for JSON payloads.
Enabling CORS
Cross-Origin Resource Sharing is essential when your frontend and backend are on different domains. Your static site at yourdomain.com needs permission to call your API at some-id.execute-api.region.amazonaws.com.
Configure CORS on your API Gateway to allow requests from your static site domain. Specify the allowed origin, allowed methods (POST and OPTIONS), and allowed headers (Content-Type at minimum).
Without proper CORS configuration, browsers block the request and your form silently fails.
Deploying the API
API Gateway requires deployment to a stage before your endpoint becomes accessible. Create a stage named prod or live. After deployment, you receive an invoke URL that your frontend uses.
Save this URL. You configure your frontend form to send submissions here.
Connecting Your Frontend
With the backend complete, update your static site to use it.
Form Structure
Your HTML form needs fields for whatever information you want to collect. Common fields include name, email, and message. Add any other fields relevant to your use case.
The form does not need an action attribute or traditional submit behavior. JavaScript handles the submission.
JavaScript Submission
Write JavaScript that intercepts the form submission, prevents the default browser behavior, collects the form field values, and sends them to your API endpoint as a JSON POST request.
Handle the response appropriately. On success, show a thank you message and clear the form. On failure, display an error message asking the user to try again or contact you directly.
User Experience Considerations
Disable the submit button while the request is processing to prevent duplicate submissions. Show a loading indicator so users know something is happening. Provide clear feedback on both success and failure.
Consider what happens if JavaScript fails to load or the user has it disabled. Progressive enhancement with a fallback email link improves accessibility.
Security Considerations
A public form endpoint attracts abuse. Plan for it.
Rate Limiting
Configure throttling on your API Gateway to limit requests per second. This prevents a single source from overwhelming your backend or running up your AWS bill.
Start with conservative limits. A legitimate contact form rarely receives more than a few submissions per minute. You can always increase limits if needed.
Spam Prevention
Bots constantly scan the internet for forms to abuse. Several techniques help reduce spam.
Honeypot fields are hidden form fields that humans never see or fill out. Bots often fill every field they find. If the honeypot field contains data, reject the submission.
Time-based validation rejects submissions that happen too quickly. A human takes at least several seconds to fill out a form. A bot submits instantly. Record when the page loaded and reject submissions that arrive suspiciously fast.
CAPTCHA services like reCAPTCHA add a challenge that bots struggle to complete. They add friction for users but dramatically reduce automated spam.
Consider implementing at least one of these techniques from the start. Cleaning up after a spam attack is more work than preventing it.
Input Sanitization
Sanitize all input before including it in emails. This prevents injection attacks and ensures your emails render correctly. Be especially careful with HTML content if you send HTML-formatted emails.
Cost Analysis
Serverless pricing aligns well with contact form usage patterns.
Expected Costs
API Gateway HTTP APIs cost one dollar per million requests. Lambda charges based on execution time and memory, with a generous free tier of one million requests per month. SES costs ten cents per thousand emails.
For a typical small business website receiving a few hundred contact form submissions monthly, the entire backend costs pennies. Many sites stay within free tier limits indefinitely.
Cost Controls
Set up billing alerts to notify you of unexpected charges. A sudden spike in requests could indicate abuse or a misconfigured frontend.
The rate limiting configured earlier also protects your budget. Even if someone attempts to abuse your endpoint, throttling limits how many requests actually process.
Testing Your Setup
Test thoroughly before announcing your new contact form.
Manual Testing
Submit test entries through your actual form. Verify emails arrive with correct formatting. Test with various input lengths and special characters. Confirm error cases display appropriate messages.
Edge Cases
Test what happens with empty required fields. Submit extremely long messages. Include special characters and emoji. Try submitting while offline. Each test might reveal handling you need to improve.
Monitoring
After launch, monitor your Lambda function logs for errors. Watch SES for bounces or complaints. Set up CloudWatch alarms to alert you if error rates spike.
Going Further
Once your basic contact form works, consider enhancements.
Store submissions in DynamoDB for a searchable archive. Add SNS to send notifications through multiple channels. Implement different notification routing based on form fields. Build an admin dashboard to view and manage submissions.
Each enhancement adds complexity. Add them only when you have a clear need.
Conclusion
A serverless contact form backend solves a real problem elegantly. You get reliable form processing without maintaining servers, pay only for actual usage, and keep full control over your data.
The combination of API Gateway, Lambda, and SES provides a robust foundation that scales from zero to thousands of submissions without configuration changes. For most static sites, this backend costs nothing or nearly nothing to operate.
Build it once, deploy it, and focus on the rest of your site knowing that contact form submissions will reliably reach your inbox.



How do you limit number of Lambda executions as that can be abused as well?