TL;DR: Web apps can't reliably send email directly — they use transactional email services (Resend, SendGrid, Postmark, Amazon SES) that handle deliverability for you. AI generates the code to call these services via their APIs. The hard part isn't the code — it's configuring SPF and DKIM DNS records so your emails don't land in spam. Start with Resend: it has the best developer experience and AI handles its setup well.

Why AI Coders Need This

Email is one of the first "real infrastructure" problems you hit when building a web app. It seems simple — just send a message, right? But email has been around since the 1970s, and the internet has built an enormous amount of anti-spam machinery on top of it. That machinery doesn't care that you're a solo founder who just wants to send a welcome email.

When you ask AI to add email to your app, it will confidently scaffold something that works — in development. Then you deploy to production, and your emails either never arrive or land directly in spam. The AI generated correct code; it just didn't tell you about the DNS records you also need to configure, and it had no way to do that for you.

This guide gives you the mental model so you know:

  • Why a dedicated email service is necessary (not optional)
  • What the main services are and how to choose one
  • What AI generates and how to read it
  • What SPF and DKIM actually mean in plain English
  • Why emails go to spam and how to fix it

Why You Can't Just Use Gmail

The first thing most people try is using their Gmail account as the SMTP server. Nodemailer even makes this easy with a "Gmail" transport option. This works for a few test emails and then breaks in one of several painful ways:

Rate limits

Gmail allows roughly 500 emails per day through the standard SMTP interface, and far less through the API for free accounts. Your app might blow through that sending welcome emails during a product launch.

Google blocks it

Google actively detects and blocks applications that use Gmail accounts for automated sending. You'll get authentication errors, "Less secure app" warnings, or your account suspended. Google doesn't want Gmail used as an app email server — that's not what it's for.

Deliverability is terrible

Gmail's IP addresses have a mixed sending reputation because millions of people use them for personal email. When your app sends from a Gmail address, other mail servers apply extra scrutiny. The result: spam folder.

Your domain, not Google's

Professional apps send from addresses like hello@yourdomain.com or noreply@yourdomain.com — not yourthing@gmail.com. To send from your own domain reliably, you need infrastructure tied to that domain.

The solution is a dedicated transactional email service. These companies have built the infrastructure, IP reputation, and deliverability tooling that make email work at scale — and they expose it all through a simple API.

Transactional vs. Marketing Email: The Key Distinction

Before looking at services, understand this distinction — it affects which tool you use:

  • Transactional email — triggered by a user action. Password reset, email verification, order confirmation, welcome email, invoice, two-factor auth code. One email to one person because they did something. These are expected, often required, and almost always wanted.
  • Marketing email — sent in bulk to a list. Newsletters, product announcements, promotional offers, re-engagement campaigns. One send to many people. Subject to stricter legal requirements (CAN-SPAM, GDPR) and requiring explicit opt-in.

Most vibe coder apps only need transactional email. This guide focuses on that. If you need newsletters, look at Mailchimp, ConvertKit, or Loops. Sending marketing email through a transactional service (or vice versa) is a fast way to get your account flagged.

The Transactional Email Landscape

Here are the main options you'll encounter when AI suggests or generates email code:

Resend

The newest player and currently the developer favourite. Built specifically for developers, with a clean API, official React Email integration (you write email templates as React components), and excellent documentation. Free tier: 3,000 emails/month and 100/day. AI coding tools handle Resend very well because its API is simple and its SDK is modern TypeScript-first.

Best for: New projects, Next.js/React apps, developers who want a great DX and don't want to wrestle with configuration.

See also: What Is Resend?

SendGrid (Twilio)

The incumbent. Been around since 2009, now owned by Twilio. Handles massive volume and has every feature you could want — including marketing email, analytics, and A/B testing. The API and dashboard are more complex, and the free tier (100 emails/day) is less generous. AI generates SendGrid code reliably because it has enormous training data coverage.

Best for: Teams that need enterprise features, compliance tooling, or are already in the Twilio ecosystem.

Postmark

