Skip to content

Template Service

The Template service manages email and document templates for Microtec ERP. It stores bilingual (Arabic/English) templates with Handlebars-style placeholder syntax and renders them on demand with caller-supplied data. The Notification service is its primary consumer.


Overview

PropertyValue
ServiceTemplate.Apis
CAE placementPrivate CAE
Gateway route prefix/template-apis/
SourcePlatforms/Src/InfrastructureServices/ (Template sub-module)
Primary consumerNotification.Apis
Template syntaxHandlebars ()
LanguagesArabic (ar), English (en)
Auth realmmicrotec (Keycloak)

Architecture

Templates are stored per-tenant. Each tenant can override the default platform templates to match their brand, language, and communication style.


Domain Model


Template Types

TypeEnumUsage
EmailTemplateType.EmailFull HTML email bodies
SMSTemplateType.SmsShort plain-text messages
PushTemplateType.PushShort push notification title + body
DocumentTemplateType.DocumentPDF/printed document layouts
InAppTemplateType.InAppIn-app notification messages

API Endpoints

Template Management

MethodRouteOperation
GET/TemplatesList all templates (paged)
GET/Templates/{Id}Get template with all language variants
GET/Templates/ByCode/{code}Get template by code
POST/TemplatesCreate new template
PUT/TemplatesUpdate template
DELETE/Templates/{Id}Delete custom template (system templates protected)

Template Content (per Language)

MethodRouteOperation
GET/Templates/{Id}/ContentsList all language variants
PUT/Templates/{Id}/ContentsUpdate content for a language
POST/Templates/{Id}/PreviewRender template with sample data

Render Endpoint

MethodRouteOperation
POST/Templates/RenderRender a template by code with caller-supplied data

System Template Codes

Platform templates are pre-seeded for every new tenant during provisioning:

CodeTypeDescription
INVOICE_POSTEDEmailCustomer invoice notification
PAYMENT_RECEIVEDEmailPayment receipt confirmation
PAYROLL_SALARY_CREDITEDSMSSalary credit alert
WORKFLOW_APPROVAL_REQUIREDPushApproval request notification
WORKFLOW_APPROVEDEmailWorkflow approval confirmation
WORKFLOW_REJECTEDEmailWorkflow rejection with comment
TENANT_WELCOMEEmailNew tenant onboarding email
SUBSCRIPTION_EXPIRY_WARNINGEmailSubscription expiry reminder (7, 3, 1 day)
SUBSCRIPTION_RENEWEDEmailSubscription renewal confirmation
USER_PASSWORD_RESETEmailPassword reset link
REPORT_READYEmailScheduled report delivery
LEAVE_REQUEST_SUBMITTEDPushLeave request notification to manager
LEAVE_REQUEST_APPROVEDPushLeave approval notification to employee

System Templates

Templates with IsSystemTemplate = true cannot be deleted. They can be customized (body and subject) per-tenant but the code and placeholder schema remain fixed.


Template Syntax

Templates use placeholders. HTML email templates support full HTML and inline CSS:

html
<!-- INVOICE_POSTED — Arabic (ar) variant -->
<html dir="rtl" lang="ar">
<body style="font-family: Arial, sans-serif; direction: rtl;">
  <h2>عزيزي {{CustomerName}}</h2>
  <p>نحيطكم علماً بإصدار الفاتورة رقم <strong>{{InvoiceNumber}}</strong></p>
  <table>
    <tr>
      <td>تاريخ الفاتورة</td>
      <td>{{InvoiceDate}}</td>
    </tr>
    <tr>
      <td>إجمالي المبلغ</td>
      <td>{{TotalAmount}} ريال</td>
    </tr>
  </table>
  <p>شكراً لثقتكم بنا.</p>
  <p>{{CompanyName}}</p>
</body>
</html>
html
<!-- INVOICE_POSTED — English (en) variant -->
<html dir="ltr" lang="en">
<body style="font-family: Arial, sans-serif;">
  <h2>Dear {{CustomerName}},</h2>
  <p>Invoice <strong>{{InvoiceNumber}}</strong> has been issued.</p>
  <table>
    <tr><td>Invoice Date</td><td>{{InvoiceDate}}</td></tr>
    <tr><td>Total Amount</td><td>SAR {{TotalAmount}}</td></tr>
  </table>
  <p>Thank you for your business.</p>
  <p>{{CompanyName}}</p>
</body>
</html>

Render API

The Notification service calls the Render endpoint internally. External callers can also use it for preview purposes:

Request

