MONCEF

Sending Emails with Astro v3 and Nodemailer

this is an image
3 min read

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

Setup

# Initialize Astro project and install dependencies
pnpm create astro@latest && cd <project_name> && pnpm i

Install Packages

# Install Nodemailer for sending emails and Zod for validation
pnpm i nodemailer zod

Environment Variables

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

MY_EMAIL=yourappemail@gmail.com 
MY_PASSWORD=yourapppassword

Email Utility Function

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

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}\nMessage: ${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:

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:

<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:

---
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:

pnpm astro add node

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

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!