The deliverability specialist. Postmark has historically had the best inbox placement rates in the industry, partly because they are strict about only allowing transactional email (no marketing). Their "message streams" feature keeps transactional and broadcast email completely separate. Slightly more expensive, but developers who care deeply about every email arriving use Postmark.

Best for: Apps where email reliability is critical — SaaS products where a missed password reset means a lost customer.

Amazon SES (Simple Email Service)

AWS's email service. The cheapest option at scale ($0.10 per 1,000 emails). No monthly base cost. But the setup is significantly more involved — IAM permissions, verified identities, moving out of the sandbox (requires an AWS support request), and configuration that isn't beginner-friendly. AI can generate SES code, but the infrastructure setup requires more manual work than the alternatives.

Best for: High-volume apps already on AWS where cost matters and you have someone comfortable with AWS configuration.

Nodemailer

Nodemailer is not a service — it's a Node.js library that speaks SMTP. It connects to any SMTP server, including those provided by Resend, SendGrid, Postmark, or SES. When AI generates Nodemailer code, it's generating the code that talks to one of the above services via the older SMTP protocol instead of a modern REST API. Most modern services also offer direct SDK/API access, which is simpler. If AI gives you Nodemailer code, it still needs a real email service behind it.

Service Free Tier DX Best For
Resend 3,000/mo, 100/day Excellent New projects, React apps
SendGrid 100/day Good Enterprise, high volume
Postmark 100/mo trial Good Deliverability-critical apps
Amazon SES 3,000/mo (EC2) Complex High volume, AWS shops

What AI Generates

Here is what a typical AI-generated email setup looks like using Resend. This is a password reset email triggered from an API route in Next.js.

Prompt I Would Type

Add a password reset flow to my Next.js app. When the user submits their
email, send them a password reset link using Resend. Use React Email for
the template. Store the reset token in the database with a 1-hour expiry.

AI will generate several files. Here's the core email-sending logic — read the comments to understand what each piece does:

// lib/email.ts — your email sending helper

import { Resend } from 'resend';                   // official Resend SDK
import { PasswordResetEmail } from '@/emails/PasswordResetEmail'; // React Email template

// Resend client — API key comes from environment variable, never hardcoded
const resend = new Resend(process.env.RESEND_API_KEY);

export async function sendPasswordResetEmail(
  toEmail: string,
  resetToken: string
) {
  const resetUrl = `${process.env.NEXT_PUBLIC_APP_URL}/reset-password?token=${resetToken}`;

  const { data, error } = await resend.emails.send({
    from: 'Your App <noreply@yourdomain.com>',  // must be a verified domain in Resend
    to: toEmail,
    subject: 'Reset your password',
    react: PasswordResetEmail({ resetUrl }),        // React component renders to HTML
  });

  if (error) {
    // Log the error — common causes: unverified domain, invalid API key
    console.error('Email send failed:', error);
    throw new Error('Failed to send password reset email');
  }

  return data;
}
// emails/PasswordResetEmail.tsx — React Email template
// This is a React component that renders to email-safe HTML

import {
  Body, Button, Container, Head, Html,
  Preview, Section, Text
} from '@react-email/components';

interface Props {
  resetUrl: string;
}

export function PasswordResetEmail({ resetUrl }: Props) {
  return (
    <Html>
      <Head />
      {/* Preview text shown in inbox before opening */}
      <Preview>Reset your password — link expires in 1 hour</Preview>
      <Body style={{ fontFamily: 'Arial, sans-serif', backgroundColor: '#f4f4f4' }}>
        <Container style={{ maxWidth: '600px', margin: '0 auto', padding: '24px' }}>
          <Text>Someone requested a password reset for your account.</Text>
          <Text>Click the button below. This link expires in 1 hour.</Text>
          <Section>
            <Button
              href={resetUrl}
              style={{ backgroundColor: '#0070f3', color: '#fff', padding: '12px 24px' }}
            >
              Reset Password
            </Button>
          </Section>
          <Text style={{ color: '#666', fontSize: '14px' }}>
            If you didn't request this, ignore this email.
          </Text>
        </Container>
      </Body>
    </Html>
  );
}
// app/api/auth/forgot-password/route.ts — the API route that triggers the email

