Stop Paying for Resend: BetterAuth + Nodemailer Integration Guide
nextjsbetterauthnodemailertypescriptauth

Stop Paying for Resend: BetterAuth + Nodemailer Integration Guide

How to replace Resend with Nodemailer in your Next.js application for free, unlimited email verification and password resets.

# Why I Ditched Resend for Nodemailer in BetterNotes

Let's be real: Resend is amazing until you hit that paywall. When you're building a project like BetterNotes, you want a seamless auth flow without the "onboarding@resend.dev" limitation or the monthly subscription just to send verification emails to your users.

If you are using BetterAuth with Next.js 15, switching to Nodemailer is a no-brainer. It gives you full control and lets you use your own SMTP server (like Gmail) for free.

## The Problem with Resend Free Tier

  • Limited Delivery: You are restricted to sending emails primarily to your own registered address.

  • Forced Branding: You have to deal with generic "onboarding" addresses instead of your own brand identity.

  • Scalability: For indie hackers, the jump from free to paid tiers can be a barrier to entry when managing a growing product from scratch.

## The Solution: A Custom SMTP Transporter

By creating a simple utility using Nodemailer, we can intercept BetterAuth's email events and route them through our own mailer. This setup is perfect for modern frontend apps that prioritize performance and cost-efficiency.

### 1. The Environment Setup (Gmail)

Before coding, you need to configure your credentials. You cannot use your regular Gmail password for security reasons; you must use an App Password.

  • EMAIL_USER: This is simply your full Gmail address (e.g., yourname@gmail.com).
  • EMAIL_PASS: This is the 16-character App Password generated by Google.

#### How to get your App Password:

  1. Go to your Google Account settings.
  2. Navigate to Security.
  3. Enable 2-Step Verification (this is mandatory).
  4. Search for "App Passwords" in the search bar.
  5. Give it a name (like "BetterNotes") and copy the code provided.

### 2. Setup the Email Library

Create a utility file to handle the SMTP transport. This keeps your code clean and reusable across your application.

// lib/email.ts
import nodemailer from 'nodemailer';
 
const transporter = nodemailer.createTransport({
  service: 'gmail',
  auth: {
    user: process.env.EMAIL_USER,
    pass: process.env.EMAIL_PASS, // The 16-char App Password
  },
});
 
export async function sendEmail({
  to,
  subject,
  html,
}: {
  to: string;
  subject: string;
  html: string;
}) {
  await transporter.sendMail({
    from: `"BetterNotes" <${process.env.EMAIL_USER}>`,
    to,
    subject,
    html,
  });
}

### 3. Connect Nodemailer to BetterAuth

Now, we need to tell BetterAuth to stop looking for Resend and start using our new sendEmail function. We use @react-email/components to render our templates into raw HTML strings.

// lib/auth.ts
import { betterAuth } from 'better-auth';
import { render } from '@react-email/components';
import { sendEmail } from '@/lib/email';
import VerificationEmail from '@/components/emails/VerificationEmail';
import ResetPasswordEmail from '@/components/emails/ResetPasswordEmail';
 
export const auth = betterAuth({
  emailVerification: {
    sendVerificationEmail: async ({ user, url }) => {
      const emailHTML = await render(
        VerificationEmail({
          name: user.name,
          verificationUrl: url,
        }),
      );
 
      await sendEmail({
        to: user.email,
        subject: 'Verify your email address',
        html: emailHTML,
      });
    },
    sendOnSignUp: true,
  },
  emailAndPassword: {
    requireEmailVerification: true,
    enabled: true,
    sendResetPassword: async ({ user, url }) => {
      const emailHTML = await render(
        ResetPasswordEmail({
          name: user.name,
          resetPasswordUrl: url,
        }),
      );
 
      await sendEmail({
        to: user.email,
        subject: 'Reset your password',
        html: emailHTML,
      });
    },
  },
  // ... rest of your config (drizzleAdapter, etc.)
});

## Key Benefits of this Stack

  • Next.js 15 + TypeScript: Fully type-safe implementation ensures your user objects and URLs are never undefined during the build process.

  • BetterAuth Hooks: By using the sendVerificationEmail and sendResetPassword hooks, you bypass default providers entirely.

  • Developer Experience (DX): You get the benefit of autocompletion and refactoring confidence that comes with a robust TypeScript setup.

## Real Use Cases

  • Verification: Ensure every user in your database is real before they access their data.

  • Security: Centralizing authorization logic where data is fetched to prevent accidental leaks.

  • Personal Branding: Sending from your own domain or professional Gmail without third-party tags.

## Conclusion

Switching from Resend to Nodemailer took me about 10 minutes, and it saved me from future subscription headaches. If you're building a production app but want to keep overhead low, this is the way to go.

Check out the live project at better-notes-og.vercel.app or dive into the code on GitHub.

Next Steps: Want to see how I used TypeScript with Drizzle or managed a side project from zero to deployment? Let me know on LinkedIn!

Related Posts

I Built an AI That Turns GitHub Repos Into Startup Pitches

I Built an AI That Turns GitHub Repos Into Startup Pitches

How I used Gemini, ElevenLabs, and Lingo.dev to transform code into investable stories.

frontendnextjsai+1
Read More
calculating...

Design & Developed by Karan Singh

© 2026. All rights reserved