User GuideVariables & Dynamic Content

Variables & Dynamic Content

Variables are the heart of FormaMail’s template system, allowing you to create dynamic, personalized emails and attachments. This comprehensive guide covers everything you need to know about using variables effectively.


Table of Contents

  1. Overview
  2. Variable Types
  3. Constant Variables
  4. System-Provided Global Constants
  5. Calculated Variables
  6. Using Variables in Templates
  7. Data Tables & Columns
  8. Formatting Options
  9. Best Practices
  10. Examples

Overview

Variables allow you to insert dynamic content into your templates. When sending an email or generating an attachment, you provide actual values for these variables, and FormaMail substitutes them into your template.

Key Concepts

Variables: Placeholders for dynamic data (e.g., {{firstName}}, {{order.total}})

All variable types are stored in a single unified variables array with different characteristics:

  • Input Variables: Required data provided via API (required: true)
  • Constant Variables: Fixed values with defaults, not requiring user input (required: false, isCalculated: false, has defaultValue)
  • Calculated Variables: Computed values derived from other variables (isCalculated: true, has expression)

Variable Syntax

Variables use Handlebars syntax with double curly braces:

{{variableName}}
{{user.email}}
{{order.items[0].name}}

Important: Handlebars syntax ({{}}) is used for:

  • Inserting variables in templates
  • Referencing data sources in tables and loops

Direct references (without {{}}) are used for:

  • Calculated variable expressions
  • Table column formulas
  • Conditional expressions

Variable Types

FormaMail supports six variable types, each with specific characteristics and validation rules.

1. String Variables

Text data of any length.

Definition:

{
  "name": "firstName",
  "type": "string",
  "required": true,
  "defaultValue": "Guest",
  "description": "Customer's first name"
}

Properties:

  • format: Optional format validation (email, url, uuid, date, datetime, phone)
  • minLength: Minimum string length
  • maxLength: Maximum string length

Examples:

{
  "name": "email",
  "type": "string",
  "format": "email",
  "required": true
}
 
{
  "name": "companyWebsite",
  "type": "string",
  "format": "url",
  "defaultValue": "https://example.com"
}
 
{
  "name": "description",
  "type": "string",
  "minLength": 10,
  "maxLength": 500
}

Usage in template:

Hello {{firstName}},
Your email is {{email}}.
Visit us at {{companyWebsite}}.

2. Number Variables

Numeric data (integers or decimals).

Definition:

{
  "name": "quantity",
  "type": "number",
  "required": true,
  "defaultValue": 1,
  "min": 0,
  "max": 1000
}

Properties:

  • min: Minimum value
  • max: Maximum value
  • format: Formatting options for display

Formatting Options:

{
  "name": "price",
  "type": "number",
  "format": {
    "type": "number",
    "preset": "CURRENCY",
    "currency": "USD",
    "decimals": 2
  }
}

Format Presets:

  • DECIMAL: Standard decimal number (e.g., 1234.56)
  • CURRENCY: Currency with symbol (e.g., $1,234.56)
  • PERCENT: Percentage (e.g., 75.5%)
  • CUSTOM: Custom format string

Supported Currencies: USD, EUR, GBP, JPY, CNY, INR, CAD, AUD

Examples:

{
  "name": "total",
  "type": "number",
  "format": {
    "type": "number",
    "preset": "CURRENCY",
    "currency": "USD",
    "decimals": 2
  }
}
 
{
  "name": "discountPercent",
  "type": "number",
  "format": {
    "type": "number",
    "preset": "PERCENT",
    "decimals": 1
  }
}

Usage in template:

Quantity: {{quantity}}
Price: {{price}}
Total: {{total}}
Discount: {{discountPercent}}

3. Date Variables

Date and time values.

Definition:

{
  "name": "orderDate",
  "type": "date",
  "required": true,
  "description": "Order placement date"
}

Accepted Formats:

  • ISO 8601 string: "2025-01-15T10:30:00Z"
  • Date object: new Date()
  • Unix timestamp: 1705320600000

Formatting Options:

{
  "name": "deliveryDate",
  "type": "date",
  "format": {
    "type": "date",
    "preset": "US"
  }
}

Date Format Presets:

  • ISO: 2025-01-15 (YYYY-MM-DD)
  • US: 01/15/2025 (MM/DD/YYYY)
  • EU: 15/01/2025 (DD/MM/YYYY)
  • LONG: January 15, 2025
  • SHORT: Jan 15, 2025
  • CUSTOM: Custom format string

Custom Format Tokens:

  • yyyy: 4-digit year (2025)
  • yy: 2-digit year (25)
  • MMMM: Full month name (January)
  • MMM: Short month name (Jan)
  • MM: 2-digit month (01)
  • M: Month without leading zero (1)
  • dd: 2-digit day (15)
  • d: Day without leading zero (15)
  • HH: 24-hour format (14)
  • hh: 12-hour format (02)
  • mm: Minutes (30)
  • ss: Seconds (45)
  • a: AM/PM

