DEV Community

Emmanuel Xs
Emmanuel Xs

Posted on

How to Send Emails for Free in Next.js Using Gmail and Nodemailer

Want to add email functionality—like sending OTPs (One-Time Passwords) for verification—to your Next.js app without spending a dime? Many email services require a custom domain, but if you’re working on a personal project with no budget for a domain, don’t worry—I’ve got you covered! In this blog, I’ll show you how to use Gmail and Nodemailer to send emails for free in Next.js. There’s a catch, though: these emails might land in spam. I’ll explain why that happens, how to set it up anyway, and what you can do about it. Let’s dive in!


What You’ll Need to Get Started

Before we dive in, make sure you have:

  • A basic understanding of JavaScript, React, Next.js, or Node.js.
  • A Gmail account. (I recommend creating a new one for this project to keep things separate—sign up at mail.google.com if you don’t have one)

Why Emails Might Land in Spam (And Why This Method Still Works)

Before we get to the setup, let’s address the elephant in the room: emails sent through Gmail and Nodemailer often end up in spam folders. Here’s why:

  • No Custom Domain: Gmail’s @gmail.com isn’t yours to authenticate with SPF or DKIM records, which email providers use to verify senders. Without these, your emails look less trustworthy.
  • SMTP Limits: Gmail’s SMTP setup isn’t optimized for bulk sending, so spam filters might flag it.
  • Reputation: Free Gmail accounts don’t have the sender reputation of paid services like SendGrid.

Does this make it useless? Not at all! For personal projects, testing, or small-scale use (like sending OTPs to yourself or a few users), it’s still practical. Plus, it’s free and doesn’t need a domain. I’ll share tips later to improve delivery odds.


Step 1: Configure Your Gmail Account

To use Gmail with Nodemailer, you need to allow external apps (like Nodemailer) to send emails through your account. Here’s how, based on your security settings:

If You Don’t Use 2-Step Verification

  1. Go to your Google Account settings.
  2. Enable “Allow less secure apps”.

If You Use 2-Step Verification (Recommended for Security)

  1. Go to your App Passwords settings in your Google Account. app passowrd image
  2. Select “Mail” as the app and “Other” as the device, then name it (e.g., “Nodemailer” or your app’s name). name the password
  3. Click “Generate” and save the 16-character app password—you’ll need this later. The app password generated lets you use nodemailer without giving out your main Gmail password.

Tips:

  • I prefer using an app password with 2-Step Verification because it’s more secure than enabling “less secure apps” (which is riskier and only needed if your email server is outdated or 2FA isn’t enabled).
  • If you can’t find these settings, search “app password” or “less secure apps” in your Google Account.

Step 2: Install Nodemailer in Your Next.js Project

Open a terminal in your Next.js project and run:

npm install nodemailer
Enter fullscreen mode Exit fullscreen mode

Nodemailer is a fantastic Node.js library I love for its simplicity and flexibility in sending emails—no domain required!


Step 3: Store Your Gmail Credentials Securely

To keep your Gmail username and password (or app password) safe, use environment variables. Create a .env.local file in your project’s root:

GMAIL_USERNAME=your-email@gmail.com
GMAIL_PASSWORD=your-password-or-app-password
Enter fullscreen mode Exit fullscreen mode
  • Use your Gmail password if you enabled “less secure apps.”
  • Use the app password if you went with 2-Step Verification (my recommended approach).

Note: Add .env.local to your .gitignore file to keep it private—never share these credentials!


Step 4: Set Up a Server Action or API Routes to Send Emails

Using Server Actions (Preferred)

Create a file at app/actions/sendEmail.js (for the App Router):

'use server'; // Marks this as a Server Action

import nodemailer from 'nodemailer';

export async function sendEmail(formData) {
  const name = formData.get('name');
  const email = formData.get('email');
  const message = formData.get('message');

  const transporter = nodemailer.createTransport({
    host: 'smtp.gmail.com',
    port: 587,
    auth: {
      user: process.env.GMAIL_USERNAME,
      pass: process.env.GMAIL_PASSWORD,
    },
  });

  try {
    await transporter.sendMail({
      from: process.env.GMAIL_USERNAME,
      to: 'recipient@example.com', // Replace with your desired recipient
      subject: `New message from ${name}`,
      text: message,
      replyTo: email,
    });
    return { success: true, message: 'Email sent successfully!' };
  } catch (error) {
    console.error(error);
    return { success: false, message: 'Failed to send email.' };
  }
}
Enter fullscreen mode Exit fullscreen mode

Using API Routes (Alternative)

  • If you’re using the API routes, you can create app/api/sendEmail.js instead:
import nodemailer from 'nodemailer';

