Next Starter Kit
Features

Email System

Multi-provider email system with customizable templates

Email System

The starter kit includes a flexible email system supporting multiple providers with a visual template editor.

Supported Providers

  • Nodemailer (SMTP) - Default, works with any SMTP server
  • Resend - Modern email API
  • SendGrid - Popular email service
  • Mailgun - Developer-friendly email API

Configuration

Choose Your Provider

Set in .env:

EMAIL_PROVIDER="nodemailer"  # or "resend", "sendgrid", "mailgun"

Nodemailer (SMTP)

Works with Gmail, Outlook, or any SMTP server:

EMAIL_PROVIDER="nodemailer"
EMAIL_HOST="smtp.gmail.com"
EMAIL_PORT="587"
EMAIL_USER="your-email@gmail.com"
EMAIL_PASS="your-app-password"
EMAIL_SECURE="false"
EMAIL_FROM="Your App <noreply@yourapp.com>"

Gmail Setup:

  1. Enable 2FA on your Google account
  2. Generate App Password: myaccount.google.com/apppasswords
  3. Use app password in EMAIL_PASS

Resend

Modern email API with great DX:

EMAIL_PROVIDER="resend"
RESEND_API_KEY="re_xxxxxxxxxxxx"
EMAIL_FROM="Your App <noreply@yourdomain.com>"

Get API key: resend.com/api-keys

SendGrid

EMAIL_PROVIDER="sendgrid"
SENDGRID_API_KEY="SG.xxxxxxxxxxxx"
EMAIL_FROM="Your App <noreply@yourdomain.com>"

Get API key: app.sendgrid.com/settings/api_keys

Mailgun

EMAIL_PROVIDER="mailgun"
MAILGUN_API_KEY="your-api-key"
MAILGUN_DOMAIN="mg.yourdomain.com"
MAILGUN_REGION="us"  # or "eu"
EMAIL_FROM="Your App <noreply@mg.yourdomain.com>"

Get credentials: app.mailgun.com

Email Templates

Template Types

The system includes pre-built templates:

  • welcome - Welcome new users
  • email-verification - Verify email address
  • password-reset - Reset password link
  • subscription-created - New subscription confirmation
  • subscription-canceled - Cancellation confirmation
  • invoice-paid - Payment receipt

Template Structure

{
  type: "welcome",
  subject: "Welcome to {{appName}}!",
  htmlContent: "<h1>Welcome, {{userName}}!</h1>...",
  textContent: "Welcome, {{userName}}!...",
  variables: ["appName", "userName", "loginUrl"],
  active: true
}

Template Editor

Navigate to /dashboard/admin/email-templates to:

  • View all templates
  • Edit HTML and text versions
  • Preview with sample data
  • Test send emails
  • Manage template variables

Monaco Editor

The template editor uses Monaco (VS Code editor):

  • Syntax highlighting for HTML
  • Auto-completion
  • Error checking
  • Split view (HTML + Preview)
  • Variable suggestions

Template Variables

Use {{variableName}} in templates:

<h1>Hello, {{userName}}!</h1>
<p>Welcome to {{appName}}!</p>
<a href="{{verificationUrl}}">Verify your email</a>

Common variables:

  • {{userName}} - User's name
  • {{userEmail}} - User's email
  • {{appName}} - Application name
  • {{appUrl}} - Application URL
  • {{verificationUrl}} - Email verification link
  • {{resetUrl}} - Password reset link
  • {{loginUrl}} - Sign in page URL

Sending Emails

Using the Email Service

import { sendEmail } from "@/lib/email";

// Send with template
await sendEmail({
  to: "user@example.com",
  template: "welcome",
  variables: {
    userName: "John Doe",
    appName: "My App",
    loginUrl: "https://myapp.com/sign-in",
  },
});

// Send custom email
await sendEmail({
  to: "user@example.com",
  subject: "Custom Email",
  html: "<h1>Hello!</h1><p>This is a custom email.</p>",
  text: "Hello! This is a custom email.",
});

Email Service Factory

The system automatically selects the configured provider:

// lib/email/index.ts
import { getEmailService } from "@/lib/email";

const emailService = getEmailService();

// Provider-agnostic sending
await emailService.send({
  to: ["user@example.com"],
  subject: "Test Email",
  html: "<p>Hello!</p>",
});

Transactional Emails

Emails are sent automatically for:

Authentication:

  • Email verification after sign-up
  • Password reset requests
  • Email change confirmation

Subscriptions (if enabled):

  • New subscription created
  • Subscription canceled
  • Payment failed
  • Invoice paid

Template Examples

Welcome Email

