Email Templates
Override the emails nauth-toolkit sends to your users and control which security notifications are enabled. @nauth-toolkit/email-nodemailer ships with production-ready HTML templates for all 18 email types --- you only need to override the ones you want to customize.
The nauth example apps include working email configuration with custom templates.
You can disable the built-in email system entirely (emailNotifications.enabled: false) and use Lifecycle Hooks to send your own notifications via any service. Hooks fire on the same auth events (password changed, account locked, MFA enabled, etc.) and give you complete control over delivery.
Prerequisites
- A working auth setup (Quick Start)
- An email provider configured (Email & SMS Providers)
Step 1: Configure Branding
Global variables are available to every email template. Set them once and they appear in all emails automatically.
{
email: {
globalVariables: {
appName: 'My App',
companyName: 'My Company Inc.',
companyAddress: '123 Main St, City, Country',
supportEmail: 'support@myapp.com',
brandColor: '#4f46e5',
logoUrl: 'https://myapp.com/logo.png',
dashboardUrl: 'https://app.myapp.com',
},
},
}
The template engine automatically injects currentYear (e.g. 2026) into every render. Use it in footers: © {{currentYear}} {{companyName}}.
Step 2: Override a Template
Override any template via email.templates.customTemplates. This example overrides the verification email using a file-based template:
{
email: {
globalVariables: { /* ... */ },
templates: {
customTemplates: {
verification: {
htmlPath: './email-templates/verification.html.hbs',
textPath: './email-templates/verification.text.hbs',
},
},
},
},
}
HTML email templates use YAML frontmatter for the subject line:
---
subject: Verify your email - {{appName}}
---
<!DOCTYPE html>
<html>
<body>
{{#if firstName}}
<p>Hi {{firstName}},</p>
{{else}} {{#if userName}}
<p>Hi {{userName}},</p>
{{/if}} {{/if}}
<p>Your verification code: <strong>{{code}}</strong></p>
{{#if link}}
<p><a href="{{link}}">Verify Email Address</a></p>
{{/if}} {{#if expiryMinutes}}
<p>This code expires in {{expiryMinutes}} minutes.</p>
{{/if}}
</body>
</html>
Plain text templates are simple Handlebars text files (no frontmatter needed):
Verify Your Email
{{#if firstName}}Hi {{firstName}},{{else}}{{#if userName}}Hi {{userName}},{{/if}}{{/if}}
Your verification code: {{code}}
{{#if link}}Or use this link: {{link}}{{/if}}
{{#if expiryMinutes}}This code expires in {{expiryMinutes}} minutes.{{/if}}
You can also define templates inline or use a mix of file-based HTML with inline text:
{
email: {
templates: {
customTemplates: {
welcome: {
subject: 'Welcome to {{appName}}!',
html: '<h1>Welcome!</h1><p>Thanks for joining {{appName}}.</p>',
text: 'Welcome to {{appName}}!',
},
verification: {
subject: 'Verify your email - {{appName}}',
htmlPath: './email-templates/verification.html.hbs',
text: 'Your code: {{code}}. Expires in {{expiryMinutes}} minutes.',
},
},
},
},
}
Validation rules
| Rule | Details |
|---|---|
Must provide htmlPath or html | Cannot provide both |
Can optionally provide textPath or text | Cannot provide both |
subject is optional | Extracted from HTML frontmatter if not provided |
| Template must reference all required variables | Validated at startup; app fails to start if missing |
Text templates are optional. If you don't provide a text template, the engine generates one by stripping HTML tags from the rendered HTML. Providing both improves deliverability and accessibility.
See Notifications & Templates for the complete list of template types, required variables, and Handlebars syntax reference.
Step 3: Enable Security Notifications
Security notification emails (password changed, account lockout, new device, etc.) are suppressed by default. Enable the ones you want:
{
emailNotifications: {
enabled: true,
suppress: {
passwordChanged: false, // Enable password changed alerts
accountLockout: false, // Enable lockout alerts
newDevice: false, // Enable new device alerts
sessionsRevoked: false, // Enable session revocation alerts
},
},
}
Set a suppress key to false to enable that notification. See Notifications & Templates > Suppression for the full list of suppressible notification types and the mapping between suppress keys and template types.
Step 4: Test Your Template
Render templates locally to preview them before deploying:
import { HandlebarsTemplateEngine } from '@nauth-toolkit/email-nodemailer';
import { TemplateType } from '@nauth-toolkit/core';
import * as fs from 'fs';
const engine = new HandlebarsTemplateEngine({ baseDir: './email-templates' });
await engine.registerTemplateFromFile(TemplateType.VERIFICATION, 'verification.html.hbs', 'verification.text.hbs');
const result = await engine.render(TemplateType.VERIFICATION, {
code: '123456',
link: 'https://myapp.com/verify?code=123456',
expiryMinutes: 10,
userName: 'testuser',
firstName: 'John',
appName: 'My App',
supportEmail: 'support@myapp.com',
companyName: 'My Company Inc.',
});
console.log('Subject:', result.subject);
fs.writeFileSync('preview.html', result.html);
For local development, use the console provider to log emails instead of sending them:
import { ConsoleEmailProvider } from '@nauth-toolkit/email-console';
{
emailProvider: new ConsoleEmailProvider(),
}
Advanced
Partials (shared header/footer)
Reuse header and footer blocks across all templates using Handlebars partials.
Create a partials directory:
email-templates/
partials/
header.hbs
footer.hbs
verification.html.hbs
welcome.html.hbs
Write your partial:
Configure the engine to load them:
import { NodemailerEmailProvider, HandlebarsTemplateEngine } from '@nauth-toolkit/email-nodemailer';
const templateEngine = new HandlebarsTemplateEngine({
baseDir: process.cwd(),
partialsDir: './email-templates/partials',
});
{
emailProvider: new NodemailerEmailProvider({
transport: { /* ... */ },
defaults: { from: '"My App" <noreply@example.com>' },
templateEngine,
}),
}
Use partials in any template:
You can also register partials inline without files:
const templateEngine = new HandlebarsTemplateEngine({
partials: {
footer: '<footer>© {{currentYear}} {{companyName}}</footer>',
header: '<header><img src="{{logoUrl}}" alt="{{appName}}" /></header>',
},
});
Custom Handlebars helpers
Register custom helpers for advanced template logic:
import { HandlebarsTemplateEngine } from '@nauth-toolkit/email-nodemailer';
const templateEngine = new HandlebarsTemplateEngine({
helpers: {
uppercase: (str: string) => str.toUpperCase(),
truncate: (str: string, length: number) => (str.length > length ? str.substring(0, length) + '...' : str),
},
});
Use in templates:
Pass the engine to your email provider configuration:
{
emailProvider: new NodemailerEmailProvider({
transport: { /* ... */ },
defaults: { from: '"My App" <noreply@example.com>' },
templateEngine,
}),
}
Troubleshooting
Template validation fails at startup
Invalid template configuration for "verification":
Missing required parameters: link, expiryMinutes.
Template must include: {{code}}, {{link}}, {{expiryMinutes}}
Fix: Add the missing variables to your template. Use {{#if variable}} if you want conditional rendering --- the validator checks for presence, not unconditional usage.
Variables render as {{code}} instead of actual values
- Variable name typo in template
- Template engine not configured (check
email.templates.engine) - Variable not passed at render time
Emails go to spam
- Provide both
htmlandtexttemplates - Configure SPF, DKIM, DMARC on your domain
- Use a reputable email provider (AWS SES, SendGrid)
- Set a proper
fromaddress with domain matching
What's Next
- Notifications & Templates --- All template types, variables, and Handlebars syntax reference
- SMS Templates --- Customize SMS messages
- Email & SMS Providers --- Configure email transport (SMTP, AWS SES, SendGrid)
- Configuration --- Full configuration reference