Build a Contact Form with Resend, AWS Lambda, and React.js
sahil

sahil @itxsahil

About: 👋 Hi, I’m Sahil Khan, a Full-Stack Developer passionate about building modern web apps and scalable systems. I love sharing insights and writing about web development on Dev.to. 🚀

Joined:
Jan 27, 2024

Build a Contact Form with Resend, AWS Lambda, and React.js

Publish Date: May 11
0 0

By Sahil Khan

Contact forms are essential for portfolios, SaaS platforms, and production-grade websites, enabling seamless communication between clients and service providers. There are many ways to implement them, but in this blog, we’ll explore how to receive emails from website visitors using AWS Lambda, Resend API, and React.js.

We’ll build a simple Contact Us form using React.js and Tailwind CSS, validate the input, and send the data via an HTTP request to an AWS Lambda function. This function will revalidate and format the data before sending it as an email using Resend.

💡 If you don’t have an AWS account? You can use Vercel’s serverless functions for free with hobby projects. If you're interested in learning about Vercel functions, stay tuned—I'll write a guide on that soon.


🛠️ Setting Up the Serverless Framework

  1. Install the Serverless Framework globally (if you haven't already):
   npm i serverless -g
Enter fullscreen mode Exit fullscreen mode
  1. Create a new project:
   serverless
Enter fullscreen mode Exit fullscreen mode

Note: The Serverless Framework needs access to your AWS credentials. Running the command above will prompt you to configure your credentials if not already set.

Follow the official credential setup guide if needed.

  1. Choose a template:

Select:

   AWS / Node.js / HTTP API
Enter fullscreen mode Exit fullscreen mode
  1. Name your service and proceed.

  2. Choose or create a new app:

   ❯ Create A New App
     ecommerce
     blog
     acmeinc
     Skip Adding An App
Enter fullscreen mode Exit fullscreen mode

Select Create A New App, and name it anything you like.


💻 Time to Dive Into Code

🔧 Tech Stack


👤 What Are We Collecting?

The form will collect the following from users:

  1. Name
  2. Email
  3. Subject
  4. Message (query)

📦 Let’s Start with the Lambda Function

We'll begin with the backend. Your handler.js file (from the Serverless template) includes a default function like this:

exports.hello = async (event) => {
  return {
    statusCode: 200,
    body: JSON.stringify({
      message: "This is a serverless function",
    }),
  };
};
Enter fullscreen mode Exit fullscreen mode

Let’s clean that up and prepare it to process incoming data.

🧹 Clean the boilerplate:

exports.hello = async (event) => {};
Enter fullscreen mode Exit fullscreen mode

📥 Parse incoming request data:

exports.hello = async (event) => {
  const body = JSON.parse(event.body);
};
Enter fullscreen mode Exit fullscreen mode

✅ Validating Input Using Zod

1. Install Zod:

npm i zod
Enter fullscreen mode Exit fullscreen mode

2. Import it:

const { z } = require("zod");
exports.hello = async (event) => {
  const body = JSON.parse(event.body);
};
Enter fullscreen mode Exit fullscreen mode

3. Define the validation function:

const { z } = require("zod");

exports.hello = async (event) => {
  const body = JSON.parse(event.body);
};

function validateBody(body) {
  const schema = z.object({
    name: z.string().min(2),
    email: z.string().email(),
    subject: z.string().min(10),
    message: z.string().min(20),
  });
  return schema.safeParse(body);
}
Enter fullscreen mode Exit fullscreen mode

4. Use it inside your handler:

const { z } = require("zod");
exports.hello = async (event) => {
  const body = JSON.parse(event.body);
  const result = validateBody(body);

  if (!result.success) {
    return {
      statusCode: 400,
      body: JSON.stringify({
        message: result.error.issues,
      }),
    };
  }

  // Continue to sending email...
};
// validate function
Enter fullscreen mode Exit fullscreen mode

📬 Send the Email with Resend

1. Import Resend:

const { Resend } = require("resend");
Enter fullscreen mode Exit fullscreen mode

2. Initialize Resend client:

const resend = new Resend(process.env.RESEND_API_KEY);
Enter fullscreen mode Exit fullscreen mode

3. Send the email:

const { data, error } = await resend.emails.send({
  from: "YourName <username@yourdomain.com>",
  to: ["youremail@example.com"],
  subject: "New Contact Message from Your Portfolio",
  html: `
    <p><strong>From:</strong> ${body.name}</p>
    <p><strong>Email:</strong> ${body.email}</p>
    <p><strong>Subject:</strong> ${body.subject}</p>
    <p><strong>Message:</strong> ${body.message}</p>
  `,
});
Enter fullscreen mode Exit fullscreen mode

4. Handle errors:

if (error) {
  return {
    statusCode: 500,
    body: JSON.stringify({ error }),
  };
}
Enter fullscreen mode Exit fullscreen mode

5. Final success response:

return {
  statusCode: 200,
  body: JSON.stringify({
    message: "Email sent successfully",
    success: true,
  }),
};
Enter fullscreen mode Exit fullscreen mode

The Final code will look like

const { Resend } = require("resend");
const { z } = require("zod");

const resend = new Resend(process.env.RESEND_API_KEY);
exports.hello = async (event) => {
  const body = JSON.parse(event.body);
  const result = validateBody(body);
  if (!result.success) {
    return {
      statusCode: 400,
      body: JSON.stringify({
        message: result.error.issues,
      }),
    };
  }

  const { data, error } = await resend.emails.send({
    from: "YourName <username@yourdomain.com>",
    to: ["youremail@example.com"],
    subject: "New Contact Message from Your Portfolio",
    html: `
    <p><strong>From:</strong> ${body.name}</p>
    <p><strong>Email:</strong> ${body.email}</p>
    <p><strong>Subject:</strong> ${body.subject}</p>
    <p><strong>Message:</strong> ${body.message}</p>
  `,
  });

  if (error) {
    return {
      error: JSON.stringify(error),
      statusCode: 500,
    };
  }
  return {
    statusCode: 200,
    body: JSON.stringify({
      message: "Email sent successfully",
      success: true,
    }),
  };
};

function validateBody(body) {
  const schema = z.object({
    name: z.string().min(2),
    email: z.string().email(),
    subject: z.string().min(10),
    message: z.string().min(20),
  });
  return schema.safeParse(body);
}
Enter fullscreen mode Exit fullscreen mode

🔧 Configuring serverless.yml for Deployment

Now let’s configure the serverless.yml file so your AWS Lambda function can deploy correctly and securely.

org: yourorganization # (optional) your Serverless Dashboard organization
app: somexyz # (optional) your app name
service: somexyz # name of your service/project

provider:
  name: aws
  runtime: nodejs20.x
  region: ap-south-1 # choose your preferred AWS region
  httpApi:
    cors: true # enables CORS for cross-origin requests

functions:
  hello:
    handler: handler.hello
    environment:
      RESEND_API_KEY: ${env:RESEND_API_KEY} # reads the key from your .env file
    events:
      - httpApi:
          path: /send/mail
          method: post
Enter fullscreen mode Exit fullscreen mode

💡 Make sure the path and method match what your frontend will use. A POST method is necessary for sending a body with the request.


🔐 Create a .env File

In the root of your project (same level as serverless.yml), create a .env file and add your Resend API key like so:

RESEND_API_KEY="your-resend-api-key"
Enter fullscreen mode Exit fullscreen mode

🚀 Deploy Your Serverless Function

Open your terminal, make sure you're in the root directory (where your serverless.yml is), and run:

sls deploy
Enter fullscreen mode Exit fullscreen mode

The Serverless Framework will:

  • Package your function
  • Upload it to AWS Lambda
  • Set up the HTTP endpoint
  • Return a live API URL like:
https://xxxxx.execute-api.ap-south-1.amazonaws.com/send/mail
Enter fullscreen mode Exit fullscreen mode

You’ll use this URL to connect your React frontend to your backend.


💬 Creating the Contact Form (Client-Side)

Now let’s build the React frontend to let users send messages through your serverless email API.

Here’s the structure of the form:

  • Name
  • Email
  • Subject
  • Message

All fields are mandatory. We’ll use the useState hook to capture and manage the form data.

Logic & Submission

We’ve implemented a handleSubmit function that performs:

  • Validation of fields
  • API call to the AWS Lambda endpoint
  • Loading indicator and success feedback

Make sure to store your deployed API URL in a .env file at the root of your project like so:

VITE_MAIL_ENDPOINT=https://your-aws-lambda-url/send/mail
Enter fullscreen mode Exit fullscreen mode

Complete JSX Contact Form with Tailwind Styling

import { useState } from "react";

const Contact = () => {
  const [formData, setFormData] = useState({
    name: "",
    email: "",
    subject: "",
    message: "",
  });
  const [isMailSuccess, setMailSuccess] = useState(false);
  const [isMailSending, setMailSending] = useState(false);

  const validateForm = () => {
    const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/;
    if (
      !formData.name ||
      !formData.email ||
      !formData.subject ||
      !formData.message ||
      !emailRegex.test(formData.email)
    ) {
      return {
        message: "All fields are required and email must be valid.",
        status: false,
      };
    }
    return {
      message: "",
      status: true,
    };
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    setMailSending(true);

    const { status, message } = validateForm();

    if (!status) {
      setMailSending(false);
      alert(message);
      return;
    }

    try {
      const response = await fetch(import.meta.env.VITE_MAIL_ENDPOINT, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify(formData),
      });

      const data = await response.json();

      if (data.success) {
        setMailSuccess(true);
        setFormData({
          name: "",
          email: "",
          subject: "",
          message: "",
        });
        setMailSending(false);
        setTimeout(() => {
          setMailSuccess(false);
        }, 1000);
      }
    } catch (error) {
      setMailSending(false);
      setMailSuccess(false);
      alert("Something went wrong.");
    }
  };

  return (
    <div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-60 z-50">
      <div className="bg-zinc-900 rounded-xl p-6 w-80 md:w-full max-w-lg shadow-lg relative">
        <h2 className="text-2xl font-bold mb-4 text-white text-center">
          Get in Touch
        </h2>

        <form onSubmit={handleSubmit} className="space-y-4 text-white">
          <div>
            <label className="block text-sm font-medium text-white">
              Name
            </label>
            <input
              type="text"
              placeholder="Your name"
              value={formData.name}
              onChange={(e) =>
                setFormData({ ...formData, name: e.target.value })
              }
              className="mt-1 w-full border border-gray-300 rounded-lg px-4 py-2 placeholder-zinc-400 focus:outline-none"
            />
          </div>

          <div>
            <label className="block text-sm font-medium text-white">
              Email
            </label>
            <input
              type="email"
              placeholder="you@example.com"
              value={formData.email}
              onChange={(e) =>
                setFormData({ ...formData, email: e.target.value })
              }
              className="mt-1 w-full border border-gray-300 rounded-lg px-4 py-2 placeholder-zinc-400 focus:outline-none"
            />
          </div>

          <div>
            <label className="block text-sm font-medium text-white">
              Subject (10 words min)
            </label>
            <input
              type="text"
              placeholder="Subject"
              value={formData.subject}
              onChange={(e) =>
                setFormData({ ...formData, subject: e.target.value })
              }
              className="mt-1 w-full border border-gray-300 rounded-lg px-4 py-2 placeholder-zinc-400 focus:outline-none"
            />
          </div>

          <div>
            <label className="block text-sm font-medium text-white">
              Message (20 words min)
            </label>
            <textarea
              placeholder="Your message..."
              value={formData.message}
              onChange={(e) =>
                setFormData({ ...formData, message: e.target.value })
              }
              className="mt-1 w-full border border-gray-300 rounded-lg px-4 py-2 h-28 resize-none placeholder-zinc-400 focus:outline-none"
            ></textarea>
          </div>

          {isMailSending ? (
            <div className="flex items-center justify-center">
              <div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-gray-200"></div>
            </div>
          ) : (
            <button
              type="submit"
              className="w-full py-2 px-4 rounded-lg bg-cyan-500 hover:bg-cyan-600 transition text-white"
            >
              {isMailSuccess ? "Sent successfully" : "Send"}
            </button>
          )}
        </form>
      </div>
    </div>
  );
};

