Developer GuideTemplate Schema Reference

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:

  1. Variables Array - The data schema defining what variables the template expects
  2. 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

FieldTypeRequiredDescription
idstringNo*Unique identifier (auto-generated if omitted)
namestringYesVariable name (used in {{variableName}} syntax)
typestringYesOne of: string, number, boolean, date, array, object
requiredbooleanNoWhether the variable is required (default: false)
descriptionstringNoHuman-readable description
defaultValueanyRecommendedDefault/sample value for testing and preview
isCalculatedbooleanNotrue if this is a calculated variable
expressionstringNoJavaScript expression (required if isCalculated: true)
schemaobjectNoType schema for array and object types
formatobjectNoDisplay 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

TypeDescription
headerTop section (logo, navigation)
main or bodyMain content section
footerBottom 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

FormatDescription
["pdf"]Generate PDF attachment
["excel"]Generate Excel attachment
["pdf", "excel"]Generate both formats

Page Settings

FieldTypeDefaultDescription
formatstring"A4""A4", "Letter", "Legal", "A3", "A5", "Tabloid"
orientationstring"portrait""portrait" or "landscape"
marginobject-Page margins (top, right, bottom, left)
backgroundColorstring"#ffffff"Page background color
headerobject-Header configuration
footerobject-Footer configuration

Component Reference

Component Types by Template Type

ComponentEmailPDFExcelDescription
rich-textRich text with HTML
buttonCTA buttons
imageImages
tableData tables
containerComponent wrapper
columnsMulti-column layout
dividerHorizontal rule
spacerVertical space
conditionalIf/else rendering
loopIteration over arrays
listOrdered/unordered lists
qr-codeQR codes
html-fragmentRaw HTML (email only)
chartCharts (attachments only)
timelineActivity 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

OperatorDescription
==Equals
!=Not equals
>Greater than
<Less than
>=Greater than or equal
<=Less than or equal
containsString/array contains
startsWithString starts with
endsWithString ends with
existsValue exists (not null/undefined)
isEmptyValue 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

PropRequiredDescription
dataSourceYesVariable containing array: {{items}}
itemVariableYesName for current item: item
indexVariableNoName for current index: index

System-Provided Global Constants

These variables are automatically available in all templates:

Date Constants

VariableExample Value
{{__CURRENT_DATE__}}2025-12-02
{{__CURRENT_YEAR__}}2025
{{__CURRENT_MONTH__}}December
{{__CURRENT_DAY__}}2
{{__CURRENT_DAY_NAME__}}Tuesday

Company Constants

VariableDescription
{{__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 name and type fields
  • Every variable has a defaultValue for testing
  • Array variables have itemType and schema.itemSchema.properties
  • Object variables have schema.properties
  • Schema has sections array (email) or pages array (attachment)
  • Every component has a unique id
  • Variable references use {{variableName}} syntax
  • Loop components use {{arrayVariable}} for dataSource
  • 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

  1. Click Manage Variables in the toolbar
  2. Switch to the Raw JSON tab
  3. Paste your variables array
  4. Click Validate to check for errors
  5. Click Apply Changes

Import Schema

  1. Click the code icon (</>) in the toolbar to open Raw Schema Editor
  2. Paste your schema JSON
  3. Click Validate to check for errors
  4. Click Apply Changes

Save Template

Click Save to persist your changes.