SMTP OTP Dispatcher Reference
The SmtpOtpDispatcher delivers one-time passwords (OTPs) via email using SMTP. It includes a built-in default template with security warnings and supports fully customizable plain text, HTML, and subject templates.
Registration
Section titled “Registration”Register the SMTP One-Time Password (OTP) dispatcher using UseSmtpOtpDispatcher on the authentication builder:
using Duende.IdentityServer;using Duende.UserManagement;
builder.Services .AddIdentityServer() .AddUserManagement(um => um .Authentication(auth => auth.UseSmtpOtpDispatcher(options => { options.Host = "smtp.example.com"; options.Port = 587; options.EnableSsl = true; options.FromEmail = "noreply@example.com"; options.FromName = "MyApp"; })) );SmtpOtpDispatcherOptions
Section titled “SmtpOtpDispatcherOptions”All properties on SmtpOtpDispatcherOptions are configured via the Action<SmtpOtpDispatcherOptions> delegate passed to UseSmtpOtpDispatcher.
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
Host | string | Yes | N/A | SMTP server hostname or IP address. |
Port | int | No | 1025 | SMTP server port. Typically 587 for STARTTLS or 465 for implicit TLS. |
EnableSsl | bool | No | true | Whether to use SSL/TLS for the SMTP connection. Always set to true in production. |
FromEmail | string | Yes | N/A | The sender email address used in the From header. |
FromName | string | Yes | N/A | The sender display name used in the From header and in email templates. |
Domain | string? | No | null | The domain or URL where the user should enter the code (e.g. "https://app.example.com"). When set, the default template includes a domain-specific security warning. When null, templates receive "our official website" for the {Domain} placeholder. |
PlainTextTemplate | string? | No | null | Custom plain text body template. Supports template placeholders. When null, the built-in default template is used. |
HtmlTemplate | string? | No | null | Custom HTML body template. Supports template placeholders. When set, the email is sent as HTML. Takes precedence over PlainTextTemplate. |
SubjectTemplate | string? | No | null | Custom subject line template. Supports {FromName} and {Code} placeholders. When null, defaults to "{FromName} confirmation code". |
Default Email Format
Section titled “Default Email Format”When no custom templates are configured, the sender uses a built-in plain text template with security warnings.
Subject:
MyApp confirmation codeBody:
123-456 is your MyApp confirmation code (expires after 5 minute(s))
IMPORTANT SECURITY INFORMATION:- You should only use this code if you requested it- If you did not request this code, please ignore this email- Only enter this code on https://app.example.com- Do not share this code with anyone- MyApp will never ask you for this codeThe domain line is only included when Domain is set. Without it, the line reads Only enter this code on our official website.
Template Placeholders
Section titled “Template Placeholders”All three template properties (PlainTextTemplate, HtmlTemplate, SubjectTemplate) support the following placeholders:
| Placeholder | Description | Example Value |
|---|---|---|
{Code} | The OTP code, formatted with hyphens between groups. | 123-456 |
{FromName} | The configured sender name (SmtpOtpDispatcherOptions.FromName). | MyApp |
{ExpiresMinutes} | The number of minutes until the code expires, as a whole number. | 5 |
{Domain} | The configured domain (SmtpOtpDispatcherOptions.Domain), or "our official website" when not set. | app.example.com |
Note: SubjectTemplate only supports {FromName} and {Code}.
Custom Templates
Section titled “Custom Templates”Plain Text Template
Section titled “Plain Text Template”using Duende.IdentityServer;
builder.Services .AddIdentityServer() .AddUserManagement(um => um .Authentication(auth => auth.UseSmtpOtpDispatcher(options => { options.Host = "smtp.example.com"; options.Port = 587; options.EnableSsl = true; options.FromEmail = "noreply@example.com"; options.FromName = "MyApp"; options.Domain = "app.example.com";
options.PlainTextTemplate = @"Hello,
Your verification code is: {Code}
This code will expire in {ExpiresMinutes} minutes.
SECURITY NOTICE:- If you did not request this code, please ignore this email- Only enter this code on {Domain}- Never share this code with anyone, including {FromName} staff- We will never ask you to provide this code over phone or email
Thank you,The {FromName} Team"; })) );HTML Template
Section titled “HTML Template”using Duende.IdentityServer;using Duende.UserManagement;
builder.Services .AddIdentityServer() .AddUserManagement(um => um .Authentication(auth => auth.UseSmtpOtpDispatcher(options => { options.Host = "smtp.example.com"; options.Port = 587; options.EnableSsl = true; options.FromEmail = "noreply@example.com"; options.FromName = "MyApp"; options.Domain = "app.example.com";
options.HtmlTemplate = @"<!DOCTYPE html><html><head> <style> body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; } .container { max-width: 600px; margin: 0 auto; padding: 20px; } .code-box { background: #f4f4f4; padding: 20px; text-align: center; font-size: 32px; font-weight: bold; letter-spacing: 5px; margin: 20px 0; } .warning { background: #fff3cd; border-left: 4px solid #ffc107; padding: 15px; margin: 20px 0; } </style></head><body> <div class='container'> <h1>{FromName}</h1> <div class='code-box'>{Code}</div> <p>This code expires in <strong>{ExpiresMinutes} minutes</strong>.</p> <div class='warning'> <strong>Security:</strong> Only enter this code on <strong>{Domain}</strong>. Never share it with anyone. </div> </div></body></html>"; })) );Custom Subject
Section titled “Custom Subject”options.SubjectTemplate = "[{FromName}] Your verification code: {Code}";Binding From Configuration
Section titled “Binding From Configuration”SMTP connection settings can be bound from appsettings.json:
{ "Smtp": { "Host": "smtp.sendgrid.net", "Port": 587, "FromEmail": "noreply@mycompany.com" }}Your startup code can then bind to this section:
using Duende.IdentityServer;using Duende.UserManagement;
builder.Services .AddIdentityServer() .AddUserManagement(um => um .Authentication(auth => auth.UseSmtpOtpDispatcher(options => { builder.Configuration.GetSection("Smtp").Bind(options); options.EnableSsl = true; options.FromName = "MyCompany"; options.Domain = "https://app.mycompany.com"; })) );Security Best Practices
Section titled “Security Best Practices”- Always include security warnings: Whether using the default or a custom template, tell users to only enter the code if they requested it, where to enter it, and never to share it.
- Set the
Domainproperty: Telling users the exact URL where the code should be entered reduces phishing risk. - Enable SSL/TLS: Always set
EnableSsl = trueand use port587(STARTTLS) or465(implicit TLS) in production. - Use clear expiration times: Include
{ExpiresMinutes}in your template so users know how long the code is valid. - Brand your emails consistently: Use your organization name via
FromNamethroughout the template to build user trust.