import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { sendPasswordResetEmail } from '@/lib/email';
import crypto from 'crypto';

export async function POST(request: Request) {
  const { email } = await request.json();

  // Find user — return success even if email not found (don't reveal who's registered)
  const user = await db.user.findUnique({ where: { email } });
  if (!user) {
    return NextResponse.json({ success: true }); // deliberate: don't expose user existence
  }

  // Generate a secure random token — NOT a predictable value like a user ID
  const token = crypto.randomBytes(32).toString('hex');
  const expiresAt = new Date(Date.now() + 60 * 60 * 1000); // 1 hour from now

  // Store token in database
  await db.passwordResetToken.create({
    data: { userId: user.id, token, expiresAt }
  });

  // Send the email — this calls Resend's API
  await sendPasswordResetEmail(email, token);

  return NextResponse.json({ success: true });
}

The key things to notice: the API key lives in an environment variable (see What Is an Environment Variable?), the from address uses your verified domain, and the React Email template is a separate component. This is clean, idiomatic code — AI handles Resend setup well.

Understanding DNS Records: SPF and DKIM in Plain English

This is where most people get stuck. The code works fine. But emails go to spam — or don't arrive at all — because the DNS records aren't set up. DNS records are configuration files for your domain that live with your domain registrar (GoDaddy, Namecheap, Cloudflare, etc.). For background on DNS itself, see What Is DNS?

SPF (Sender Policy Framework)

SPF is a DNS record that answers one question: "Which mail servers are allowed to send email claiming to be from this domain?"

Without SPF, any server on the internet could send email with a from address of hello@yourdomain.com. Mail servers receiving that email have no way to know if it's legitimate. SPF is your domain's declaration: "Only these specific mail servers are authorized to send on my behalf."

It looks like this in your DNS settings:

Type: TXT
Name: @  (or yourdomain.com)
Value: v=spf1 include:amazonses.com include:sendgrid.net ~all

# v=spf1 — this is an SPF record
# include:amazonses.com — SES servers are authorized
# include:sendgrid.net — SendGrid servers are authorized
# ~all — treat all other senders as suspicious (soft fail)

Your email service will give you the exact record to add. You copy it into your DNS settings and wait up to 48 hours for it to propagate. Resend's dashboard walks you through this step by step.

DKIM (DomainKeys Identified Mail)

DKIM goes one step further. It adds a cryptographic signature to every email your app sends. The receiving mail server can verify that signature using a public key stored in your DNS, confirming two things: the email genuinely came from your domain, and it wasn't modified in transit.

Think of it like a wax seal on a letter. The seal (signature) can only be created with your private key. Anyone can verify the seal using your public key (published in DNS). If the seal is broken or missing, something went wrong.

Your email service generates both the private key (which they hold) and the public key (which you add to DNS). It looks like this:

Type: TXT
Name: resend._domainkey.yourdomain.com
Value: p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQ... (long public key string)

You don't need to understand the cryptography. You just need to copy the record your email service provides into your DNS settings.

DMARC (Bonus)

DMARC builds on top of SPF and DKIM. It tells receiving servers what to do when an email fails SPF or DKIM checks — reject it, quarantine it (spam folder), or just report it. You don't need DMARC to get started, but adding a basic DMARC record is a best practice once SPF and DKIM are working:

Type: TXT
Name: _dmarc.yourdomain.com
Value: v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com

# p=none — monitor only, don't reject anything yet
# rua — where to send reports (optional but useful)

The Spam Problem

You've set up the service, written the code, added the DNS records. Your emails still go to spam. Here's why that happens and how to fix it:

DNS propagation hasn't finished

DNS changes can take up to 48 hours to fully propagate across the internet. Your email service will usually show a green checkmark in their dashboard when it detects your records are live. Don't test deliverability until you see that checkmark.

New domain, zero reputation

Mail servers track the sending reputation of domains over time. A brand-new domain sending 500 emails on day one looks suspicious. This is called "warming up" your domain — you need to start small, send to engaged recipients, and build reputation gradually. Most email services have documentation on domain warm-up schedules.

Sending to bad addresses