export default Contact;
Enter fullscreen mode Exit fullscreen mode

Contact form

In the above pic you can see how the form will look like but in you case it might not look like this because I have some parent css that are not mentioned here, so feel free to add some different style.

Test time

Now any one can send mail to you from you page, lets take a look how it look like

Input fields

After writing the inputs when user will click on send It will start a loader and as the message sent sucessfully it will show a message of _Sent successfully _ and you will receive mail in your inbox.

There is a high probability that your mail move to Spam folder, if then you need to Report not spam so that you can get notification.

Receved mail

In the above Image you can see the mail I receive in my Inbox

Live Demo

Visit my Portfolio for live demo.

Conclusion

Building a contact form that sends real-time emails using React.js, AWS Lambda, and Resend may sound intimidating at first, but as we've seen, it’s surprisingly smooth when broken down step by step. With Zod ensuring your data is valid, Serverless Framework handling your AWS deployment, and Resend taking care of email delivery, you now have a powerful, scalable solution that’s perfect for portfolios, SaaS products, and production-ready sites.

If you like this blog, try to implement it in you own website so that you can reach out to your end-users.

If you'd like to give me any feedback, reach out to me at my X handle or visit my Website and if you want to use Vercel Functions insted of AWS lambda just leave a comment below I'll surely give a guide on Vercel Functions.

:-)

Comments 0 total

    Add comment