Skip to main content

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​

  1. A core service (signup, password reset, MFA, etc.) triggers a notification
  2. The appropriate provider (email or SMS) receives event-specific variables (code, link, expiry, etc.)
  3. The template engine merges global variables (branding) with event variables
  4. Handlebars renders the template
  5. 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 TypeWhen SentRequired Variables
verificationEmail verification during signupcode, link, expiryMinutes
mfaEmailCodeEmail MFA challenge codecode, expiryMinutes
passwordResetUser requests password resetlink, expiryMinutes
adminPasswordResetAdmin initiates password resetcode, link, expiryMinutes
welcomeAfter onboarding completes (post-verification or immediately if verification is disabled)(none)
Understanding "required variables"

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:

{{#if link}}<a href="{{link}}">Reset Password</a>{{/if}}

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 TypeWhen SentRequired Variables
passwordChangedPassword successfully changed(none)
newDeviceNew device detected during logindeviceName, timestamp
sessionsRevokedAll sessions terminatedrevokedCount
adaptiveMfaRiskAlertAdaptive MFA risk detected with notifyUser: trueriskLevel, riskFactors

Account management emails​

Template TypeWhen SentRequired Variables
accountDisabledAccount disabled by adminreason
accountEnabledAccount re-enabled by admin(none)
emailChangedOldEmail changed (sent to old address)(none)
emailChangedNewEmail changed (sent to new address)(none)
phoneChangedPhone number changed (sent to the account email)(none)
emailChangedEmail changed (legacy single-email template)userEmail

MFA notification emails​

Template TypeWhen SentRequired Variables
mfaEnabledFirst MFA method enabled(none)
mfaDeviceRemovedMFA device removed(none)
mfaMethodAddedAdditional MFA method added(none)

Suppression Keys vs Template Keys​

The emailNotifications.suppress keys do not always match the TemplateType keys:

Suppress KeyTemplate Type
mfaFirstEnabledmfaEnabled
adaptiveMfaRiskDetectedadaptiveMfaRiskAlert
emailChangedOldemailChangedOld
emailChangedNewemailChangedNew
phoneChangedphoneChanged

Use template type keys for customTemplates and suppress keys for emailNotifications.suppress.

Email Template Variables​

Global variables (available to all templates)​

VariableSourceDescription
appNameGlobalApplication name
companyNameGlobalCompany name
companyAddressGlobalCompany address
supportEmailGlobalSupport email
brandColorGlobalBrand hex color
logoUrlGlobalLogo URL
dashboardUrlGlobalDashboard URL
footerDisclaimerGlobalCustom footer text
currentYearAutoCurrent year (e.g. 2026)
subjectAutoRendered subject (available inside HTML/text body)
previewTextAutoEmail preview text (defaults to subject)
userNameAutoDerived from email prefix (e.g. john from john@example.com)
userEmailAutoRecipient email address

Event-specific variables​

Template TypeVariablesNotes
verificationcode, link, expiryMinuteslink only present when signup.emailVerification.baseUrl is configured
mfaEmailCodecode, expiryMinutes
passwordResetcode, link, expiryMinuteslink only present when password.passwordReset.baseUrl is configured (the shipped route ignores any request-body baseUrl)
adminPasswordResetcode, link, expiryMinuteslink only present when baseUrl is provided
welcome(none)Uses global variables only
newDevicedeviceName, deviceType, ipAddress, location, timestamp
passwordChangedchangedBy, sessionsRevoked, timestampchangedBy is 'user', 'admin', or 'reset'
sessionsRevokedrevokedCount, reason, triggerEvent, timestamp
adaptiveMfaRiskAlertriskScore, riskLevel, riskFactors, action, timestampriskLevel is 'low', 'medium', or 'high'
accountDisabledreason, performedBy, timestamp
accountEnabledreason, performedBy, timestamp
emailChangedOldnewEmail, deactivatedMFADevices, timestamp, isOldEmailSent to the old email
emailChangedNewoldEmail, timestamp, isOldEmailSent to the new email
phoneChangedoldPhone, newPhone, deactivatedMFADevices, mfaDisabled, timestampSent to the account email, never to a phone number
mfaEnabledfirstMethod, deviceName, timestamp
mfaDeviceRemoveddeviceType, deviceName, removedBy, reason, remainingDeviceCountremovedBy is 'user', 'admin', or 'system'
mfaMethodAddedmethod, enabledMethods, deviceName, timestampenabledMethods is an array

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:

{{#if link}}
<p><a href="{{link}}">Reset Password</a></p>
<p>Or enter this code manually: <strong>{{code}}</strong></p>
{{else}}
<p>Your password reset code: <strong>{{code}}</strong></p>
{{/if}}
<p>This code expires in {{expiryMinutes}} minutes.</p>

This pattern applies equally to verification and adminPasswordReset templates.

SMS Templates​

Template Types​

Template TypeWhen SentRequired Variables
verificationPhone verification during signup or phone number updatecode, expiryMinutes
mfaMFA code for two-factor authentication via SMScode, expiryMinutes
passwordResetPassword reset via SMS (when configured for SMS delivery)code, expiryMinutes
note

The template type is determined automatically by the core service:

  • Phone verification sends verification (or mfa if 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​

VariableSourceDescriptionExample
codeEventVerification/MFA/reset code123456
expiryMinutesEventCode expiration time in minutes5
appNameGlobalApplication nameMy App
firstNameEventUser's first nameJohn
lastNameEventUser's last nameDoe
userNameEventUsernamejohn_doe
userEmailEventEmail addressjohn@example.com
phoneEventPhone number (E.164)+1234567890
companyNameGlobalCompany nameMy Company Inc.
supportPhoneGlobalSupport phone number+1-800-123-4567
note

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:

<p>Welcome to {{appName}}!</p>
<p>Your code is: {{code}}</p>
<p>&copy; {{currentYear}} {{companyName}}</p>

Conditionals:

{{#if firstName}}
<p>Hello {{firstName}}!</p>
{{else}}
<p>Hello!</p>
{{/if}}

{{#unless isVerified}}
<p>Please verify your email.</p>
{{/unless}}

Loops:

{{#each riskFactors}}
<li>{{this}}</li>
{{/each}}

Built-in helpers:

{{#if (eq changedBy "admin")}}
<p>An administrator changed your password.</p>
{{/if}}

{{#if (gt riskScore 80)}}
<p>High risk detected!</p>
{{/if}}

<p>Date: {{timestampFormatted}}</p>
Prefer timestampFormatted

Notification 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.

HelperDescription
eqEqual: (eq a b)
neNot equal: (ne a b)
gtGreater than: (gt a b)
ltLess than: (lt a b)
andLogical AND: (and a b)
orLogical OR: (or a b)
formatDateFormat a date: {{formatDate timestamp}}, or {{formatDate timestamp timezone="Europe/Dublin" locale="en-GB"}}
formatDateTimeSame, including the time of day
formatDate output changed

formatDate 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:

{{#if firstName}}
<p>Hi {{firstName}},</p>
{{else}}
{{#if userName}}<p>Hi {{userName}},</p>{{else}}<p>Hi,</p>{{/if}}
{{/if}}

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}}.
warning

Advanced Handlebars features ({{#each}}, {{#unless}}, {{#with}}, nested helpers) are not supported in SMS templates. Use email templates for complex logic.

What's Next​