High bounce rates (sending to emails that don't exist) tank your sender reputation fast. If you have an old list of email addresses, validate them before importing. Most email services offer bounce handling that automatically stops sending to addresses that hard-bounce.

Spam trigger content

Spam filters analyze content. Subject lines with ALL CAPS, excessive exclamation marks, words like "FREE!!!", "URGENT", or "Winner" trigger spam classifiers. Keep your subject lines natural and your content genuine.

Missing unsubscribe link

For any email that isn't purely transactional (user specifically triggered it), include an unsubscribe link. Gmail and other providers have started automatically marking emails as spam if users mark enough of them, and one-click unsubscribe is now a Google/Yahoo requirement for bulk senders.

Wrong "from" address

The from address must match your verified domain. If you verified yourdomain.com but send from hello@otherdomain.com, the email will fail authentication and go to spam or get rejected.

Watch Out

Never test email deliverability by sending to yourself and checking your own spam folder. Use a tool like mail-tester.com or your email service's built-in deliverability testing to get an objective spam score before going live.

What AI Gets Wrong About Email

It generates code but can't configure DNS

AI will produce working code in minutes. It cannot log into your Namecheap account and add DNS records for you. The most common reason email doesn't work in production is missing SPF/DKIM records, not bad code. Always check your email service dashboard for the DNS setup checklist.

It sometimes hardcodes the API key

Less common with newer AI tools, but still happens. If you see something like const resend = new Resend('re_abc123...') with an actual key value instead of process.env.RESEND_API_KEY, that's a problem. API keys in source code get committed to git, pushed to GitHub, and scraped by bots within minutes. See What Is API Key Management? for the full picture.

It ignores error handling

AI-generated email code often assumes the send will succeed. In production, email APIs can fail: rate limits, network errors, invalid addresses, unverified domains. Wrap your email sends in try/catch and decide what your app should do when sending fails. For a password reset, you'd probably want to tell the user to try again rather than silently swallowing the error.

It picks the wrong service for your stack

AI might generate Nodemailer + SMTP code when you'd be better served by a direct REST API, or suggest Amazon SES when you're not on AWS and don't have IAM experience. If the generated code seems overly complex, ask: "Rewrite this using Resend's SDK instead" — you'll get something much simpler.

It forgets environment variable setup

AI generates the code that uses process.env.RESEND_API_KEY but often doesn't remind you that you need to add this variable to your production environment (Vercel dashboard, Railway variables, etc.), not just your local .env file. See What Is an Environment Variable?

It doesn't warn you about the sandbox

Amazon SES starts all new accounts in "sandbox mode" where you can only send to verified email addresses. You have to submit a support request to AWS to get out of sandbox mode. AI doesn't mention this. If you ask AI to set up SES and emails only work when sending to your own verified address, this is why.

What to Learn Next

Email sending connects to several other foundational pieces of your app:

Frequently Asked Questions

Technically yes, but you shouldn't. Gmail blocks apps that use it as a bulk sender, has strict rate limits (around 500 emails/day), and emails often land in spam because Gmail's IP addresses aren't configured for app-to-user sending. Use a transactional email service instead.

Transactional emails are triggered by user actions: password resets, welcome emails, order receipts, verification codes. Marketing emails are bulk sends: newsletters, promotions, announcements. They have different deliverability needs, different legal requirements (CAN-SPAM, GDPR), and should generally be sent through different systems.

SPF (Sender Policy Framework) is a DNS record that says which servers are allowed to send email for your domain. DKIM (DomainKeys Identified Mail) adds a cryptographic signature to your emails so recipients can verify they weren't tampered with. Yes, you need both. Without them, your emails will go to spam or get rejected outright.

Start with Resend. It has the best developer experience, a generous free tier (3,000 emails/month), works great with Next.js and React Email, and the API is simple enough that AI can generate working code for it with minimal prompting.

Common causes: SPF or DKIM records not configured yet (DNS changes take up to 48 hours), sending from a brand-new domain with no reputation, using spammy subject lines, sending to bad email addresses, or missing an unsubscribe link for marketing emails. Check your email service's dashboard — most show a deliverability health score.