export default async function handler(req, res) {
  if (req.method === 'POST') {
    const { name, email, message } = req.body;

    const transporter = nodemailer.createTransport({
      host: 'smtp.gmail.com',
      port: 587,
      auth: {
        user: process.env.GMAIL_USERNAME,
        pass: process.env.GMAIL_PASSWORD,
      },
    });

    try {
      await transporter.sendMail({
        from: process.env.GMAIL_USERNAME,
        to: 'recipient@example.com',
        subject: `New message from ${name}`,
        text: message,
        replyTo: email,
      });
      res.status(200).json({ message: 'Email sent successfully!' });
    } catch (error) {
      console.error(error);
      res.status(500).json({ message: 'Failed to send email.' });
    }
  } else {
    res.status(405).json({ message: 'Only POST requests allowed.' });
  }
}
Enter fullscreen mode Exit fullscreen mode

I prefer Server Actions because they’re streamlined and don’t require separate endpoints, but API routes work just as well if you’re on an older setup or like that approach.


Step 5: Create a Form to Trigger Emails with Server Actions

Let’s build a contact form to send emails, tailored to either Server Actions or API Routes.

Using Server Actions

Create app/components/ContactForm.js

import { sendEmail } from '../actions/sendEmail';

export default function ContactForm() {
  const handleSubmit = async (formData) => {
    const result = await sendEmail(formData);
    if (result.success) {
      alert(result.message);
    } else {
      alert(result.message);
    }
  };

  return (
    <form action={handleSubmit}>
      <input type="text" name="name" placeholder="Your Name" required />
      <input type="email" name="email" placeholder="Your Email" required />
      <textarea name="message" placeholder="Your Message" required />
      <button type="submit">Send Email</button>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

Add this to a page, like app/page.js:

import ContactForm from './components/ContactForm';

export default function Home() {
  return (
    <div>
      <h1>Contact Us</h1>
      <ContactForm />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Using API Routes

If you used the API route approach, tweak the form to fetch the endpoint instead. Here’s how it’d look in components/ContactForm.js:

export default function ContactForm() {
  const handleSubmit = async (event) => {
    event.preventDefault();
    const formData = new FormData(event.target);
    const data = Object.fromEntries(formData);

    try {
      const response = await fetch('/api/sendEmail', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data),
      });
      if (response.ok) {
        alert('Email sent successfully!');
      } else {
        alert('Failed to send email.');
      }
    } catch (error) {
      console.error('Error:', error);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" name="name" placeholder="Your Name" required />
      <input type="email" name="email" placeholder="Your Email" required />
      <textarea name="message" placeholder="Your Message" required />
      <button type="submit">Send Email</button>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

Add it to pages/index.js as before.


Step 6: Test It Out

Let’s make sure it works! Whether you’re using Server Actions or API routes, the testing process is similar.

Using Server Actions

  • Run your Next.js app with npm run dev.
  • Visit http://localhost:3000.
  • Fill out the form and submit it.
  • Check the recipient’s inbox (or your Gmail “Sent” folder) to confirm the email arrived.

Using API Routes

The steps are the same—just ensure your API route is set up correctly at /api/sendEmail.

If the email doesn’t show up:

  • Double-check your .env.local file for typos in GMAIL_USERNAME or GMAIL_PASSWORD.
  • Verify your Gmail security settings (Step 1) are correct.
  • Look at your terminal for error messages from Nodemailer.
  • Check your terminal for Nodemailer error messages (e.g., authentication issues).

Improving Delivery: Tips to Avoid Spam

While this method might send emails to spam, here’s how to boost their chances of hitting the inbox:

  • Use a Clear Subject: Avoid spammy words like “free” or all caps (e.g., “Your OTP Code” vs. “FREE OTP!!!”).
  • Add Text Content: Include a friendly message (e.g., “Hi, here’s your OTP: 123456”).
  • Test with Your Own Email First: Gmail trusts emails sent to yourself more.
  • Limit Sending: Gmail caps at 100-500 emails/day—don’t push it, or you’ll get flagged.

For serious projects, a custom domain and paid service would help, but for free, this is as good as it gets!


Why I Recommend This Method

Despite the spam risk, I love Gmail and Nodemailer because:

  • It’s free—no need to pay for email services or domains.
  • It’s beginner-friendly—quick to set up with Next.js.
  • It’s practical—perfect for small projects without extra complexity.

Other methods might involve paid services or domain setup, but this approach gets the job done without any of that.


Wrapping Up

You’ve just learned how to send emails for free in Next.js with Gmail and Nodemailer—no domain needed! Sure, they might land in spam, but for personal projects or learning, it’s a solid starting point. Try it out, tweak it for your OTP needs, and let me know how it goes in the comments. Happy coding!

Top comments (0)