http
POST /template-apis/Templates/Render
Authorization: Bearer <token>
Content-Type: application/json

{
  "templateCode": "INVOICE_POSTED",
  "language": "ar",
  "data": {
    "CustomerName": "شركة الأمل للتجارة",
    "InvoiceNumber": "INV-2026-00123",
    "InvoiceDate": "2026-01-15",
    "TotalAmount": "12,500.00",
    "CompanyName": "ميكروتيك للتقنية"
  }
}

Response

json
{
  "success": true,
  "data": {
    "subject": "فاتورة رقم INV-2026-00123",
    "body": "<html dir=\"rtl\">...</html>",
    "plainTextBody": "عزيزي شركة الأمل للتجارة، الفاتورة رقم INV-2026-00123..."
  }
}

CQRS Examples

AddTemplateCommand

csharp
// Commands/AddTemplate/AddTemplateCommand.cs
public record AddTemplateCommand(AddTemplateDto Dto) : ICommand<int>;

// Commands/AddTemplate/Dto/AddTemplateDto.cs
public record AddTemplateDto(
    string Code,
    string NameAr,
    string NameEn,
    TemplateType Type,
    List<TemplateContentDto> Contents,
    List<TemplatePlaceholderDto> Placeholders);

public record TemplateContentDto(
    string Language,
    string Subject,
    string Body,
    string PlainTextBody);

public record TemplatePlaceholderDto(
    string Key,
    string Description,
    bool IsRequired,
    string SampleValue);

RenderTemplateQuery

csharp
// Queries/RenderTemplate/RenderTemplateQuery.cs
public record RenderTemplateQuery(
    string TemplateCode,
    string Language,
    Dictionary<string, string> Data) : IQuery<RenderedTemplateDto>;

// Queries/RenderTemplate/RenderTemplateQueryHandler.cs
public class RenderTemplateQueryHandler(
    IUnitOfWork<TemplateDbContext> unitOfWork,
    ITemplateRenderer renderer,
    ITenantProvider tenant) : IRequestHandler<RenderTemplateQuery, ApiResponse<RenderedTemplateDto>>
{
    public async Task<ApiResponse<RenderedTemplateDto>> Handle(
        RenderTemplateQuery request, CancellationToken ct)
    {
        // Try tenant-specific template first, fall back to system template
        var content = await unitOfWork.Repository<TemplateContent>()
            .GetQueryable()
            .AsNoTracking()
            .Where(c =>
                c.Template.Code == request.TemplateCode &&
                c.Language == request.Language &&
                (c.Template.TenantId == tenant.TenantId || c.Template.IsSystemTemplate))
            .OrderByDescending(c => c.Template.TenantId == tenant.TenantId) // tenant override first
            .FirstOrDefaultAsync(ct)
            ?? throw new NotFoundException($"Template '{request.TemplateCode}' not found for language '{request.Language}'");

        var rendered = renderer.Render(content, request.Data);

        return ApiResponse.Success(rendered);
    }
}

Localization Strategy

LanguageCodeDirectionCharacter Set
ArabicarRTLUnicode / UTF-8
EnglishenLTRASCII / UTF-8

Every template must have both ar and en content entries. The Notification service passes the language preference from the original request (defaulting to ar if not specified).

Missing Language Variant

If a template exists but lacks a content entry for the requested language, the render endpoint returns HTTP 404. Always seed both language variants when creating a new template.


Template Preview (Admin Tool)

Platform admins can preview templates directly from the BusinessOwners portal:

http
POST /template-apis/Templates/{Id}/Preview
Authorization: Bearer <token>

{
  "language": "ar",
  "sampleData": {
    "CustomerName": "شركة تجريبية",
    "InvoiceNumber": "INV-TEST-001"
  }
}

Returns the fully rendered HTML for inspection before going live.


Configuration

json
// appsettings.json (Template.Apis)
{
  "ConnectionStrings": {
    "AdminConnection": "Server=...;Database=MicrotecAdmin;..."
  },
  "Keycloak": {
    "Authority": "https://<keycloak-host>/realms/microtec",
    "Audience": "account"
  },
  "TemplateDefaults": {
    "DefaultLanguage": "ar",
    "MaxBodySizeKb": 512
  }
}

Deployment

The Template service is listed in the pipeline service config:

Devops/azure/config/container-backend/services-config.json  →  "template"

On first tenant provisioning, the PlatformsWorker seeds all system templates into the tenant's template store from the embedded resource defaults in the Template service.

Internal Documentation — Microtec Platform Team