Examples:

{
  "name": "invoiceDate",
  "type": "date",
  "format": {
    "type": "date",
    "preset": "US"
  }
}
 
{
  "name": "appointmentTime",
  "type": "date",
  "format": {
    "type": "date",
    "preset": "CUSTOM",
    "customFormat": "MMMM dd, yyyy at hh:mm a"
  }
}

Usage in template:

Order placed on {{orderDate}}
Delivery scheduled for {{deliveryDate}}
Appointment: {{appointmentTime}}

4. Boolean Variables

True/false values.

Definition:

{
  "name": "isPremiumUser",
  "type": "boolean",
  "defaultValue": false
}

Usage in Conditionals:

{{#if isPremiumUser}}
  Welcome, Premium Member!
{{else}}
  Upgrade to Premium for exclusive benefits.
{{/if}}

5. Object Variables

Structured data with nested properties.

Definition:

{
  "name": "customer",
  "type": "object",
  "properties": {
    "name": { "type": "string", "required": true },
    "email": { "type": "string", "format": "email", "required": true },
    "phone": { "type": "string" },
    "address": {
      "type": "object",
      "properties": {
        "street": { "type": "string" },
        "city": { "type": "string" },
        "state": { "type": "string" },
        "zipCode": { "type": "string" }
      }
    }
  }
}

Nesting Limit: Maximum 5 levels deep

Usage in template:

Customer: {{customer.name}}
Email: {{customer.email}}
Address: {{customer.address.street}}, {{customer.address.city}}, {{customer.address.state}} {{customer.address.zipCode}}

Usage in API:

{
  "customer": {
    "name": "John Doe",
    "email": "john@example.com",
    "phone": "+1-555-0123",
    "address": {
      "street": "123 Main St",
      "city": "San Francisco",
      "state": "CA",
      "zipCode": "94105"
    }
  }
}

6. Array Variables

Lists of items (useful for tables and loops).

Definition:

{
  "name": "orderItems",
  "type": "array",
  "itemType": "object",
  "properties": {
    "name": { "type": "string", "required": true },
    "quantity": { "type": "number", "required": true },
    "unitPrice": { "type": "number", "required": true },
    "total": { "type": "number", "required": true }
  }
}

Item Types:

  • string: Array of strings
  • number: Array of numbers
  • date: Array of dates
  • boolean: Array of booleans
  • object: Array of objects (most common for tables)

Usage in Tables:

{
  "type": "table",
  "props": {
    "dataSource": "{{orderItems}}",
    "columns": [
      { "header": "Item", "field": "name" },
      { "header": "Qty", "field": "quantity" },
      { "header": "Price", "field": "unitPrice" },
      { "header": "Total", "field": "total" }
    ]
  }
}

Usage in Loops:

{{#each orderItems}}
  - {{name}}: {{quantity}} x {{unitPrice}} = {{total}}
{{/each}}

Usage in API:

{
  "orderItems": [
    {
      "name": "Widget A",
      "quantity": 2,
      "unitPrice": 25.00,
      "total": 50.00
    },
    {
      "name": "Widget B",
      "quantity": 1,
      "unitPrice": 75.00,
      "total": 75.00
    }
  ]
}

Constant Variables

Constants are fixed values that don’t change per email. They’re useful for company information, tax rates, or other static data. In the unified variable system, constants are defined as regular variables with required: false, isCalculated: false, and a defaultValue.

Defining Constants

Constants are defined within the variables array:

{
  "variables": [
    {
      "id": "const_company",
      "name": "COMPANY_NAME",
      "type": "string",
      "required": false,
      "isCalculated": false,
      "defaultValue": "Acme Corporation",
      "description": "Company name (constant)"
    },
    {
      "id": "const_taxrate",
      "name": "TAX_RATE",
      "type": "number",
      "required": false,
      "isCalculated": false,
      "defaultValue": 0.08,
      "description": "Tax rate (constant)"
    },
    {
      "id": "const_support",
      "name": "SUPPORT_EMAIL",
      "type": "string",
      "required": false,
      "isCalculated": false,
      "defaultValue": "support@acme.com",
      "description": "Support email (constant)"
    },
    {
      "id": "const_freeship",
      "name": "FREE_SHIPPING_THRESHOLD",
      "type": "number",
      "required": false,
      "isCalculated": false,
      "defaultValue": 50.00,
      "description": "Free shipping threshold (constant)"
    }
  ]
}

Naming Convention: UPPER_CASE with underscores is recommended but not enforced.

Constant Characteristics:

  • required: Set to false (constants don’t require user input)
  • isCalculated: Set to false (constants are not computed)
  • defaultValue: Must provide a default value
  • type: Any type is allowed: string, number, boolean, date, object, array

Using Constants in Templates

Thank you for choosing {{COMPANY_NAME}}!

Contact us at {{SUPPORT_EMAIL}}

Tax ({{TAX_RATE}}%): {{calculatedTax}}

🎁 Free shipping on orders over ${{FREE_SHIPPING_THRESHOLD}}!

Using Constants in Calculated Variables

Constants can be referenced in calculated variable expressions:

{
  "id": "calc_total",
  "name": "totalWithTax",
  "type": "number",
  "required": false,
  "isCalculated": true,
  "expression": "subtotal * (1 + TAX_RATE)"
}
{
  "id": "calc_freeship",
  "name": "qualifiesForFreeShipping",
  "type": "boolean",
  "required": false,
  "isCalculated": true,
  "expression": "total >= FREE_SHIPPING_THRESHOLD"
}

System-Provided Global Constants

FormaMail automatically provides system-level global constants that are available in all templates without definition. These use double-underscore prefix/suffix (__NAME__) to distinguish them from user-defined variables.

Date/Time Constants

ConstantTypeDescriptionExample
__CURRENT_DATE__stringCurrent date (YYYY-MM-DD)"2025-12-02"
__CURRENT_YEAR__numberCurrent year2025
__CURRENT_MONTH__stringCurrent month name"December"
__CURRENT_MONTH_NUMBER__numberCurrent month (1-12)12
__CURRENT_DAY__numberCurrent day of month2
__CURRENT_DAY_NAME__stringCurrent day name"Monday"

User/Team Constants

ConstantTypeDescription
__CURRENT_USER_ID__stringCurrent user’s ID
__CURRENT_USER_NAME__stringCurrent user’s name
__CURRENT_USER_EMAIL__stringCurrent user’s email
__CURRENT_TEAM_ID__stringCurrent team’s ID
__CURRENT_TEAM_NAME__stringCurrent team’s name

Company Constants

These are automatically populated from your Company Settings:

ConstantTypeDescription
__COMPANY_NAME__stringCompany display name (trade name or legal name)
__COMPANY_LEGAL_NAME__stringCompany legal/registered name
__COMPANY_TRADE_NAME__stringCompany trade/brand name
__COMPANY_EMAIL__stringCompany contact email
__COMPANY_PHONE__stringCompany phone number
__COMPANY_WEBSITE__stringCompany website URL
__COMPANY_ADDRESS_LINE1__stringAddress line 1
__COMPANY_ADDRESS_LINE2__stringAddress line 2
__COMPANY_CITY__stringCity
__COMPANY_STATE__stringState/Province
__COMPANY_POSTAL_CODE__stringPostal/ZIP code
__COMPANY_COUNTRY__stringCountry
__COMPANY_GSTIN__stringGST Identification Number
__COMPANY_PAN__stringPAN Number

Template Constants

ConstantTypeDescription
__TEMPLATE_ID__stringCurrent template’s ID
__TEMPLATE_NAME__stringCurrent template’s name

Using System Constants in Templates

Copyright Footer:

© {{__CURRENT_YEAR__}} {{__COMPANY_NAME__}}. All rights reserved.

Company Address Block:

{{__COMPANY_NAME__}}
{{__COMPANY_ADDRESS_LINE1__}}
{{__COMPANY_CITY__}}, {{__COMPANY_STATE__}} {{__COMPANY_POSTAL_CODE__}}
{{__COMPANY_COUNTRY__}}

Contact Information:

Contact us:
Email: {{__COMPANY_EMAIL__}}
Phone: {{__COMPANY_PHONE__}}
Website: {{__COMPANY_WEBSITE__}}

Template Information:

Generated from template: {{__TEMPLATE_NAME__}}
Date: {{__CURRENT_DATE__}}

System constants are read-only and automatically populated at render time. You don’t need to define these in your template’s variables - they’re always available.

⚠️

Company constants require Company Settings to be configured. If not set up, these values will be empty.


Calculated Variables

Calculated variables are computed from other variables using JavaScript expressions. They’re perfect for deriving values without requiring the API caller to compute them. In the unified variable system, calculated variables have isCalculated: true and an expression field.

Defining Calculated Variables

Calculated variables are defined within the variables array with isCalculated: true:

{
  "variables": [
    {
      "id": "calc_total",
      "name": "totalWithTax",
      "type": "number",
      "required": false,
      "isCalculated": true,
      "expression": "subtotal * (1 + TAX_RATE)",
      "description": "Subtotal including tax"
    }
  ]
}

Required Properties:

  • name: Variable name (must follow JavaScript naming rules)
  • isCalculated: Must be true for calculated variables
  • expression: JavaScript expression
  • type: Expected return type (string, number, date, boolean, array, object)

Optional Properties:

  • required: Should be false (calculated values are computed, not provided)
  • description: Human-readable description

Naming Rules

Valid names:

  • Must start with letter or underscore: total, _temp, Total
  • Can contain letters, numbers, underscores: totalPrice, item_1, qty2

Invalid names:

  • Can’t start with number: 2total
  • Can’t use reserved keywords: if, for, while, function, class, etc.
  • Can’t use special characters: total-price, qty!, price@

Expression Syntax

Allowed Operations:

  1. Arithmetic:

    subtotal + tax          // Addition
    total - discount        // Subtraction
    quantity * unitPrice    // Multiplication
    total / quantity        // Division
    amount % 10             // Modulo
    price ** 2              // Exponentiation
  2. Comparison:

    quantity > 10           // Greater than
    price >= 100            // Greater than or equal
    stock < minimum         // Less than
    age <= 18               // Less than or equal
    status === 'active'     // Equality
    type !== 'guest'        // Inequality
  3. Logical:

    isPremium && isActive   // AND
    isNew || isFeatured     // OR
    !isExpired              // NOT
  4. Ternary (Conditional):

    status ? 'Active' : 'Inactive'
    quantity > 10 ? price * 0.9 : price
    isPremium ? 'Gold' : (isActive ? 'Silver' : 'Bronze')
  5. String Operations:

    firstName + ' ' + lastName              // Concatenation
    email.toLowerCase()                     // Method call
    message.substring(0, 100)               // Substring
    text.replace('old', 'new')              // Replace
    name.trim()                             // Trim whitespace
  6. Math Functions:

    Math.round(price)                       // Round
    Math.floor(value)                       // Floor
    Math.ceil(value)                        // Ceiling
    Math.abs(difference)                    // Absolute value
    Math.min(a, b, c)                       // Minimum
    Math.max(a, b, c)                       // Maximum
    Math.sqrt(number)                       // Square root
    Math.pow(base, exponent)                // Power
  7. String Methods:

    text.toUpperCase()                      // Upper case
    text.toLowerCase()                      // Lower case
    text.trim()                             // Trim
    text.substring(start, end)              // Substring
    text.slice(start, end)                  // Slice
    text.replace(search, replace)           // Replace
    text.split(',')                         // Split
    text.includes('substring')              // Includes
    text.startsWith('prefix')               // Starts with
    text.endsWith('suffix')                 // Ends with
  8. Array Operations:

    items.length                            // Array length
    items[0]                                // Array access
    items.join(', ')                        // Join
  9. Object Property Access:

    customer.name                           // Dot notation
    order['total']                          // Bracket notation
    user.address.city                       // Nested access

Important Security Restrictions:

Forbidden operations (for security):

eval()                    // Code execution
Function()                // Function constructor
require()                 // Module loading
import                    // ES6 imports
process                   // Node.js process
global                    // Global scope
window                    // Browser window
document                  // DOM access
localStorage              // Storage access
fetch()                   // Network requests

Expression Examples

Basic Math:

{
  "name": "total",
  "expression": "quantity * unitPrice"
}
 
{
  "name": "discount",
  "expression": "total * 0.10"
}
 
{
  "name": "finalPrice",
  "expression": "total - discount"
}

With Constants:

{
  "name": "totalWithTax",
  "expression": "total * (1 + TAX_RATE)"
}
 
{
  "name": "shippingCost",
  "expression": "total >= FREE_SHIPPING_THRESHOLD ? 0 : 9.99"
}

String Manipulation:

{
  "name": "fullName",
  "expression": "firstName + ' ' + lastName"
}
 
{
  "name": "displayEmail",
  "expression": "email.toLowerCase()"
}
 
{
  "name": "shortDescription",
  "expression": "description.substring(0, 100) + '...'"
}

Conditional Logic:

{
  "name": "tierLabel",
  "expression": "points >= 1000 ? 'Gold' : (points >= 500 ? 'Silver' : 'Bronze')"
}
 
{
  "name": "stockStatus",
  "expression": "inventory > 10 ? 'In Stock' : (inventory > 0 ? 'Low Stock' : 'Out of Stock')"
}

Date Operations:

{
  "name": "isExpired",
  "expression": "new Date(expiryDate) < new Date()"
}
 
{
  "name": "daysSinceOrder",
  "expression": "Math.floor((new Date() - new Date(orderDate)) / (1000 * 60 * 60 * 24))"
}

Complex Calculations:

{
  "name": "volumeDiscount",
  "expression": "quantity > 100 ? 0.20 : (quantity > 50 ? 0.15 : (quantity > 20 ? 0.10 : 0))"
}
 
{
  "name": "loyaltyPoints",
  "expression": "Math.floor(total * (isPremium ? 2 : 1) * POINTS_MULTIPLIER)"
}

Common Pitfalls

Don’t use Handlebars syntax in expressions:

{
  "name": "total",
  "expression": "{{quantity}} * {{unitPrice}}"  // WRONG
}

Use direct variable references:

{
  "name": "total",
  "expression": "quantity * unitPrice"  // CORRECT
}

Don’t use reserved keywords:

{
  "name": "for",  // WRONG - 'for' is reserved
  "expression": "quantity * 2"
}

Use descriptive non-reserved names:

{
  "name": "doubleQuantity",  // CORRECT
  "expression": "quantity * 2"
}

Using Variables in Templates

Email Templates

Text Components:

Hello {{firstName}},

Your order #{{order.id}} has been confirmed.
Total: {{total}}

Button Components:

{
  "type": "button",
  "props": {
    "text": "View Order",
    "url": "{{dashboardUrl}}/orders/{{order.id}}"
  }
}

Image Components:

{
  "type": "image",
  "props": {
    "src": "{{user.avatarUrl}}",
    "alt": "{{user.name}}"
  }
}

Conditional Components:

{
  "type": "conditional",
  "props": {
    "condition": "{{isPremium}}",
    "children": [
      {
        "type": "text",
        "props": { "content": "Thank you for being a Premium member!" }
      }
    ]
  }
}

Attachment Templates (PDF/Excel)

Variables work the same way in attachment templates:

Invoice #{{invoice.number}}
Date: {{invoice.date}}
Customer: {{customer.name}}

Total: {{total}}

Charts with Variables:

{
  "type": "chart",
  "props": {
    "chartType": "bar",
    "dataSource": "{{salesData}}",
    "xField": "month",
    "yField": "revenue"
  }
}

Data Tables & Columns

Tables are powerful components for displaying structured data. They work with array variables and support various column types, formulas, and formatting.

Basic Table Structure

{
  "type": "table",
  "props": {
    "dataSource": "{{orderItems}}",
    "columns": [
      { "header": "Item", "field": "name" },
      { "header": "Quantity", "field": "quantity" },
      { "header": "Price", "field": "unitPrice" },
      { "header": "Total", "field": "total" }
    ]
  }
}

Column Types

1. Simple Field Column

Display a field from the data source directly.

{
  "header": "Product Name",
  "field": "name",
  "width": "40%",
  "align": "left"
}

Properties:

  • header (required): Column header text
  • field (required): Property name from data source
  • width: Column width ("40%", "120px", 120)
  • align: Text alignment ("left", "center", "right")
  • format: Formatting options (see Formatting Options)

2. Calculated Column

Compute values using a JavaScript expression.

{
  "header": "Total",
  "isCalculated": true,
  "expression": "quantity * unitPrice",
  "format": {
    "type": "number",
    "preset": "CURRENCY",
    "currency": "USD",
    "decimals": 2
  }
}

Properties:

  • header (required): Column header text
  • isCalculated (required): Must be true
  • expression (required): JavaScript expression
  • format: Formatting options

Expression Context: In table column expressions, you can reference:

  • Other fields in the current row: quantity, unitPrice, discount
  • Constants: TAX_RATE, SHIPPING_COST

Examples:

// Subtotal with discount
{
  "header": "Subtotal",
  "isCalculated": true,
  "expression": "quantity * unitPrice * (1 - discount)"
}
 
// Tax amount
{
  "header": "Tax",
  "isCalculated": true,
  "expression": "subtotal * TAX_RATE"
}
 
// Conditional pricing
{
  "header": "Price",
  "isCalculated": true,
  "expression": "quantity > 10 ? unitPrice * 0.9 : unitPrice"
}
 
// Status label
{
  "header": "Status",
  "isCalculated": true,
  "expression": "stock > 10 ? 'Available' : (stock > 0 ? 'Low Stock' : 'Out of Stock')"
}

3. Calculated Column

Use JavaScript expressions for calculated columns. Works for both PDF and Excel attachments.

{
  "header": "Total",
  "formula": "quantity * unitPrice"
}

Properties:

  • header (required): Column header text
  • formula (required): Simple expression

Supported Operators: +, -, *, /, (), . (property access)

Examples:

// Basic calculation
{
  "header": "Total",
  "formula": "quantity * unitPrice"
}
 
// With property access
{
  "header": "Discounted Total",
  "formula": "item.quantity * item.price * (1 - item.discount)"
}

4. Component-Based Column (Email Templates Only)

Render custom components in table cells (buttons, images).

{
  "header": "Actions",
  "renderType": "component",
  "component": {
    "type": "button",
    "props": {
      "text": "View Details",
      "url": "https://example.com/products/{{id}}"
    },
    "contentField": "id"
  }
}

Supported Component Types:

  • Email templates: button, image, text
  • Attachment templates: image, text

Component Structure:

{
  "type": "button" | "image" | "text",
  "props": {
    // Component-specific props
  },
  "contentField": "fieldName"  // Field to use for dynamic content
}

Button Example:

{
  "header": "Action",
  "renderType": "component",
  "component": {
    "type": "button",
    "props": {
      "text": "Download",
      "url": "{{downloadUrl}}",
      "backgroundColor": "#007bff",
      "textColor": "#ffffff"
    },
    "contentField": "downloadUrl"
  }
}

Image Example:

{
  "header": "Product Image",
  "renderType": "component",
  "component": {
    "type": "image",
    "props": {
      "src": "{{imageUrl}}",
      "alt": "{{name}}",
      "width": 80,
      "height": 80
    },
    "contentField": "imageUrl"
  }
}

Column Formatting

Apply formatting to column values for better display.

Number Formatting

{
  "header": "Price",
  "field": "price",
  "format": {
    "type": "number",
    "preset": "CURRENCY",
    "currency": "USD",
    "decimals": 2
  }
}

Number Format Presets:

  • DECIMAL: 1234.56
  • CURRENCY: $1,234.56
  • PERCENT: 75.5%
  • CUSTOM: Custom format

Properties:

  • decimals: Number of decimal places (0-4)
  • currency: Currency code (USD, EUR, GBP, etc.)

Date Formatting

{
  "header": "Order Date",
  "field": "orderDate",
  "format": {
    "type": "date",
    "preset": "US"
  }
}

Date Format Presets:

  • ISO: 2025-01-15
  • US: 01/15/2025
  • EU: 15/01/2025
  • LONG: January 15, 2025
  • SHORT: Jan 15, 2025
  • CUSTOM: Custom format with tokens

Custom Format Example:

{
  "format": {
    "type": "date",
    "preset": "CUSTOM",
    "customFormat": "MMMM dd, yyyy"
  }
}

Column Styling

Customize column appearance with styling options.

{
  "header": "Status",
  "field": "status",
  "backgroundColor": "#28a745",
  "textColor": "#ffffff",
  "fontWeight": "bold",
  "align": "center"
}

Styling Properties:

  • backgroundColor: Background color (hex, rgb, color name)
  • textColor: Text color (hex, rgb, color name)
  • fontWeight: Font weight ("normal", "bold", "100"-"900")
  • align: Horizontal alignment ("left", "center", "right")

Table Styling

Style the entire table with header and row colors.

{
  "type": "table",
  "props": {
    "dataSource": "{{orderItems}}",
    "headerBackgroundColor": "#343a40",
    "headerTextColor": "#ffffff",
    "rowBackgroundColor": "#ffffff",
    "rowTextColor": "#212529",
    "borderColor": "#dee2e6",
    "columns": [...]
  }
}

Column Editor Interface

When editing tables in the designer, the Column Editor dialog provides a visual interface for configuring columns.

Column Editor Features:

  1. Add Column button - Add new columns
  2. Reorder - Drag handles to reorder columns
  3. Delete - Remove columns
  4. Column Properties Panel:
    • Header text input
    • Field selector dropdown (populated from data source)
    • Width input (pixels or percentage)
    • Alignment selector (left/center/right)
    • Format type selector
    • Format options (currency, decimals, date preset)

Calculated Column UI:

  • Toggle “Calculated Column” checkbox
  • Expression editor with syntax highlighting
  • Variable reference autocomplete (use row.fieldName to access row data)
  • Real-time expression validation
  • Preview with sample data

Formatting Options

Number Formatting

Decimal:

{
  "type": "number",
  "preset": "DECIMAL",
  "decimals": 2
}

Output: 1234.56

Currency:

{
  "type": "number",
  "preset": "CURRENCY",
  "currency": "USD",
  "decimals": 2
}

Output: $1,234.56

Percent:

{
  "type": "number",
  "preset": "PERCENT",
  "decimals": 1
}

Output: 75.5%

Custom:

{
  "type": "number",
  "preset": "CUSTOM",
  "customFormat": "#,##0.00"
}

Date Formatting

ISO:

{
  "type": "date",
  "preset": "ISO"
}

Output: 2025-01-15

US:

{
  "type": "date",
  "preset": "US"
}

Output: 01/15/2025

EU:

{
  "type": "date",
  "preset": "EU"
}

Output: 15/01/2025

Long:

{
  "type": "date",
  "preset": "LONG"
}

Output: January 15, 2025

Custom:

{
  "type": "date",
  "preset": "CUSTOM",
  "customFormat": "MMMM dd, yyyy 'at' hh:mm a"
}

Output: January 15, 2025 at 02:30 PM


Best Practices

Variable Naming

Do:

  • Use descriptive names: firstName, orderTotal, deliveryDate
  • Use camelCase for variables: userName, phoneNumber
  • Use UPPER_CASE for constants: TAX_RATE, COMPANY_NAME

Don’t:

  • Use vague names: x, temp, data1
  • Use reserved keywords: if, for, class
  • Mix naming conventions: FirstName, last_name, EMAIL

Variable Defaults

Always provide defaults for optional variables:

{
  "id": "var_greeting",
  "name": "greeting",
  "type": "string",
  "required": false,
  "defaultValue": "Hello"
}

Benefits:

  • Templates work even if variable is missing
  • Better user experience
  • Fewer API errors

Required Variables

Mark critical variables as required:

{
  "name": "email",
  "type": "string",
  "format": "email",
  "required": true
}

FormaMail validates required variables at:

  1. Template design time - Warns if required variable used but not defined
  2. API send time - Rejects request if required variable missing

Variable Documentation

Always add descriptions to variables:

{
  "name": "orderTotal",
  "type": "number",
  "required": true,
  "description": "Total order amount including tax and shipping",
  "format": {
    "type": "number",
    "preset": "CURRENCY",
    "currency": "USD",
    "decimals": 2
  }
}

Benefits:

  • Helps other developers understand usage
  • Shows in API documentation
  • Appears in designer tooltips

Calculated Variables

Use calculated variables to simplify API calls:

Without calculated variables (API must compute):

// API caller must compute these
{
  "subtotal": 100,
  "taxAmount": 8,
  "total": 108,
  "taxRate": 0.08
}

With calculated variables (computed automatically):

// API only provides:
{
  "subtotal": 100
}
 
// Template variables include:
{
  "id": "calc_tax",
  "name": "taxAmount",
  "type": "number",
  "required": false,
  "isCalculated": true,
  "expression": "subtotal * TAX_RATE"
},
{
  "id": "calc_total",
  "name": "total",
  "type": "number",
  "required": false,
  "isCalculated": true,
  "expression": "subtotal + taxAmount"
}

Nesting Depth

Limit object nesting to improve readability:

Good (2-3 levels):

{
  "customer": {
    "name": "John Doe",
    "address": {
      "city": "San Francisco",
      "state": "CA"
    }
  }
}

Avoid (5+ levels):

{
  "data": {
    "customer": {
      "profile": {
        "address": {
          "primary": {
            "location": {
              "city": "..."
            }
          }
        }
      }
    }
  }
}

Maximum Allowed: 5 levels


Expression Complexity

Keep expressions simple and readable:

Simple (preferred):

{
  "name": "discount",
  "expression": "total * 0.10"
}

Moderate (acceptable):

{
  "name": "shippingCost",
  "expression": "total >= FREE_SHIPPING_THRESHOLD ? 0 : 9.99"
}

Complex (avoid):

{
  "name": "complexCalculation",
  "expression": "((quantity > 100 ? price * 0.8 : (quantity > 50 ? price * 0.9 : price)) * quantity) * (isPremium ? 0.95 : 1) + (total > 1000 ? 0 : 15)"
}

If expression is too complex:

  1. Break into multiple calculated variables
  2. Compute on server and pass as regular variable
  3. Use constants to make expression more readable

Examples

Example 1: Order Confirmation Email

Variables:

{
  "variables": [
    {
      "id": "var_customer",
      "name": "customerName",
      "type": "string",
      "required": true
    },
    {
      "id": "var_orderno",
      "name": "orderNumber",
      "type": "string",
      "required": true
    },
    {
      "id": "var_orderdate",
      "name": "orderDate",
      "type": "date",
      "required": true,
      "format": {
        "type": "date",
        "preset": "LONG"
      }
    },
    {
      "id": "var_items",
      "name": "orderItems",
      "type": "array",
      "itemType": "object",
      "schema": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "name": { "type": "string", "required": true },
            "quantity": { "type": "number", "required": true },
            "unitPrice": { "type": "number", "required": true }
          }
        }
      }
    },
    {
      "id": "var_subtotal",
      "name": "subtotal",
      "type": "number",
      "required": true
    },
    {
      "id": "const_taxrate",
      "name": "TAX_RATE",
      "type": "number",
      "required": false,
      "isCalculated": false,
      "defaultValue": 0.08,
      "description": "Tax rate (constant)"
    },
    {
      "id": "const_company",
      "name": "COMPANY_NAME",
      "type": "string",
      "required": false,
      "isCalculated": false,
      "defaultValue": "Acme Store",
      "description": "Company name (constant)"
    },
    {
      "id": "calc_tax",
      "name": "taxAmount",
      "type": "number",
      "required": false,
      "isCalculated": true,
      "expression": "subtotal * TAX_RATE",
      "description": "Tax amount (calculated)"
    },
    {
      "id": "calc_total",
      "name": "total",
      "type": "number",
      "required": false,
      "isCalculated": true,
      "expression": "subtotal + taxAmount",
      "description": "Total with tax (calculated)"
    }
  ]
}

Template Usage:

Hello {{customerName}},

Your order #{{orderNumber}} has been confirmed!
Order Date: {{orderDate}}

Items:
{{#each orderItems}}
- {{name}}: {{quantity}} x ${{unitPrice}}
{{/each}}

Subtotal: ${{subtotal}}
Tax: ${{taxAmount}}
Total: ${{total}}

Thank you for shopping at {{COMPANY_NAME}}!

API Call:

POST /api/emails/send
{
  "templateId": "order-confirmation",
  "to": ["customer@example.com"],
  "variables": {
    "customerName": "John Doe",
    "orderNumber": "ORD-12345",
    "orderDate": "2025-01-15T10:30:00Z",
    "orderItems": [
      { "name": "Widget A", "quantity": 2, "unitPrice": 25.00 },
      { "name": "Widget B", "quantity": 1, "unitPrice": 50.00 }
    ],
    "subtotal": 100.00
  }
}

Example 2: Invoice PDF with Table

Variables:

{
  "variables": [
    {
      "id": "var_invno",
      "name": "invoiceNumber",
      "type": "string",
      "required": true
    },
    {
      "id": "var_invdate",
      "name": "invoiceDate",
      "type": "date",
      "required": true,
      "format": { "type": "date", "preset": "US" }
    },
    {
      "id": "var_customer",
      "name": "customer",
      "type": "object",
      "schema": {
        "type": "object",
        "properties": {
          "name": { "type": "string", "required": true },
          "email": { "type": "string", "format": "email", "required": true },
          "address": {
            "type": "object",
            "properties": {
              "street": { "type": "string" },
              "city": { "type": "string" },
              "state": { "type": "string" },
              "zip": { "type": "string" }
            }
          }
        }
      }
    },
    {
      "id": "var_items",
      "name": "lineItems",
      "type": "array",
      "schema": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "description": { "type": "string", "required": true },
            "quantity": { "type": "number", "required": true },
            "rate": { "type": "number", "required": true }
          }
        }
      }
    },
    {
      "id": "const_taxrate",
      "name": "TAX_RATE",
      "type": "number",
      "required": false,
      "isCalculated": false,
      "defaultValue": 0.08,
      "description": "Tax rate (constant)"
    },
    {
      "id": "const_company",
      "name": "COMPANY_NAME",
      "type": "string",
      "required": false,
      "isCalculated": false,
      "defaultValue": "Acme Corp",
      "description": "Company name (constant)"
    },
    {
      "id": "const_address",
      "name": "COMPANY_ADDRESS",
      "type": "string",
      "required": false,
      "isCalculated": false,
      "defaultValue": "123 Main St, San Francisco, CA 94105",
      "description": "Company address (constant)"
    }
  ]
}

