| name | email-template-developer |
| description | Use this skill when working on email, SMS, or push notification templates โ authoring Handlebars HTML email templates, understanding the backend template engine, visual testing with Playwright, or managing multi-tenant template deployment. Triggers on: body.hbs.html, subject.hbs.txt, Handlebars email templates, notification templates, email branding, _MASTER_TEMPLATE, template screenshots, Playwright visual regression, notification tenancy, template blob storage, EMAIL/SMS/PUSH channels, or cross-client HTML email compatibility. |
Email Template Developer
Author and maintain notification templates across the Handlebars template ecosystem. Covers template authoring conventions, HTML email constraints, visual testing, backend template processing, and multi-tenant deployment.
Scope: Template authoring and backend template engine understanding. For general backend service work, use the backend-developer skill.
Repositories
| Repo | Location | Purpose |
|---|
| Notification-Templates | | Template authoring, Handlebars HTML, visual testing |
| Notification-Service | | Backend notification service, template engine, tenant config |
Template Directory Structure
Notification-Templates/
โโโ .github/
โ โโโ instructions/
โ โ โโโ copilot.instructions.md # HTML email development guidelines
โ โโโ prompts/
โ โโโ create_new_template.prompt.md
โ โโโ create_screenshots.prompt.md
โ โโโ validate_urls.prompt.md
โโโ Templates/
โ โโโ EMAIL/
โ โ โโโ _MASTER_TEMPLATE/ # Base for ALL new templates
โ โ โ โโโ DE/body.hbs.html + subject.hbs.txt
โ โ โ โโโ EN/body.hbs.html + subject.hbs.txt
โ โ โ โโโ FR/body.hbs.html + subject.hbs.txt
โ โ โ โโโ IT/body.hbs.html + subject.hbs.txt
โ โ โโโ <TEMPLATE_NAME>/
โ โ โโโ DE/body.hbs.html + subject.hbs.txt
โ โ โโโ EN/body.hbs.html + subject.hbs.txt
โ โ โโโ FR/body.hbs.html + subject.hbs.txt
โ โ โโโ IT/body.hbs.html + subject.hbs.txt
โ โโโ SMS/
โ โ โโโ <TEMPLATE_NAME>/
โ โ โโโ <LANG>/body.hbs.txt
โ โโโ PUSH/
โ โ โโโ <TEMPLATE_NAME>/
โ โ โโโ <LANG>/body.hbs.txt + title.hbs.txt
โ โโโ DEFINITION/
โ โ โโโ _MASTER_TEMPLATE.json
โ โ โโโ <TEMPLATE_NAME>.json
โ โโโ global_testdata.json # Shared test data for all templates
โโโ package.json # Node.js: handlebars, playwright, pixelmatch
โโโ playwright.config.js # Chromium, 800x1200 viewport, threshold 0.2
Golden Rules
- ALWAYS use
_MASTER_TEMPLATE as base for new email templates โ never start from scratch.
- 4 languages always: DE, EN, FR, IT โ every template must have all 4 language variants.
- No translations in
body.hbs.html โ the HTML body structure is identical across languages. Only subject.hbs.txt differs per language.
- Preserve block comment anchors โ comments like
<!-- Content 1-spaltig -->, <!-- Address block --> serve as structural markers and must be kept.
- Stay under ~100kB per template to avoid Gmail truncation.
Existing Copilot Prompts
The repo includes pre-built prompts in /.github/prompts/ โ use them instead of reinventing workflows:
| Prompt | Purpose |
|---|
/create_new_template | Copy _MASTER_TEMPLATE โ new template, set subjects per language |
/create_screenshots | Replace placeholders with global_testdata.json, generate Playwright screenshots to /.temp/ |
/validate_urls | Check all template URLs per language, output CSV to /.temp/URL_Validation_Results.csv |
Creating a New Template (Workflow)
- Copy
/Templates/EMAIL/_MASTER_TEMPLATE/ โ /Templates/EMAIL/<NEW_NAME>/
- Copy
/Templates/DEFINITION/_MASTER_TEMPLATE.json โ /Templates/DEFINITION/<NEW_NAME>.json
- Update subjects in each
subject.hbs.txt per language
- Update
<title> tags in each body.hbs.html
- Replace
_MASTER_TEMPLATE references with the new template name
- Modify HTML body content as needed (same structure across all languages)
- Generate screenshots and validate
HTML Email Constraints
Cross-Client Compatibility
Target clients (last 24 months): Apple Mail, Outlook (classic + new), Gmail, Yahoo Mail.
| Constraint | Rule |
|---|
| Layout | Table-based responsive, max-width: 620px |
| Background | background-color: #f4f4f4 outer wrapper |
| Buttons | Bulletproof (VML fallback for Outlook), min 44ร44px tap target |
| Contrast | WCAG AA minimum |
| Format detection | x-ms-format-detection="none" to suppress auto-linking |
| Outlook conditionals | <!--[if mso]> and <!--[if !mso]><!--> ... <!--<![endif]--> |
| Dark mode | color-scheme: light dark, prefers-color-scheme media query, neutral base palette |
| Validation | Audit against Can I Email before finalizing |
Template HTML Structure
<table role="presentation" width="100%" style="background-color: #f4f4f4;">
<tr><td align="center">
<table width="620" style="max-width: 620px;">
</table>
</td></tr>
</table>
Size Optimization
Keep templates compact. Refactor verbose markup into block-based structure. Remove redundant inline styles by consolidating shared styles. Minimize whitespace in production templates.
Brand Colors
| Name | RGB | Hex |
|---|
| primary-red | rgb(216, 32, 52) | #D82034 |
| anthrazit | rgb(53, 53, 53) | #353535 |
| white | rgb(255, 255, 255) | #FFFFFF |
| light-grey | rgb(244, 244, 244) | #F4F4F4 |
| warm-grey | rgb(189, 183, 175) | #BDB7AF |
| dark-warm-grey | rgb(148, 142, 136) | #948E88 |
| sand | rgb(233, 227, 218) | #E9E3DA |
| burgundy | rgb(147, 25, 66) | #931942 |
| midnight-blue | rgb(31, 51, 84) | #1F3354 |
| forest-green | rgb(81, 117, 95) | #51755F |
| ocean-blue | rgb(72, 122, 150) | #487A96 |
| petrol | rgb(47, 107, 109) | #2F6B6D |
| clay | rgb(190, 135, 100) | #BE8764 |
| mauve | rgb(162, 128, 148) | #A28094 |
| golden-yellow | rgb(209, 168, 70) | #D1A846 |
Handlebars Syntax
Placeholders
{{ Salutation }}
{{ FirstName }}
{{ LastName }}
Conditionals
{{#if DisplayDebugInfo}}
<p>Debug: {{Environment}}-{{NotificationId}}</p>
{{#if RobotSessionId}}Robot: {{RobotSessionId}}{{/if}}
{{/if}}
Iteration
{{#each Docs}}
<tr><td>{{Name}}</td><td>{{Date}}</td></tr>
{{/each}}
Test Data
All placeholders map to keys in Templates/global_testdata.json. This file contains ~100+ fields including:
Salutation (per-language variants: "Sehr geehrter Herr", "Dear Mr", "Cher Monsieur", "Gentile Signor")
FirstName, LastName, personal data fields
Docs array, EV_Contracts, ThreeAStart data
DisplayDebugInfo, Environment, NotificationId, RobotSessionId
When creating screenshots, resolve placeholders with this test data. Handle language-specific overrides (e.g., Salutation changes per language).
Visual Testing
Playwright Screenshots
npm run screenshots
npm test
npm run test:update
npm run test:ui
npm run test:report
- Viewport: 800ร1200, Chromium only
- Comparison:
pixelmatch with threshold 0.2, binary mode
- Output: Screenshots saved to
/.temp/<TEMPLATE_NAME>_<LANG>.png
- Process: Replace Handlebars placeholders with
global_testdata.json data โ render in Playwright โ capture full-page screenshot
Section Testing
Use testi.at for testing individual sections or new components before integrating them into a full template.
Backend Template Engine
File Name Constants (WellKnown.cs)
EMAIL: body.hbs.html + subject.hbs.txt
SMS: body.hbs.txt
PUSH: body.hbs.txt + title.hbs.txt
Folder names: EMAIL, SMS, PUSH, DEFINITION
Two Template Engines
| Engine | Type | When Used |
|---|
| Handlebars | File-based (Blob Storage) | Default for all channels and content types |
| Render | API-based | Email channel only, when application is specified and tenant has Render configured |
Selection logic (TemplateEngineSelector):
- Render: Email + non-null application + tenant has Render config โ uses Render
- Handlebars: everything else (fallback for all channels)
- No cross-engine fallback โ if the selected engine fails, the request fails
Handlebars Processing Chain
Request โ TemplateEngineSelector
โ TenantAwareTemplateResolver (resolve tenant config)
โ HandlebarsTemplateRepository (fetch from Azure Blob Storage)
โ HandlebarsCompiledTemplatesCache (memory cache: 24h absolute / 1h sliding)
โ HandlebarsContentParser.ParseAsync(tenantId, templateKey, data)
โ HandlebarsDotNet compilation + rendering
Blob Storage Path
Templates stored at: {channel}/{TEMPLATE_NAME}/{languageCode}/{fileName}
Example: EMAIL/ADDRESS_CHANGE_NOTIFICATION/DE/body.hbs.html
Backend Models
record EmailTemplate(TemplateCompositeKey Id, string SubjectTemplate, string BodyTemplate);
record TemplateCompositeKey(string TemplateName, string LanguageCode);
record TemplateKey(string TemplateName, string LanguageCode, ChannelType ChannelType, ContentFieldType ContentFieldType);
enum ChannelType { Email, Sms, Push }
enum ContentFieldType { Body, Subject }
Multi-Tenant Architecture
Tenants
| Tenant | ID | Default Lang | Supported Languages |
|---|
| TenantA | tenanta | de | de, fr, it, en |
| TenantB | tenantb | de | de, fr, it, en |
| TenantC | tenantc | de | de, fr, it, en |
Each tenant has:
- Isolated blob storage for templates (own container)
- Own provider configuration (SendGrid, Twilio, template engines)
- Feature flags controlling which channels are enabled
Template Provider Configuration
Templates = new TemplateProvidersConfiguration
{
RenderDebugInfo = "Tenants:{TenantId}:ProvidersConfiguration:Templates:RenderDebugInfo",
Handlebars = new HandlebarsTemplateProviderConfiguration
{
BlobStorage = new BlobStorageConfiguration
{
ConnectionString = "Tenants:{TenantId}:...:BlobStorage:ConnectionString",
Url = "Tenants:{TenantId}:...:BlobStorage:Url",
ContainerName = "templates"
}
},
Render = new RenderTemplateProviderConfiguration
{
TenantName = "chid",
Applications = []
}
}
Template Deployment
- Create templates in the Notification-Templates repo
- Test with Playwright screenshots and
global_testdata.json
- Upload to tenant-specific Azure Blob Storage
- Verify by sending test notifications in the tenant context
Deployment methods:
- Azure Storage Explorer (manual)
- CI/CD pipeline with Azure CLI
- Notification CLI tool (
notification-cli publish)
API Tenancy Requirements
All Notification API requests require:
- Header:
X-Tenant-Id: {tenant-id}
- OAuth scopes:
api.notification.access + notification.tenant.{tenant-id}
SMS & Push Templates
SMS Template Pattern
Your verification code is: {{Code}}
{{#if DisplayDebugInfo}}
{{Environment}}-{{NotificationId}}
{{#if RobotSessionId}}Robot:{{RobotSessionId}}{{/if}}
{{/if}}
Push Template Pattern
title.hbs.txt โ notification title
body.hbs.txt โ notification body
Both use same Handlebars syntax as email templates, without HTML.
Common Pitfalls
- Starting from scratch instead of
_MASTER_TEMPLATE โ always copy the master.
- Translating body HTML โ the body structure is language-independent; only subjects differ.
- Missing a language โ every template needs DE, EN, FR, IT.
- Ignoring Outlook โ always test MSO conditional comments and VML button fallbacks.
- Gmail truncation โ keep total HTML under ~100kB.
- Forgetting debug info block โ include
{{#if DisplayDebugInfo}} section for diagnostics.
- Wrong file names โ must be exactly
body.hbs.html and subject.hbs.txt (backend WellKnown.cs constants).
- Missing test data keys โ verify all placeholders exist in
global_testdata.json before generating screenshots.