Notifications & Templates
nauth-toolkit sends emails and SMS messages automatically during authentication flows --- verification codes, password resets, MFA challenges, and security alerts. Both systems use Handlebars templates that you can override with your own branding and wording.
How It Works
- A core service (signup, password reset, MFA, etc.) triggers a notification
- The appropriate provider (email or SMS) receives event-specific variables (code, link, expiry, etc.)
- The template engine merges global variables (branding) with event variables
- Handlebars renders the template
- The message is sent via your configured transport
@nauth-toolkit/email-nodemailer ships with production-ready responsive HTML templates for all 18 email types. SMS ships with 3 default templates. You only need to override the ones you want to customize.
Email Templates
Template Types
Core authentication emails
These emails are sent automatically during authentication flows. They cannot be suppressed individually (except via the global kill switch emailNotifications.enabled: false).
| Template Type | When Sent | Required Variables |
|---|---|---|
verification | Email verification during signup | code, link, expiryMinutes |
mfaEmailCode | Email MFA challenge code | code, expiryMinutes |
passwordReset | User requests password reset | link, expiryMinutes |
adminPasswordReset | Admin initiates password reset | code, link, expiryMinutes |
welcome | After onboarding completes (post-verification or immediately if verification is disabled) | (none) |
The validator checks that your template mentions each variable (e.g. {{code}} or {{#if code}}). At runtime, some variables may be undefined --- for example, link is only provided when a baseUrl is configured. Use Handlebars conditionals to handle this:
Security notification emails
These emails are suppressed by default. Enable them individually via emailNotifications.suppress:
{
emailNotifications: {
enabled: true,
suppress: {
passwordChanged: false, // Enable password changed notifications
sessionsRevoked: false, // Enable session revocation notifications
},
},
}
| Template Type | When Sent | Required Variables |
|---|---|---|
passwordChanged | Password successfully changed | (none) |
newDevice | New device detected during login | deviceName, timestamp |
sessionsRevoked | All sessions terminated | revokedCount |
adaptiveMfaRiskAlert | Adaptive MFA risk detected with notifyUser: true | riskLevel, riskFactors |
Account management emails
| Template Type | When Sent | Required Variables |
|---|---|---|
accountDisabled | Account disabled by admin | reason |
accountEnabled | Account re-enabled by admin | (none) |
emailChangedOld | Email changed (sent to old address) | (none) |
emailChangedNew | Email changed (sent to new address) | (none) |
phoneChanged | Phone number changed (sent to the account email) | (none) |
emailChanged | Email changed (legacy single-email template) | userEmail |
MFA notification emails
| Template Type | When Sent | Required Variables |
|---|---|---|
mfaEnabled | First MFA method enabled | (none) |
mfaDeviceRemoved | MFA device removed | (none) |
mfaMethodAdded | Additional MFA method added | (none) |
Suppression Keys vs Template Keys
The emailNotifications.suppress keys do not always match the TemplateType keys:
| Suppress Key | Template Type |
|---|---|
mfaFirstEnabled | mfaEnabled |
adaptiveMfaRiskDetected | adaptiveMfaRiskAlert |
emailChangedOld | emailChangedOld |
emailChangedNew | emailChangedNew |
phoneChanged | phoneChanged |
Use template type keys for customTemplates and suppress keys for emailNotifications.suppress.
Email Template Variables
Global variables (available to all templates)
| Variable | Source | Description |
|---|---|---|
appName | Global | Application name |
companyName | Global | Company name |
companyAddress | Global | Company address |
supportEmail | Global | Support email |
brandColor | Global | Brand hex color |
logoUrl | Global | Logo URL |
dashboardUrl | Global | Dashboard URL |
footerDisclaimer | Global | Custom footer text |
currentYear | Auto | Current year (e.g. 2026) |
subject | Auto | Rendered subject (available inside HTML/text body) |
previewText | Auto | Email preview text (defaults to subject) |
userName | Auto | Derived from email prefix (e.g. john from john@example.com) |
userEmail | Auto | Recipient email address |
Event-specific variables
| Template Type | Variables | Notes |
|---|---|---|
verification | code, link, expiryMinutes | link only present when signup.emailVerification.baseUrl is configured |
mfaEmailCode | code, expiryMinutes | |
passwordReset | code, link, expiryMinutes | link only present when password.passwordReset.baseUrl is configured (the shipped route ignores any request-body baseUrl) |
adminPasswordReset | code, link, expiryMinutes | link only present when baseUrl is provided |
welcome | (none) | Uses global variables only |
newDevice | deviceName, deviceType, ipAddress, location, timestamp | |
passwordChanged | changedBy, sessionsRevoked, timestamp | changedBy is 'user', 'admin', or 'reset' |
sessionsRevoked | revokedCount, reason, triggerEvent, timestamp | |
adaptiveMfaRiskAlert | riskScore, riskLevel, riskFactors, action, timestamp | riskLevel is 'low', 'medium', or 'high' |
accountDisabled | reason, performedBy, timestamp | |
accountEnabled | reason, performedBy, timestamp | |
emailChangedOld | newEmail, deactivatedMFADevices, timestamp, isOldEmail | Sent to the old email |
emailChangedNew | oldEmail, timestamp, isOldEmail | Sent to the new email |
phoneChanged | oldPhone, newPhone, deactivatedMFADevices, mfaDisabled, timestamp | Sent to the account email, never to a phone number |
mfaEnabled | firstMethod, deviceName, timestamp | |
mfaDeviceRemoved | deviceType, deviceName, removedBy, reason, remainingDeviceCount | removedBy is 'user', 'admin', or 'system' |
mfaMethodAdded | method, enabledMethods, deviceName, timestamp | enabledMethods is an array |
Password reset: code + optional link
The password reset flow always sends a code. The link is only included when a base URL is configured — password.passwordReset.baseUrl for the shipped forgot-password route (the route never takes a base URL from the request body, for security), or a server-computed baseUrl passed to AuthService.forgotPassword from your own controller. Your template should handle both scenarios:
This pattern applies equally to verification and adminPasswordReset templates.
SMS Templates
Template Types
| Template Type | When Sent | Required Variables |
|---|---|---|
verification | Phone verification during signup or phone number update | code, expiryMinutes |
mfa | MFA code for two-factor authentication via SMS | code, expiryMinutes |
passwordReset | Password reset via SMS (when configured for SMS delivery) | code, expiryMinutes |
The template type is determined automatically by the core service:
- Phone verification sends
verification(ormfaif the phone is already verified and the check is for MFA) - SMS MFA challenge sends
mfa - Password reset via SMS sends
passwordReset
Default SMS Templates
If you don't override any templates, these defaults are used:
verification:
{{#if appName}}{{appName}}: {{/if}}Your verification code is: {{code}}. Valid for {{expiryMinutes}} minutes.
mfa:
{{#if appName}}{{appName}}: {{/if}}Your MFA code is: {{code}}. Valid for {{expiryMinutes}} minutes.
passwordReset:
{{#if appName}}{{appName}}: {{/if}}Your password reset code is: {{code}}. Valid for {{expiryMinutes}} minutes.
SMS Template Variables
| Variable | Source | Description | Example |
|---|---|---|---|
code | Event | Verification/MFA/reset code | 123456 |
expiryMinutes | Event | Code expiration time in minutes | 5 |
appName | Global | Application name | My App |
firstName | Event | User's first name | John |
lastName | Event | User's last name | Doe |
userName | Event | Username | john_doe |
userEmail | Event | Email address | john@example.com |
phone | Event | Phone number (E.164) | +1234567890 |
companyName | Global | Company name | My Company Inc. |
supportPhone | Global | Support phone number | +1-800-123-4567 |
You can add any additional custom variables to globalVariables. They will be available in all templates via {{variableName}}.
Handlebars Syntax Reference
Email templates (full syntax)
Email templates support the full Handlebars feature set:
Variables:
Conditionals:
Loops:
Built-in helpers:
timestampFormattedNotification templates receive two forms of the event time: {{timestamp}} is the raw
UTC ISO string, and {{timestampFormatted}} is the same instant rendered in the
recipient's timezone and locale. Prefer the formatted one — a Handlebars helper cannot
know who the email is addressed to, so {{formatDate timestamp}} can only use the
server's settings unless you pass a timezone explicitly.
The recipient's preferences come from user.timezone / user.locale, falling back to
email.defaultTimezone / email.defaultLocale, then the host's settings, then UTC. How the
timezone itself is named (Australian Eastern Standard Time vs. GMT+10) is controlled by
email.timeZoneNameStyle. See Configuration.
| Helper | Description |
|---|---|
eq | Equal: (eq a b) |
ne | Not equal: (ne a b) |
gt | Greater than: (gt a b) |
lt | Less than: (lt a b) |
and | Logical AND: (and a b) |
or | Logical OR: (or a b) |
formatDate | Format a date: {{formatDate timestamp}}, or {{formatDate timestamp timezone="Europe/Dublin" locale="en-GB"}} |
formatDateTime | Same, including the time of day |
formatDate output changedformatDate previously called toLocaleDateString() with no options, giving a
short numeric date in the server's locale (14/09/2026). It now renders a medium-style
date (14 Sept 2026) and accepts optional timezone/locale hash arguments. If a custom
template depends on the old numeric form, pass an explicit locale or format the value
before it reaches the template.
Greeting pattern:
SMS templates (subset only)
SMS templates use a lightweight Handlebars-like engine that supports {{variable}} and {{#if variable}}...{{/if}} only.
{{#if appName}}{{appName}}: {{/if}}Your code is {{code}}.
Advanced Handlebars features ({{#each}}, {{#unless}}, {{#with}}, nested helpers) are not supported in SMS templates. Use email templates for complex logic.
What's Next
- Email Templates --- Override email templates and enable security notifications
- SMS Templates --- Override SMS templates with custom wording
- Email & SMS Providers --- Configure SMTP, AWS SES, SendGrid, AWS SNS, or a custom provider
- Configuration --- Full configuration reference