Template Schema Reference
This guide defines the exact JSON schema structure for email and attachment templates in FormaMail. Use this as the authoritative reference when creating templates programmatically, via API, or with AI assistants.
For AI/LLM Systems: Variables must be a plain JSON array [...], NOT wrapped in an object like {"variables": [...]}. See the AI Import Format section below.
AI/LLM Import Format
When generating templates with AI, you need to provide two separate JSON structures:
- Variables Array - The data schema defining what variables the template expects
- Template Schema - The visual design structure (sections and components)
Variables Format
⚠️ CRITICAL FOR AI/LLM: Variables must be a PLAIN JSON ARRAY [...], NOT wrapped in an object like { "variables": [...] }.
Common AI Mistake: Do NOT wrap variables in an object. The format is a plain array.
✅ CORRECT format (plain array):
[
{
"id": "var_1",
"name": "customerName",
"type": "string",
"required": true,
"description": "Customer's full name",
"defaultValue": "John Doe"
},
{
"id": "var_2",
"name": "orderTotal",
"type": "number",
"required": true,
"defaultValue": 99.99
}
]❌ INCORRECT format (DO NOT USE):
{
"variables": [
{ "name": "customerName", "type": "string" }
]
}This WILL NOT work. Variables must be a plain array, not wrapped in an object.
[
{
"id": "var_1",
"name": "customerName",
"type": "string",
"required": true,
"description": "Customer's full name",
"defaultValue": "John Doe"
}
]Variable Object Fields
| Field | Type | Required | Description |
|---|---|---|---|
id | string | No* | Unique identifier (auto-generated if omitted) |
name | string | Yes | Variable name (used in {{variableName}} syntax) |
type | string | Yes | One of: string, number, boolean, date, array, object |
required | boolean | No | Whether the variable is required (default: false) |
description | string | No | Human-readable description |
defaultValue | any | Recommended | Default/sample value for testing and preview |
isCalculated | boolean | No | true if this is a calculated variable |
expression | string | No | JavaScript expression (required if isCalculated: true) |
schema | object | No | Type schema for array and object types |
format | object | No | Display format configuration |
*If id is omitted, it will be auto-generated when imported.
Array Variable Example
{
"id": "var_items",
"name": "items",
"type": "array",
"itemType": "object",
"required": true,
"description": "List of order items",
"defaultValue": [
{ "name": "Product A", "quantity": 1, "price": 29.99 }
],
"schema": {
"type": "array",
"itemType": "object",
"itemSchema": {
"properties": {
"name": { "type": "string" },
"quantity": { "type": "number" },
"price": { "type": "number" }
}
}
}
}Object Variable Example
{
"id": "var_customer",
"name": "customer",
"type": "object",
"required": true,
"description": "Customer information",
"defaultValue": {
"name": "Jane Smith",
"email": "jane@example.com"
},
"schema": {
"type": "object",
"properties": {
"name": { "type": "string", "description": "Customer name" },
"email": { "type": "string", "description": "Customer email" }
}
}
}Email Template Schema
Email templates use a sections array at the root level.
{
"sections": [
{
"id": "body-section",
"type": "body",
"props": {
"backgroundColor": "#ffffff",
"padding": "40px 20px"
},
"components": [
// Array of component objects
]
}
]
}Section Types
| Type | Description |
|---|---|
header | Top section (logo, navigation) |
main or body | Main content section |
footer | Bottom section (copyright, links) |
Attachment Template Schema
PDF and Excel templates use a pages array with nested sections:
{
"outputFormats": ["pdf"],
"pages": [
{
"pageId": "page-1",
"pageName": "Invoice",
"pageSettings": {
"format": "A4",
"orientation": "portrait",
"margin": { "top": "20mm", "right": "15mm", "bottom": "20mm", "left": "15mm" }
},
"sections": [
{
"id": "main-section",
"type": "main",
"props": {},
"components": []
}
]
}
]
}Output Formats
| Format | Description |
|---|---|
["pdf"] | Generate PDF attachment |
["excel"] | Generate Excel attachment |
["pdf", "excel"] | Generate both formats |
Page Settings
| Field | Type | Default | Description |
|---|---|---|---|
format | string | "A4" | "A4", "Letter", "Legal", "A3", "A5", "Tabloid" |
orientation | string | "portrait" | "portrait" or "landscape" |
margin | object | - | Page margins (top, right, bottom, left) |
backgroundColor | string | "#ffffff" | Page background color |
header | object | - | Header configuration |
footer | object | - | Footer configuration |
Component Reference
Component Types by Template Type
| Component | Excel | Description | ||
|---|---|---|---|---|
rich-text | ✓ | ✓ | ✓ | Rich text with HTML |
button | ✓ | ✗ | ✗ | CTA buttons |
image | ✓ | ✓ | ✓ | Images |
table | ✓ | ✓ | ✓ | Data tables |
container | ✓ | ✓ | ✗ | Component wrapper |
columns | ✓ | ✓ | ✗ | Multi-column layout |
divider | ✓ | ✓ | ✗ | Horizontal rule |
spacer | ✓ | ✓ | ✗ | Vertical space |
conditional | ✓ | ✓ | ✓ | If/else rendering |
loop | ✓ | ✓ | ✓ | Iteration over arrays |
list | ✓ | ✓ | ✗ | Ordered/unordered lists |
qr-code | ✓ | ✓ | ✗ | QR codes |
html-fragment | ✓ | ✗ | ✗ | Raw HTML (email only) |
chart | ✗ | ✓ | ✓ | Charts (attachments only) |
timeline | ✓ | ✓ | ✗ | Activity timeline |
Component Structure
Components use one of three patterns:
1. Content-based (rich-text, button, list):
{
"id": "welcome-text",
"type": "rich-text",
"props": { "marginBottom": "16px" },
"content": "<p>Hello {{name}}</p>"
}2. Props-based (image, divider, spacer, qr-code):
{
"id": "logo",
"type": "image",
"props": {
"src": "{{company.logo}}",
"alt": "Logo",
"width": "120px"
}
}3. Children-based (container, columns, conditional, loop):
{
"id": "wrapper",
"type": "container",
"props": { "padding": "20px" },
"children": [
{ "id": "child-1", "type": "rich-text", "content": "..." }
]
}Variable Syntax
Basic Substitution
{{variableName}}
{{customer.firstName}}
{{order.items[0].name}}In Content
{
"type": "rich-text",
"content": "<p>Hello {{customer.name}}, your order #{{order.number}} is ready.</p>"
}In Props
{
"type": "button",
"props": {
"href": "{{trackingUrl}}"
},
"content": "Track Package"
}Conditional Logic
{
"id": "discount-banner",
"type": "conditional",
"props": {
"condition": "hasDiscount",
"operator": "==",
"value": true
},
"children": [
{
"id": "discount-text",
"type": "rich-text",
"content": "<p>You saved ${{discountAmount}}!</p>"
}
]
}Operators
| Operator | Description |
|---|---|
== | Equals |
!= | Not equals |
> | Greater than |
< | Less than |
>= | Greater than or equal |
<= | Less than or equal |
contains | String/array contains |
startsWith | String starts with |
endsWith | String ends with |
exists | Value exists (not null/undefined) |
isEmpty | Value is empty |
Loop Iteration
{
"id": "items-loop",
"type": "loop",
"props": {
"dataSource": "{{items}}",
"itemVariable": "item",
"indexVariable": "index"
},
"children": [
{
"id": "item-row",
"type": "rich-text",
"content": "<p>{{index}}. {{item.name}} - ${{item.price}}</p>"
}
]
}Loop Props
| Prop | Required | Description |
|---|---|---|
dataSource | Yes | Variable containing array: {{items}} |
itemVariable | Yes | Name for current item: item |
indexVariable | No | Name for current index: index |
System-Provided Global Constants
These variables are automatically available in all templates:
Date Constants
| Variable | Example Value |
|---|---|
{{__CURRENT_DATE__}} | 2025-12-02 |
{{__CURRENT_YEAR__}} | 2025 |
{{__CURRENT_MONTH__}} | December |
{{__CURRENT_DAY__}} | 2 |
{{__CURRENT_DAY_NAME__}} | Tuesday |
Company Constants
| Variable | Description |
|---|---|
{{__COMPANY_NAME__}} | Company display name |
{{__COMPANY_LEGAL_NAME__}} | Legal name |
{{__COMPANY_EMAIL__}} | Contact email |
{{__COMPANY_PHONE__}} | Phone number |
{{__COMPANY_WEBSITE__}} | Website URL |
{{__COMPANY_ADDRESS_LINE1__}} | Address line 1 |
{{__COMPANY_CITY__}} | City |
{{__COMPANY_STATE__}} | State/Province |
{{__COMPANY_POSTAL_CODE__}} | Postal/ZIP code |
{{__COMPANY_COUNTRY__}} | Country |
Complete Example
Variables
[
{
"id": "var_customer",
"name": "customer",
"type": "object",
"required": true,
"defaultValue": { "name": "Jane Smith", "email": "jane@example.com" },
"schema": {
"type": "object",
"properties": {
"name": { "type": "string" },
"email": { "type": "string" }
}
}
},
{
"id": "var_items",
"name": "items",
"type": "array",
"itemType": "object",
"required": true,
"defaultValue": [
{ "name": "Widget", "qty": 2, "price": 25.00 }
],
"schema": {
"type": "array",
"itemType": "object",
"itemSchema": {
"properties": {
"name": { "type": "string" },
"qty": { "type": "number" },
"price": { "type": "number" }
}
}
}
},
{
"id": "var_total",
"name": "orderTotal",
"type": "number",
"required": true,
"defaultValue": 99.99
}
]Email Schema
{
"sections": [
{
"id": "body-section",
"type": "body",
"props": { "backgroundColor": "#f9fafb", "padding": "40px 20px" },
"components": [
{
"id": "main-container",
"type": "container",
"props": {
"backgroundColor": "#ffffff",
"maxWidth": "600px",
"margin": "0 auto",
"borderRadius": "8px",
"padding": "32px"
},
"children": [
{
"id": "greeting",
"type": "rich-text",
"props": { "marginBottom": "24px" },
"content": "<h1 style=\"margin: 0; font-size: 24px;\">Hello {{customer.name}}</h1>"
},
{
"id": "intro",
"type": "rich-text",
"props": { "marginBottom": "16px" },
"content": "<p style=\"margin: 0; color: #6b7280;\">Thank you for your order:</p>"
},
{
"id": "items-loop",
"type": "loop",
"props": {
"dataSource": "{{items}}",
"itemVariable": "item"
},
"children": [
{
"id": "item-row",
"type": "rich-text",
"content": "<p style=\"margin: 4px 0;\">• {{item.name}} x {{item.qty}} = ${{item.price}}</p>"
}
]
},
{
"id": "total-row",
"type": "rich-text",
"props": { "marginTop": "16px" },
"content": "<p style=\"margin: 0; font-weight: bold;\">Total: ${{orderTotal}}</p>"
}
]
}
]
}
]
}AI Generation Checklist
Before outputting a template, verify:
- Variables are a plain array
[...], not{ "variables": [...] } - Every variable has
nameandtypefields - Every variable has a
defaultValuefor testing - Array variables have
itemTypeandschema.itemSchema.properties - Object variables have
schema.properties - Schema has
sectionsarray (email) orpagesarray (attachment) - Every component has a unique
id - Variable references use
{{variableName}}syntax - Loop components use
{{arrayVariable}}fordataSource - Nested object access uses dot notation:
{{customer.name}}
How to Import into FormaMail
Open the Template Designer
Navigate to Templates and create a new template or edit an existing one.
Import Variables
- Click Manage Variables in the toolbar
- Switch to the Raw JSON tab
- Paste your variables array
- Click Validate to check for errors
- Click Apply Changes
Import Schema
- Click the code icon (
</>) in the toolbar to open Raw Schema Editor - Paste your schema JSON
- Click Validate to check for errors
- Click Apply Changes
Save Template
Click Save to persist your changes.