AWS Builder Center
Contact Form πŸ“§ - Making Your Website Interactive

Contact Form πŸ“§ - Making Your Website Interactive

Learn to build a contact form that sends you email notifications when visitors fill it out. Understand the fundamentals of web forms, serverless computing, and notification systems.

Developer Advocate @ AWS
Welcome to Day 6 of the AWS Builder Challenge #2Β  πŸš€! Today you'll learn how to build a contact form that sends you email notifications when visitors send you a message.
In about 60 minutes, you will learn why contact forms need more than just HTML, how to run code in the cloud when needed, and how to connect web forms to backend services. We are not just adding a form, we are learning how websites process information.
By the end of today, you'll have a working contact form that follows good practices. Let's get started!

What you will accomplish today

  • Understand what happens when someone submits a form
  • Learn how to run code in the cloud
  • Set up email notifications
  • Configure secure permissions
  • Add a contact form to your website

What you will need

  • Your AWS account from Day 1Β  πŸ’»
  • Your Amplify website from Day 5Β  🌐
  • About 60 minutes of your time ⏰
  • Email address to receive notifications πŸ“§

Step 1: What happens when someone clicks 'submit'? πŸ€·β€β™€οΈ

Picture this: someone visits your website, fills out your contact form, clicks "Submit" and... then what? If you're thinking "an email magically appears in my inbox," there's actually a whole journey happening behind the scenes.
JavaScript running in the browser can validate forms, manipulate data, and make API calls to servers. But it can't directly send emails or access databases, those operations need to happen on a server. Think of browser JavaScript as the messenger that asks servers to do the tasks.

Client-server exchange

Here's what actually happens when someone clicks submit:
  1. JavaScript packages the data and makes an API call to a server
  2. Server receives the API call and processes it (validates, stores, sends emails, etc.)
  3. Server responds back to the JavaScript - "success!" or "error"
  4. JavaScript updates the page based on the response
This isn't just contact forms, it is a fundamental pattern behind every interactive website. Shopping carts, user logins, file uploads: they all follow this same client-server process.

Traditional servers vs. Serverless functions

Traditionally, you'd rent a server that's always running, waiting for messages on your form. Serverless functions flip this: your code only runs when needed. Someone submits a form β†’ code runs, processes the message, then stops running.
It's like ordering takeout when you're hungry, instead of hiring a personal chef who sits in your kitchen all day.
This "something happens β†’ code responds" pattern is called event-driven architecture:
  • Photo upload β†’ resize and generate thumbnails
  • Order placed β†’ charge card and send confirmation
  • Form submitted β†’ validate and send notification
Understanding this pattern is important for modern web development. The skills you learn building this contact form apply everywhere - from simple websites to complex systems.

Step 2: Receive notifications πŸ“§

When someone fills out your contact form, how do you actually get notified? This is where messaging systems come in. You need a separate system that can receive a message when your form is submitted and sends you an email with the details.

How notification systems works

Think of it like a bulletin board:
  • You create a "topic" (like a bulletin board called "Contact Form Messages")
  • You subscribe to that topic (like saying "email me whenever someone posts here")
  • Other systems can send messages to that topic
  • Everyone subscribed gets a notification
This pattern is called publish-subscribe: one system publishes messages, and multiple systems can subscribe to receive them.

