Reza Zahedi

Articles

How to build a contact form in Astro, Vercel and SendGrid

Having contact form in your website make it easier for users to interact with you and also look more professional, but have downsides, It opens a door for spammer/hackers to give you headache too.

Create Astro Project

To start building our contact form first we should have our Astro project to be ready:

npm create astro@latest
npm run dev

Now that your Astro project is ready and running locally, create your first page and add the form with necessary input fields. as Astro is a file-based routing, by creating new file in src/pages/ you will have new endpoint in your site.

New file: src/pages/contact.astro

---
// Frontmatter section
// Component scripts goes here.
---
<html lang="en">
  <head>
    <title>Contact Me</title>
  </head>
  <body>
    <h1>Say Hi to me!</h1>
    <form method="POST">
      <div>
        <input type="text" name="name" placeholder="Name" />
      </div>
      <div>
        <input type="email" name="email" placeholder="Email" />
      </div>
      <div>
        <textarea name="message" placeholder="Message"></textarea>
      </div>
      <div>
        <button type="submit">Send</button>
      </div>
    </form>
  </body>
</html>

Astro uses a code fence (---) to identify the component script in your Astro component. If you’ve ever written Markdown before, you may already be familiar with a similar concept called frontmatter. Astro’s idea of a component script was directly inspired by this concept.

Although there are security aspects to consider when creating forms that I’m not covering here but it’s an important topic to keep in mind.

Setup SendGrid

SendGrid is a popular email delivery platform that provides a wide range of email marketing and transactional email services. SendGrid is a reliable and flexible email delivery platform that we are going to use it to send emails.

First, create new SendGrid account or use an existing one, then create your API key here. The SendGrid’s API keys always start with “SG.”!

Sendgrid requires you to create a Sender Identity to protect against spam and malicious mails. To do so, go to the Sendgrid Identity page and click on Create New Sender to create a sender identity.

You’ll be rqeuired to fill out a detailed form. Just complete the form and hit submit. Finally, just verify your email address and you’re done.

Once you’ve retrieved the API keys, add it to your .env file in your local environment:

SENDGRID_API_KEY = "SendGrid API key goes here"

Note: If you setup .gitignore in your project to ignore .env files, so do not forget to add your env variable SENDGRID_API_KEY under your project in Vercel, Settings > Environment Variables.

Then we need to install SendGrid’s package:

npm i --save @sendgrid/mail

Create the Vercel serverless function

The SendGrid does not allow browser-based API calls, because of security reasons, also if you do, you will reveal your API key to the public. I did and received a CORS error from SendGrid API, So we should have a server-side function and do our SendGrid API call on server, We need to create a Nodejs function.

npx astro add vercel

This command will install @astrojs/vercel astro@latest and change your project’s configuration in /astro.config.mjs file by importing vercel serverless, adding output: ‘server’ amd vercel() as adapter.

import { defineConfig } from 'astro/config';

import vercel from "@astrojs/vercel/serverless";

// https://astro.build/config
export default defineConfig({
   output: 'server',
   adapter: vercel()
});

Now we can receive data from our contact form on server-side, validate, handling errors and do the SendGrid API call on server. You can read more about Server-Side Rendering (SSR) on Vercel documentation.

---
import sendGrid from '@sendgrid/mail';
sendGrid.setApiKey( import.meta.env.SENDGRID_API_KEY );

if (Astro.request.method === 'POST') {
  try{
    // Getting posted data from our contact form
    const data = await Astro.request.formData();
    const name = data.get('name');
    const email = data.get('email');
    const message = data.get('message');

    // TODO: Do not forget to do validation and
    // error handling over received data from your contact form.

    // Sending email
    const msg = {
      to: 'test@example.com', // Change to your recipient
      from: 'text@example.com', // Change to your verified sender
      replyTo: {email:email, name:name},
      subject: `Contact form submission from ${name}`,
      text: message,
    }
    await sendGrid.send(msg).then(() => {
      console.log('Email sent')
    }).catch((error) => {
      console.error(error)
    });

  } catch (error) {
    console.error(error);
  }
}
---
<html lang="en">
  <head>
    <title>Contact Me</title>
  </head>
  <body>
    <h1>Say Hi to me!</h1>
    <form method="POST">
      <div>
        <input type="text" name="name" placeholder="Name" />
      </div>
      <div>
        <input type="email" name="email" placeholder="Email" />
      </div>
      <div>
        <textarea name="message" placeholder="Message"></textarea>
      </div>
      <div>
        <button type="submit">Send</button>
      </div>
    </form>
  </body>
</html>

It’s an old-fashion to handle the forms like this by reloading entire page for posting data to servers, but it’s just the base codes, So next step is to make http request (send and receive form’s data) by browser’s fetch API.