Sending Emails with Astro v3 and Nodemailer

Writed by Moncef in 05/07/2023.

This post focuses on how to send emails in an Astro v3 project using TypeScript and Nodemailer.

Setup

Initialize Astro project and install dependencies

Terminal

pnpm create astro@latest && cd project-name && pnpm i

Install Packages

Install Nodemailer for sending emails and Zod for validation

Terminal

pnpm i nodemailer zod

Environment Variables

Create a .env file and add your credentials see this to learn how to get it :

.env

MY_EMAIL="yourappemail@gmail.com" 
MY_PASSWORD="yourapppassword"

Email Utility Function

Create a file src/lib/utils.ts and add:

src/lib/utils.ts

import * as nodemailer from "nodemailer";
const MY_EMAIL = import.meta.env.MY_EMAIL;
const MY_PASSWORD = import.meta.env.MY_PASSWORD;

export const sendEmail = async ({name,email,message}: {
  name: string;
  email: string;
  message: string;
}): Promise<void> => {
  const transporter = nodemailer.createTransport({
    service: "gmail",
    host: "smtp.gmail.com",
    port: 587,
    secure: false,
    auth: {
      user: MY_EMAIL,
      pass: MY_PASSWORD,
    },
  });
    const mailOptions = {
    from: MY_EMAIL,
    to: "youEmail@gmail.com", # wirte you email address here
    subject: ` Contact form submission from ${name}`,
    text: Email:` ${email} Message: ${message}`,
  };

await transporter.sendMail(mailOptions);
};

Building the Validation Function

Let's start by defining our validation function using Zod. Create a new file under src/lib/utils and add the following code:

src/lib/utils.ts

import { z } from "zod";

export const formDataSchema = z.object({
  name: z
    .string()
    .min(3, { message: "Name must be at least 3 characters long" }),
  email: z.string().email({ message: "Invalid email address" }),
  message: z
    .string()
    .min(8, { message: "Message must be at least 8 characters long" }),
});

export type FormData = z.infer<typeof formDataSchema>;

Form UI

Update src/pages/index.astro to handle the form submission we can start by building the ui:

src/pages/index.astro

<Layout description="email with astro demo" title="email with astro">
  <main class="text-primary-50 flex-1 bg-primary-500 w-full h-screen flex justify-center items-center rounded-xl p-6">
    <form method="POST" class="flex flex-col w-full gap-4">
      <input
        type="text"
        name="name"
        class="w-full rounded-lg h-12 px-2 bg-primary-600 placeholder:text-light-100"
        placeholder="Name"
      />
      {errors.name && <p>{errors.name}</p>}
      <input
        type="email"
        name="email"
        class="w-full rounded-lg h-12 px-2 bg-primary-600 placeholder:text-light-100"
        placeholder="email"
        required
      />
      {errors.email && <p>{errors.email}</p>}
      <label>
        <textarea
          name="message"
          required
          minlength="6"
          class="w-full rounded-lg h-36 resize-y px-2 bg-primary-600 placeholder:text-light-100"
          name="message"
          placeholder="Message"
        />
      </label>
      {errors.message && <p>{errors.message}</p>}
      <button class="h-12 w-full rounded-xl bg-light-50 dark:bg-dark-900 dark:text-light-50 text-dark-900">
        Register
      </button>
    </form>
  </main>
</Layout>

Building the contact form logic

After that, we can start building the contact form logic. Place the following code at the top of src/pages/index.astro:

src/pages/index.astro

---
import { z } from "astro:content";
import { sendEmail, formDataSchema } from "../lib/utils";
import Layout from "../layouts/Layout.astro";

let errors = { name: "", email: "", message: "" };

if (Astro.request.method === "POST") {
try {
const data = await Astro.request.formData();
const name = data.get("name");
const email = data.get("email");
const message = data.get("message");

    const formData = formDataSchema.parse({
      name,
      email,
      message,
    });

    await sendEmail(formData).catch((error) => {
      console.error(`An error occurred: ${error}`);
    });

} catch (error) {
if (error instanceof z.ZodError) {
console.error(`Validation error: ${error}`);
} else {
console.error(`An error occurred: ${error}`);
 }}
}
---

The last steps

While nodemailer can only run in a Node.js runtime, Astro is a static site generator (SSG) by default. To switch Astro to server-side rendering (SSR) mode, you can run the following command in a Node.js environment:

Terminal

pnpm astro add node

However, if you wish to deploy it serverlessly on Vercel, use this command instead:

Terminal

pnpm astro add vercel

For more information on this subject, check out the Astro SSR guide.

That's it! You can now easily send emails directly from your Astro project using Nodemailer.

Happy coding!

- Like it ? cost 0$ just share it :)
- About Moncef Aissaoui:

Made selance, rawdati, dushdo. I'm currently building an open-source project for Algerian developers and startup founders named wolfroad and cheapres. I love reading and writing about business and programming.

Get exclusive content