Create your notification topic

  1. Navigate to SNS - In the AWS Console, search for "SNS" and click on it
  2. Create a topic:
    • Topic name: contact-form-notifications
    • Click "Next step"
  3. Details:
    • Type: Select "Standard" (handles multiple messages, order doesn't matter for notifications)
    • Name: contact-form-notifications
    • Leave other settings as default
    • Click "Create topic"
  4. Save your Topic ARN - Copy the long identifier that appears (looks like arn:aws:sns:us-east-1:123456789012:contact-form-notifications)

Subscribe your email

  1. Create subscription - In your topic, click "Create subscription"
  2. Configure it:
    • Protocol: Email
    • Endpoint: Enter your email address
    • Click "Create subscription"
  3. Confirm it - Check your email for a confirmation message from AWS and click "Confirm subscription"
  4. Test it - Back in SNS, click "Publish message", add a test subject and message, then click "Publish". You should get the test email within seconds.

Step 3: Configure permissions πŸ”

Now we need to give our contact form code permission to send emails through SNS. AWS services don't trust each other by default, we have to explicitly say how they are allowed to interact with each other.

Identity and Access Management

You are already familiar with IAM from Day 1Β , when we used it to create an admin user for our AWS account. IAM (Identity and Access Management) controls who can do what in your AWS account. Instead of writing code that has your AWS login and password, we create a "role". A role works like a permission that says "code with this role can send messages to SNS topics."

Create a permission policy

A policy is a document that defines what actions are allowed or denied. Think of it like a permission slip that says "the holder of this slip can do X, but not Y." In our case, we're creating a policy that says "whoever has this policy can publish messages to SNS topics, but nothing else."
  1. Go to IAM - Search for "IAM" in the AWS Console
  2. Create policy - Click "Policies" β†’ "Create policy"
  3. Select a service - Choose "SNS"
  4. Actions allowed - Search for "Publish" and select the checkbox
  5. Resources:
    • Select "Specific"
    • Click on "Add ARNs"
    • Select "Text" on Specify ARNs
    • Paste the ARN of the topic you created on Step 2 (similar to arn:aws:sns:us-east-1:123456789012:contact-form-notifications)
  6. Add ARN - Click on "Add ARNs" and then click on "Next"
  7. Policy details: Name your policy ContactFormSNSPublish and then cick on "Create Policy"

Create a role

Now we'll create a role that includes our policy plus a logging policy. This lets us assign the permissions our code will need.
  1. Create role - In IAM, click "Roles" β†’ "Create role"
  2. Set it up:
    • Trusted entity type: "AWS service"
    • Use case: "Lambda"
    • Click "Next"
  3. Add permissions - Select these two policies:
    • AWSLambdaBasicExecutionRole (lets Lambda write logs for debugging)
    • ContactFormSNSPublish (the policy we just created)
  4. Finish:
    • Click "Next"
    • Role name: contact-form-lambda-role
    • Description: β€œAllows Lambda functions to publish messages to the contact-form-notifications SNS topic”
    • Click "Create role"

Step 4: Write the code to process the messages ⚑

Now let's create the code that will run when someone submits your contact form. AWS LambdaΒ  runs your code in response to events, like when someone submits a form. When that happens, Lambda will:
  1. Receive the form data
  2. Run your code to process it
  3. Send the message to our SNS topic (which emails us)
  4. Send a response back to the visitor's browser
All that without you having to set up or manage any servers.

Create your function

  1. Navigate to Lambda - Search for "Lambda" in the AWS Console
  2. Create function:
    • Click "Create a function"
    • Choose "Author from scratch"
    • Function name: contact-form-function
    • Runtime: "Python 3.13"
  3. Set permissions:
    • Expand "Change default execution role"
    • Select "Use an existing role"
    • Choose contact-form-lambda-role
  4. Create it - Click "Create function"

Add your code

This code receives form data, validates it, sends you an email notification via SNS, and responds to the visitor with success or error messages.
  1. Replace the default code with the code below.
  2. πŸ”§ Replace YOUR_TOPIC_ARN_HERE with your actual Topic ARN from Step 1.
  3. Deploy the function by clicking the β€œDeploy” button in the left menu.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import json
import boto3

def lambda_handler(event, context):
try:
# Parse the form data from the request
body = json.loads(event['body'])
name = body.get('name', '')
email = body.get('email', '')
message = body.get('message', '')

# Validate that all required fields are present
if not all([name, email, message]):
return {
'statusCode': 400,
'body': json.dumps({'error': 'Missing required fields'})
}

# Format the notification message
notification_message = f"""Contact Form Submission

Name: {name}
Email: {email}

Message:
{message}"""


# Send notification via SNS
sns = boto3.client('sns')
sns.publish(
# Replace with your Topic ARN from Step 1
TopicArn='YOUR_TOPIC_ARN_HERE',
Subject=f'Contact Form: {name}',
Message=notification_message
)

# Return success response
return {
'statusCode': 200,
'body': json.dumps({'message': 'Message sent successfully!'})
}

except Exception as e:
# Return error response if anything goes wrong
return {
'statusCode': 500,
'body': json.dumps({'error': 'Failed to send message'})
}

Step 5: Create a function URL 🌐

Your code exists, but how does your webpage call it? When someone clicks "Submit" on your contact form, their browser needs to know where to send that information.
Function URLsΒ  give your Lambda function a web address (like https://abc123.lambda-url.us-east-1.on.aws) that browsers can call directly. This creates a simple endpoint for your contact form without needing to set up additional things

Creating a Function URL

  1. Create:
    • In your Lambda function, go to "Configuration" β†’ "Function URL"
    • Click "Create function URL"
  2. Auth type: NONE (allows public access - fine for contact forms)
  3. Configure CORS: CORS (Cross-Origin Resource Sharing) is a security feature that controls which websites can call your function. Since your contact form and Lambda function are on different domains, you need to tell AWS it's okay for your website to send data to your function.
    • Additional settings: Expand additional settings
    • Configure cross-origin resource sharing (CORS): Select this option
    • Allow origin: Your website's domain (e.g. https://main.ddrcpyox7wwsr.amplifyapp.comΒ )
    • Allow headers: Content-Type
    • Allow methods: POST
  4. Save
  5. Copy the Function URL - This is your endpoint, we will use it on your page in the next step (looks likehttps://abc123.lambda-url.us-east-1.on.aws)

Step 6: Add the contact form to your website πŸ“

Now let's add the contact form code to your website. Since your website is connected to GitHub (from Day 5Β ), you can edit your code directly in your browser:
Option 1: Quick edit
  1. Go to your GitHub repository β†’ click any file β†’ click the pencil icon (✏️) to edit
Option 2: Full editor (recommended)
  1. Go to your GitHub repository and press the . (period) key, OR
  2. Change github.comΒ  to github.dev in your URL (e.g. https://github.dev/your-username/your-repo-name)
  3. This opens GitHub's web-based VS Code editor with all your files

Add contact form to your page

Add a contact form to your index.html page right before the closing </main> tag:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!-- CONTACT FORM SECTION - Added for user contact functionality -->
<section class="contact-form">
<h3>Get In Touch</h3>
<p>Have a question or want to connect? Send me a message!</p>
<!-- Form message container for success/error messages -->
<div id="form-message" class="form-message" style="display: none;"></div>
<form class="contact-form-container">
<!-- Contact form input fields -->
<div class="form-group">
<label for="name">Name</label>
<input type="text" id="name" name="name" required>
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email" required>
</div>
<div class="form-group">
<label for="message">Message</label>
<textarea id="message" name="message" rows="5" required></textarea>
</div>
<!-- Contact form submit button -->
<button type="submit" class="contact-submit-btn">Send Message</button>
</form>
</section>
<!-- END CONTACT FORM SECTION -->

Add JavaScript to your page

Add this <script> tag with the JavaScript code to submit your form to the Lambda function right before the closing </body> tag on your index.html page.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<script>
// Contact form handler - Sends form data to AWS Lambda
const LAMBDA_URL = 'YOUR_LAMBDA_FUNCTION_URL_HERE'; // Replace with your Function URL

document.addEventListener('DOMContentLoaded', function() {
const form = document.querySelector('.contact-form-container');

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

const submitBtn = form.querySelector('.contact-submit-btn');
const messageDiv = document.getElementById('form-message');

// Show loading state
submitBtn.textContent = 'Sending...';
submitBtn.disabled = true;
messageDiv.style.display = 'none';

// Get form data
const formData = {
name: form.name.value,
email: form.email.value,
message: form.message.value
};

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

if (response.ok) {
showMessage('Message sent successfully! Thank you for reaching out.', 'success');
form.reset();
} else {
showMessage('Failed to send message. Please try again.', 'error');
}
} catch (error) {
showMessage('Network error. Please check your connection and try again.', 'error');
}

// Reset button
submitBtn.textContent = 'Send Message';
submitBtn.disabled = false;
});
});

function showMessage(text, type) {
const messageDiv = document.getElementById('form-message');
messageDiv.textContent = text;
messageDiv.className = `form-message ${type}`;
messageDiv.style.display = 'block';

// Auto-hide after 10 seconds
setTimeout(() => {
messageDiv.style.display = 'none';
}, 10000);
}
</script>

Update your CSS

Update your CSS on styles.css to make the contact form look good and functional:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
/* CONTACT FORM STYLES - Added for contact form functionality */
.contact-form {
background: white;
border-radius: 8px;
padding: 40px;
margin-top: 40px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
position: relative;
}

/* Contact form decorative element */
.contact-form::before {
content: '';
position: absolute;
top: -10px;
right: 40px;
width: 40px;
height: 40px;
background: linear-gradient(135deg, #ec4899 0%, #be185d 100%);
border-radius: 6px;
opacity: 0.9;
}

.contact-form h3 {
color: #232f3e;
font-size: 32px;
font-weight: 700;
margin-bottom: 16px;
letter-spacing: -0.02em;
text-align: center;
}

.contact-form > p {
font-size: 16px;
color: #687078;
margin-bottom: 32px;
text-align: center;
line-height: 1.6;
}

/* Contact form container and layout */
.contact-form-container {
max-width: 600px;
margin: 0 auto;
}

/* Form message styles for success/error notifications */
.form-message {
padding: 12px 16px;
border-radius: 6px;
margin-bottom: 24px;
font-weight: 500;
text-align: center;
}

.form-message.success {
background: #dcfce7;
color: #166534;
border: 1px solid #bbf7d0;
}

.form-message.error {
background: #fef2f2;
color: #dc2626;
border: 1px solid #fecaca;
}

.form-group {
margin-bottom: 24px;
}

.form-group label {
display: block;
font-weight: 600;
color: #232f3e;
margin-bottom: 8px;
font-size: 16px;
}

/* Contact form input and textarea styles */
.form-group input,
.form-group textarea {
width: 100%;
padding: 12px 16px;
border: 1px solid #e5e7eb;
border-radius: 6px;
font-size: 16px;
font-family: inherit;
background: #f8fafc;
transition: all 0.2s ease;
}

.form-group input:focus,
.form-group textarea:focus {
outline: none;
border-color: #7877c6;
background: white;
box-shadow: 0 0 0 3px rgba(120, 119, 198, 0.1);
}

.form-group textarea {
resize: vertical;
min-height: 120px;
}

/* Contact form submit button */
.contact-submit-btn {
background: #7877c6;
color: white;
padding: 12px 32px;
border: none;
border-radius: 6px;
font-weight: 500;
font-size: 16px;
cursor: pointer;
transition: all 0.2s ease;
display: block;
margin: 0 auto;
}

.contact-submit-btn:hover {
background: #6b6ab8;
transform: translateY(-1px);
}

@media (max-width: 768px) {
/* Contact form mobile styles */
.contact-form {
padding: 24px;
}

/* Contact form mobile heading */
.contact-form h3 {
font-size: 24px;
}
}

/* END CONTACT FORM STYLES */

Deploy your changes

  1. Add a commit message like "Add contact form"
  2. Click "Commit changes"
  3. Amplify automatically deploys your updates in 2-3 minutes
🚨 Having trouble? If you run into any issues with the code above, you can find the complete working version on the aws-builder-challenge-website-template  GitHub repo.

Step 7: Test everything πŸ§ͺ

Let's make sure everything works end-to-end.
  1. Visit your contact page - Go to your Amplify website and scroll down to the contact form section
  2. Fill out the form:
    • Enter your name
    • Enter your email address
    • Write a test message
    • Click "Send Message"
  3. Check it worked:
    • You should see a success message on the webpage
    • Check your email - you should get a notification within 30 seconds
    • The form should clear after sending

If something goes wrong πŸ˜–

Check some of the common problems and solutions here. If you’re still running into issues, leave a comment with details so I can help out.
No email received?
  • Check your spam folder
  • Make sure you confirmed your SNS subscription
  • Verify the Topic ARN in your Lambda code is correct
Form shows an error?
  • Check your browser's developer console (F12) for error messages
  • Make sure your Lambda Function URL is correct in the HTML
Still having issues?
  • In Lambda, go to "Monitor" β†’ "View CloudWatch logs"
  • Look for error messages that explain what went wrong

Checklist

Check the items below to make sure you have completed today's challenge:
  • [ ] Created SNS topic and confirmed email subscription
  • [ ] Created IAM role with proper permissions
  • [ ] Created Lambda function with contact form code
  • [ ] Configured Function URL
  • [ ] Added contact form page to website
  • [ ] Added JavaScript to handle form submissions and connect to Lambda function
  • [ ] Tested complete flow from form to email
πŸŽ‰ Congratulations! You've built a working contact form that runs in the cloud!
What's next? Tomorrow on Day 7, you'll document your journey and share your success! You'll write about your experience, take screenshots of what you built, and share your website with friends and family. It's time to celebrate what you've accomplished this week!
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article