Skip to content

Scriban Templating

Deepstaging includes Scriban as a native templating engine for rendering structured text content — email HTML, SMS bodies, notification copy, webhook payloads, and any other non-code output that has a fixed shape with variable data. Templates are compile-time validated against typed models, so a typo like {{ order.Totl }} is a build error, not a runtime 500.

Why Templating?

Most applications have content that follows a pattern: welcome emails, order confirmations, SMS verification codes, push notification bodies, audit log messages. Without a templating system, this content lives as interpolated strings scattered across handler code — hard to find, hard to review, impossible to validate at compile time.

Deepstaging's Scriban integration makes template-driven content a first-class concern:

  • Compile-time validation — template variables are checked against your model at build time
  • Separation of content from logic — templates live in dedicated files, not buried in C# handlers
  • Consistent syntax — one templating language across email, SMS, notifications, and beyond
  • Zero runtime reflection — templates are pre-compiled during the build

Quick Start

Define a template file and a typed model, then render:

// Models/WelcomeEmailModel.cs
public sealed record WelcomeEmailModel(string UserName, string ActivationUrl);
<!-- Templates/Email/Welcome.scriban-html -->
<h1>Welcome, {{ user_name }}!</h1>
<p>Click <a href="{{ activation_url }}">here</a> to activate your account.</p>
// In your command handler
var html = Template.Render("Email/Welcome", new WelcomeEmailModel(
    UserName: buyer.Name,
    ActivationUrl: $"https://app.example.com/activate/{token}"));

var result = await emailService.SendAsync(new EmailMessage
{
    From = new EmailAddress("noreply@example.com"),
    To = [new EmailAddress(buyer.Email)],
    Subject = "Welcome!",
    HtmlBody = html
});

Supported Modules

Each module that benefits from templating has a dedicated page explaining usage patterns. The table below links to the relevant section in each module's docs.

Module Template Use Example
Email HTML and plain-text email bodies Order confirmations, welcome emails, password resets
SMS Message body text Verification codes, shipping updates, appointment reminders
Notifications Push notification title and body {{ order_id }} has shipped, in-app alert copy
Webhooks Outbound webhook JSON payloads Custom event payload formats for third-party integrations
Audit Human-readable audit trail messages {{ actor }} updated {{ entity_type }} {{ entity_id }}: {{ changes }}
Jobs Failure and retry notification messages Dead-letter alerts, job summary reports
Integration Events Dead-letter and poison-message alert content Structured alert bodies for failed event processing

Template Syntax

Scriban uses {{ }} delimiters with a clean, Ruby-like syntax. Full reference: Scriban Language.

Variables

Hello {{ user_name }},

Your order {{ order_id }} has been confirmed.
Total: ${{ order_total | math.format "0.00" }}

Conditionals

{{ if has_tracking_url }}
Track your package: {{ tracking_url }}
{{ else }}
Tracking information will be available soon.
{{ end }}

Loops

Your order contains {{ items | array.size }} items:
{{ for item in items }}
  - {{ item.name }} × {{ item.quantity }} — ${{ item.price | math.format "0.00" }}
{{ end }}

Filters (Pipes)

Scriban filters transform values inline:

{{ name | string.upcase }}
{{ created_at | date.to_string "%B %d, %Y" }}
{{ description | string.truncate 100 }}
{{ amount | math.format "0.00" }}

Whitespace Control

Use - to strip whitespace around tags — useful for clean text output in SMS and notifications:

{{- if promo_code -}}
Use code {{ promo_code }} for {{ discount }}% off!
{{- end -}}

Best Practices

Keep templates focused on content, not logic

Templates should render content, not make business decisions. Keep conditionals simple — if a template needs complex branching, the model should pre-compute the values.

{{- # Good: model pre-computes the greeting -}}
{{ greeting }},

{{- # Bad: business logic in the template -}}
{{ if is_premium_user && renewal_date < today }}Dear valued member{{ else }}Hi{{ end }},

Use plain-text fallbacks for email

Always provide both HTML and plain-text templates for email. SMS and push notifications are inherently plain text.

Templates/
├── Email/
│   ├── Welcome.scriban-html      ← HTML version
│   └── Welcome.scriban-txt       ← plain-text fallback
├── Sms/
│   └── VerificationCode.scriban-txt
└── Notifications/
    └── OrderShipped.scriban-txt

Name templates by intent, not by module

Use names that describe the content, not the delivery channel — the same template might be used across email and in-app notifications:

Templates/
├── OrderConfirmation.scriban-html
├── OrderConfirmation.scriban-txt
├── PasswordReset.scriban-html
└── ShippingUpdate.scriban-txt