<!DOCTYPE html>
<html>
  <head>
    <style>
      body {
        font-family: Arial, sans-serif;
      }
      .container {
        max-width: 600px;
        margin: 0 auto;
      }
      .button {
        background: #0070f3;
        color: white;
        padding: 12px 24px;
        text-decoration: none;
        border-radius: 5px;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <h1>Welcome to {{appName}}!</h1>
      <p>Hi {{userName}},</p>
      <p>Thank you for joining our platform. We're excited to have you!</p>
      <p>
        <a href="{{loginUrl}}" class="button"> Get Started </a>
      </p>
      <p>Best regards,<br />The {{appName}} Team</p>
    </div>
  </body>
</html>

Password Reset

<!DOCTYPE html>
<html>
  <body>
    <div class="container">
      <h1>Reset Your Password</h1>
      <p>Hi {{userName}},</p>
      <p>
        You requested to reset your password. Click the button below to create a
        new password:
      </p>
      <p>
        <a href="{{resetUrl}}" class="button"> Reset Password </a>
      </p>
      <p>This link will expire in 1 hour.</p>
      <p>If you didn't request this, you can safely ignore this email.</p>
    </div>
  </body>
</html>

Advanced Features

Batch Sending

Send emails to multiple recipients:

const recipients = ["user1@example.com", "user2@example.com"];

for (const email of recipients) {
  await sendEmail({
    to: email,
    template: "newsletter",
    variables: {
      /* ... */
    },
  });

  // Rate limiting - wait between sends
  await new Promise((resolve) => setTimeout(resolve, 100));
}

Attachments

Send files with emails:

await sendEmail({
  to: "user@example.com",
  subject: "Invoice",
  html: "<p>Your invoice is attached.</p>",
  attachments: [
    {
      filename: "invoice.pdf",
      path: "/path/to/invoice.pdf",
    },
  ],
});

CC and BCC

await sendEmail({
  to: "user@example.com",
  cc: ["manager@example.com"],
  bcc: ["admin@example.com"],
  subject: "Important Notice",
  html: "<p>Notice content</p>",
});

Custom Headers

await sendEmail({
  to: "user@example.com",
  subject: "Newsletter",
  html: "<p>Newsletter content</p>",
  headers: {
    "X-Campaign-Id": "newsletter-2024-01",
    "X-Priority": "3",
  },
});

Testing

Test Email Sending

In development, use test recipients:

// Test mode - don't actually send
if (process.env.NODE_ENV === "development") {
  console.log("Would send email:", {
    to,
    subject,
    html,
  });
  return { success: true, messageId: "test" };
}

// Production - actually send
const result = await emailService.send({ to, subject, html });

Preview Emails

Use the template editor preview to see how emails look before sending.

Send Test Emails

From the template editor, send test emails to yourself to verify rendering and deliverability.

Monitoring

Check Email Logs

Most providers offer email logs:

  • Resend: Dashboard > Emails
  • SendGrid: Activity > Email Activity
  • Mailgun: Logs tab

Delivery Status

Track email delivery status:

// Most providers return a message ID
const result = await sendEmail({
  to: "user@example.com",
  subject: "Test",
  html: "<p>Test</p>",
});

console.log("Message ID:", result.messageId);
// Use this to track delivery in provider dashboard

Bounce Handling

Configure bounce webhooks in your email provider to handle bounced emails.

Troubleshooting

Emails not sending

  1. Check credentials - Verify API keys/SMTP credentials
  2. Check from address - Must be verified with provider
  3. Check rate limits - Provider may have rate limits
  4. Check spam filters - Test emails may go to spam
  5. Check logs - Look at console errors

Emails going to spam

  1. Verify your domain - Set up SPF, DKIM, DMARC
  2. Use verified from address - Don't use random addresses
  3. Good content - Avoid spam trigger words
  4. Warm up IP - New accounts may have lower reputation

Template variables not working

  1. Check variable names - Must match exactly
  2. Check template syntax - Use {{variableName}}
  3. Provide all variables - Missing variables show as empty
  4. Check for typos - Variable names are case-sensitive

Best Practices

  1. Always provide text version - For email clients that don't support HTML
  2. Keep templates simple - Complex HTML may not render correctly
  3. Test on multiple clients - Gmail, Outlook, Apple Mail, etc.
  4. Use responsive design - Mobile-friendly templates
  5. Include unsubscribe link - Required for marketing emails
  6. Respect rate limits - Don't send too many emails too fast
  7. Handle errors gracefully - Email sending can fail
  8. Log important emails - Keep audit trail for critical emails

Security

Prevent Email Injection

Always validate email addresses:

import { z } from "zod";

const emailSchema = z.string().email();

// Validate before sending
const validated = emailSchema.parse(userEmail);

Rate Limiting

Limit email sending to prevent abuse:

import { checkRateLimit, RateLimitPresets } from "@/lib/rate-limit";

const rateLimit = checkRateLimit(
  `email:${userId}`,
  RateLimitPresets.EMAIL_SEND
);

if (!rateLimit.success) {
  throw new Error("Too many emails sent");
}

await sendEmail({
  /* ... */
});

Sensitive Data

Don't include sensitive data in emails:

  • ❌ Passwords
  • ❌ API keys
  • ❌ Credit card numbers
  • ✅ Links to reset passwords
  • ✅ Generic confirmations

Next Steps

On this page