Guide

Email System

Send transactional emails with beautiful HTML templates

UNuxt includes a complete email system with Nodemailer integration and beautiful HTML templates for all transactional emails.

Configure SMTP

Set up your email service provider by adding SMTP credentials to your .env file:

.env
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=your-email@example.com
SMTP_PASS=your-password
FROM_EMAIL=noreply@example.com
FROM_NAME=UNuxt

Common SMTP providers

SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your-email@gmail.com
SMTP_PASS=your-app-specific-password  # Not your regular password!
Gmail users: You must use an app-specific password, not your regular password. Enable 2FA on your Google account first.

Send emails

Use the email service from anywhere in your application:

server/api/send-welcome.post.ts
import { sendEmail } from '@unuxt/email'
import { baseTemplate } from '@unuxt/email/templates/base'

export default defineEventHandler(async (event) => {
  const { email, name } = await readBody(event)

  const { html, text } = baseTemplate({
    title: 'Welcome to UNuxt!',
    preheader: 'Thanks for joining us',
    content: `
      <p>Hi ${name},</p>
      <p>Welcome to our platform! We're excited to have you on board.</p>
    `,
    actionUrl: 'https://your-app.com/get-started',
    actionText: 'Get Started',
    footer: 'Questions? Reply to this email.',
  })

  await sendEmail({
    to: email,
    subject: 'Welcome to UNuxt!',
    html,
    text,
  })

  return { success: true }
})

Use built-in templates

UNuxt includes pre-built templates for common transactional emails:

Password reset

import { sendEmail, resetPasswordEmail } from '@unuxt/email'

const emailOptions = resetPasswordEmail({
  email: 'user@example.com',
  resetUrl: 'https://app.com/reset?token=abc123',
  expiresInMinutes: 60, // Optional, defaults to 60
})

await sendEmail(emailOptions)

Email verification

import { sendEmail, verifyEmailTemplate } from '@unuxt/email'

const emailOptions = verifyEmailTemplate({
  email: 'user@example.com',
  verifyUrl: 'https://app.com/verify?token=abc123',
})

await sendEmail(emailOptions)
import { sendEmail, magicLinkEmail } from '@unuxt/email'

const emailOptions = magicLinkEmail({
  email: 'user@example.com',
  magicUrl: 'https://app.com/magic?token=abc123',
  expiresInMinutes: 15, // Optional, defaults to 15
})

await sendEmail(emailOptions)

Organization invitation

import { sendEmail, organizationInvitationEmail } from '@unuxt/email'

const emailOptions = organizationInvitationEmail({
  email: 'newmember@example.com',
  organizationName: 'Acme Corp',
  invitedBy: 'John Doe',
  inviteUrl: 'https://app.com/auth/accept-invite/inv_123',
  role: 'member',
})

await sendEmail(emailOptions)

Create custom templates

Build your own email templates using the base template:

packages/email/src/templates/custom.ts
import { baseTemplate } from './base'
import type { SendEmailOptions } from '../mailer'

export interface CustomEmailOptions {
  email: string
  userName: string
  customData: string
}

export function customEmail(options: CustomEmailOptions): SendEmailOptions {
  const { email, userName, customData } = options

  const content = `
    <p>Hi ${userName},</p>
    <p>Here's your custom content: ${customData}</p>
    <p>Thanks for using our service!</p>
  `

  const { html, text } = baseTemplate({
    title: 'Custom Email',
    preheader: 'Your custom email subject',
    content,
    actionUrl: 'https://app.com/dashboard',
    actionText: 'View Dashboard',
    footer: 'Need help? Contact support@example.com',
  })

  return {
    to: email,
    subject: 'Your Custom Email',
    html,
    text,
  }
}

Handle email failures

The email system includes automatic fallbacks for development:

try {
  await sendEmail(emailOptions)
  console.log('Email sent successfully')
} catch (error) {
  console.error('Failed to send email:', error)
  // In development, URLs are logged to console
  if (process.env.NODE_ENV === 'development') {
    console.log('Email URL:', emailOptions.actionUrl)
  }
}
Development mode: If SMTP is not configured, emails are logged to the console instead of failing. This allows you to test authentication flows without setting up email first.

Customize email styling

All email templates use the base template with consistent branding. Customize the styling in packages/email/src/templates/base.ts:

packages/email/src/templates/base.ts
const styles = {
  primaryColor: '#10b981', // Your brand color
  textColor: '#1f2937',
  backgroundColor: '#f9fafb',
  buttonColor: '#10b981',
  buttonTextColor: '#ffffff',
}

Test emails in development

Option 1: Use a test SMTP service

Services like Mailtrap or Ethereal provide test SMTP servers:

.env
SMTP_HOST=smtp.ethereal.email
SMTP_PORT=587
SMTP_USER=your-ethereal-user
SMTP_PASS=your-ethereal-pass

Option 2: Check console output

If SMTP is not configured, URLs and email content are logged to your terminal:

[EMAIL] Email service not configured - logging to console
[AUTH] Password reset URL: http://localhost:3000/reset?token=abc123

Verify email service

Check if the email service is working:

server/api/test-email.get.ts
import { verifyConnection } from '@unuxt/email'

export default defineEventHandler(async () => {
  const isConnected = await verifyConnection()
  return { connected: isConnected }
})

Troubleshooting

Emails not sending

  1. Check SMTP configuration in .env
  2. Verify SMTP credentials are correct
  3. Check server logs for errors
  4. Look for "Email service initialized successfully" message on startup

Gmail "Less secure app" error

Gmail no longer supports "less secure apps". Use an app-specific password:

  1. Enable 2FA on your Google account
  2. Go to App Passwords
  3. Generate a new app password
  4. Use that password in SMTP_PASS

Connection timeout

  • Check firewall settings
  • Verify SMTP port (usually 587 or 465)
  • Try a different port if one doesn't work
  • Some ISPs block port 25

Next steps

Copyright © 2026