This article explains how to build a fully dynamic email template engine that supports placeholders, conditional blocks, reusable layouts, variables, and preview mode.
We will cover Handlebars (client-friendly) and Razor (powerful server-side) approaches.
Sections include practical code, database scripts, workflow diagrams, ER diagram, architecture diagram, and a sequence diagram — all in smaller, professional heading styles.
1. Introduction
Modern applications often require dynamic emails such as password reset, invoice notifications, shipping alerts, onboarding sequences, and marketing campaigns.
Hard-coded email bodies quickly become difficult to maintain.
A template engine allows you to:
Create emails without deploying code
Insert placeholders such as {{UserName}} or {{TotalAmount}}
Support layouts, partials, looping, conditions
Preview email in UI
Send test emails
Manage template versions
2. Requirements for a Modern Email Template Engine
A practical enterprise-grade email system must support:
3. Database Design
Email Templates Table
| Column | Type | Description |
|---|
| TemplateId | INT (PK) | Unique ID |
| TemplateName | VARCHAR(200) | Key such as "OrderConfirmation" |
| SubjectTemplate | NVARCHAR(MAX) | Subject with placeholders |
| BodyTemplate | NVARCHAR(MAX) | HTML body |
| CreatedDate | DATETIME | |
| UpdatedDate | DATETIME | |
| IsActive | BIT | |
SQL Script
CREATE TABLE EmailTemplate (
TemplateId INT IDENTITY(1,1) PRIMARY KEY,
TemplateName VARCHAR(200) NOT NULL,
SubjectTemplate NVARCHAR(MAX) NOT NULL,
BodyTemplate NVARCHAR(MAX) NOT NULL,
CreatedDate DATETIME DEFAULT GETDATE(),
UpdatedDate DATETIME NULL,
IsActive BIT DEFAULT 1
);
4. ER Diagram (Simplified)
+-------------------+
| EmailTemplate |
+-------------------+
| TemplateId (PK) |
| TemplateName |
| SubjectTemplate |
| BodyTemplate |
| CreatedDate |
| UpdatedDate |
| IsActive |
+-------------------+
5. Architecture Diagram (Visio Style)
+-----------------+ +-------------------+
| Angular UI | <----> | Email API |
| (Template Editor)| | (ASP.NET Core) |
+-----------------+ +--------+----------+
|
|
+------v---------+
| SQL Server |
| Templates |
+----------------+
|
|
+-------v--------+
| Email Sender |
| (SMTP/SendGrid)|
+----------------+
6. Workflow / Flowchart (Template Rendering)
[User Requests Email]
|
v
[Load Template by Name]
|
v
[Inject Variables]
|
v
[Render Using Handlebars/Razor]
|
v
[Send Email via SMTP]
7. Using Handlebars in ASP.NET Core
Step 1: Install Package
dotnet add package Handlebars.Net
Step 2: Template Rendering Service
public class HandlebarsTemplateService
{
public string Render(string template, object data)
{
var compiled = Handlebars.Compile(template);
return compiled(data);
}
}
Step 3: Example Template
Hello {{UserName}},
Your order {{OrderId}} of amount {{Total}} has been confirmed.
Step 4: Rendering Example
var model = new
{
UserName = "Rajesh",
OrderId = "SO-4521",
Total = 5500
};
string output = service.Render(templateBody, model);
8. Using Razor as Template Engine
Razor gives more power:
Step 1: Install package
dotnet add package RazorLight
Step 2: Razor Rendering Service
public class RazorTemplateService
{
private readonly RazorLightEngine engine;
public RazorTemplateService()
{
engine = new RazorLightEngineBuilder()
.UseMemoryCaching()
.Build();
}
public async Task<string> RenderAsync(string template, object model)
{
return await engine.CompileRenderStringAsync(Guid.NewGuid().ToString(), template, model);
}
}
Step 3: Example Razor Email
Hi @Model.UserName,
@if(Model.IsPremium)
{
<p>Thank you for being a premium member!</p>
}
<p>Your order total: @Model.Total</p>
9. API Endpoints (ASP.NET Core)
Get All Templates
[HttpGet]
public IActionResult GetTemplates()
{
return Ok(_repository.GetAll());
}
Render Preview
[HttpPost("preview")]
public async Task<IActionResult> Preview([FromBody] PreviewRequest request)
{
var template = _repo.GetByName(request.TemplateName);
var result = await _templateService.RenderAsync(template.BodyTemplate, request.Data);
return Ok(result);
}
10. Angular Module (Template Editor UI)
Routes
const routes: Routes = [
{ path: '', component: TemplateListComponent },
{ path: 'edit/:id', component: TemplateEditorComponent },
{ path: 'preview/:id', component: TemplatePreviewComponent }
];
Template Editor (HTML)
<textarea [(ngModel)]="template.bodyTemplate" rows="20"></textarea>
<button (click)="preview()">Preview</button>
<button (click)="save()">Save</button>
Preview Component
<div [innerHTML]="previewHtml"></div>
11. Sequence Diagram (Rendering & Sending Email)
User
|
| Request → /email/send
v
Email API
| Load Template
| Inject Variables
| Render (Handlebars/Razor)
v
SMTP Service
| Send Email
v
User (Email Delivered)
12. Adding Layouts & Partials
For enterprise projects, common header/footer must be reusable.
Handlebars Example
{{> header}}
Hello {{UserName}}
{{> footer}}
Register partial
Handlebars.RegisterTemplate("header", "<h2>Welcome</h2>");
13. Versioning Support
Add a new table:
TemplateVersionId INT PK
TemplateId INT FK
VersionNo INT
BodyTemplate NVARCHAR(MAX)
CreatedDate DATETIME
14. Production-Grade Enhancements
15. Conclusion
A dynamic email template engine dramatically improves maintainability and makes your system scalable. Using Handlebars gives simplicity, while Razor provides power and flexibility.
With ASP.NET Core + Angular + SQL Server, you can build a fully professional template engine with preview, versioning, layouts, and dynamic placeholders.