Table Component:

{
  "type": "table",
  "props": {
    "dataSource": "{{lineItems}}",
    "headerBackgroundColor": "#343a40",
    "headerTextColor": "#ffffff",
    "columns": [
      {
        "header": "Description",
        "field": "description",
        "width": "50%",
        "align": "left"
      },
      {
        "header": "Qty",
        "field": "quantity",
        "width": "15%",
        "align": "center"
      },
      {
        "header": "Rate",
        "field": "rate",
        "width": "15%",
        "align": "right",
        "format": {
          "type": "number",
          "preset": "CURRENCY",
          "currency": "USD",
          "decimals": 2
        }
      },
      {
        "header": "Amount",
        "isCalculated": true,
        "expression": "quantity * rate",
        "width": "20%",
        "align": "right",
        "format": {
          "type": "number",
          "preset": "CURRENCY",
          "currency": "USD",
          "decimals": 2
        }
      }
    ]
  }
}

Example 3: Subscription Renewal Email with Conditionals

Variables:

{
  "variables": [
    {
      "id": "var_user",
      "name": "userName",
      "type": "string",
      "required": true
    },
    {
      "id": "var_tier",
      "name": "subscriptionTier",
      "type": "string",
      "required": true
    },
    {
      "id": "var_renewal",
      "name": "renewalDate",
      "type": "date",
      "required": true,
      "format": { "type": "date", "preset": "LONG" }
    },
    {
      "id": "var_premium",
      "name": "isPremium",
      "type": "boolean",
      "required": true
    },
    {
      "id": "var_monthly",
      "name": "monthlyPrice",
      "type": "number",
      "required": true
    },
    {
      "id": "var_annual",
      "name": "annualPrice",
      "type": "number",
      "required": true
    },
    {
      "id": "calc_savings",
      "name": "annualSavings",
      "type": "number",
      "required": false,
      "isCalculated": true,
      "expression": "(monthlyPrice * 12) - annualPrice",
      "description": "Annual savings amount (calculated)"
    },
    {
      "id": "calc_percent",
      "name": "savingsPercent",
      "type": "number",
      "required": false,
      "isCalculated": true,
      "expression": "Math.round((annualSavings / (monthlyPrice * 12)) * 100)",
      "description": "Savings percentage (calculated)"
    }
  ]
}

Template with Conditionals:

Hello {{userName}},

Your {{subscriptionTier}} subscription will renew on {{renewalDate}}.

{{#if isPremium}}
  Thank you for being a Premium member! You're getting the best value with:
  - Unlimited emails
  - Priority support
  - Advanced features
{{else}}
  Consider upgrading to Premium for more features!
{{/if}}

💰 Save {{savingsPercent}}% by switching to annual billing!
- Monthly: ${{monthlyPrice}}/month (${{monthlyPrice * 12}}/year)
- Annual: ${{annualPrice}}/year (save ${{annualSavings}}!)

[Upgrade to Annual Billing]


Need help? Contact support@formamail.com or check our FAQ