MailLandDocs

Templates

Create and manage reusable email templates with dynamic variable substitution

Create reusable email templates with dynamic variables. Templates store your email subject, HTML, and text content so you can send personalized emails without rebuilding content on every request.

Create a Template

POST https://api.mail.land/v1/templates
Create a template
curl -X POST https://api.mail.land/v1/templates \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Order Confirmation",
    "subject": "Your order #{{orderNumber}} is confirmed",
    "htmlContent": "<h1>Thanks, {{firstName}}!</h1><p>Order #{{orderNumber}} has been confirmed. Total: {{total}}</p>",
    "textContent": "Thanks, {{firstName}}! Order #{{orderNumber}} has been confirmed. Total: {{total}}",
    "categoryId": "k3v9q2m7xw4tpa"
  }'
Response: 201 Created
{
  "id": "t8x2m4q9zk7wpr",
  "name": "Order Confirmation",
  "subject": "Your order #{{orderNumber}} is confirmed",
  "htmlContent": "<h1>Thanks, {{firstName}}!</h1>...",
  "textContent": "Thanks, {{firstName}}!...",
  "categoryId": "k3v9q2m7xw4tpa",
  "categoryName": "Transactional",
  "categoryColor": "#3B82F6",
  "createdAt": "2026-02-17T10:00:00Z",
  "updatedAt": null
}

Validation Rules

FieldConstraint
nameRequired, max 200 characters
subjectRequired, max 500 characters
htmlContentRequired, max 1 MB
textContentOptional, max 1 MB
categoryIdOptional, must reference an existing category

Variable Syntax

Use double curly braces for variable substitution:

Template with variables
<h1>Hello {{firstName}},</h1>
<p>Your order #{{orderNumber}} shipped on {{shipDate}}.</p>
<p>Track your package: <a href="{{trackingUrl}}">{{trackingNumber}}</a></p>

Variables are replaced at send time. Pass values via the variables field in your send request:

Send with template variables
{
  "from": { "email": "[email protected]" },
  "to": [
    {
      "email": "[email protected]",
      "variables": {
        "firstName": "Jane",
        "orderNumber": "ORD-12345",
        "shipDate": "Feb 17, 2026",
        "trackingUrl": "https://track.example.com/ABC123",
        "trackingNumber": "ABC123"
      }
    }
  ],
  "templateId": "t8x2m4q9zk7wpr"
}

Variables work in both the subject and htmlContent/textContent. Every {{variable}} a template uses must have a value, either in the request's variables or in the recipient's own variables. If one has no value, MailLand does not send the message with the placeholder in it: POST /v1/transactional/send answers 422 TEMPLATE_VARIABLE_MISSING and names the missing variables, and POST /v1/transactional/send-batch skips that recipient and lists it in rejectedRecipients. Campaigns and automations skip the recipient too. Inline htmlContent sent without any variables is sent as written. Placeholders are exactly {{name}}, with no filters, conditions or loops. A template reports grammarVersion: "legacy-v1", the only grammar today; you can omit the field when you create or update a template.

Default Values

A template can carry a default value for each contact field it uses, so a subscriber who has no value for that field still receives the email. Set them with variableDefaults, an object of placeholder name to value, on create and update. It is returned when you get, list or duplicate a template.

Template with a default
{
  "name": "Welcome",
  "subject": "Hello {{firstName}}",
  "htmlContent": "<p>Hi {{firstName}}</p>",
  "variableDefaults": { "firstName": "there" }
}

Rules:

  • Defaults apply to campaign and automation sends only. A recipient's own non-blank value always wins over the default.
  • A field with neither a value nor a default is still refused: MailLand never sends a literal {{firstName}}.
  • Transactional API and SMTP sends are not affected. There, the values come from your request, and a missing one is refused as described above.
  • On update, leaving variableDefaults out keeps the stored defaults. Send {} to clear them.
  • Names follow the placeholder syntax. email and subscriberId are always supplied and cannot have a default. A value is one line of at most 200 characters, and a template holds at most 50 defaults.

In the dashboard editor, each placeholder has a default input that shows how many subscribers are missing that field, and saving warns you if a field with missing subscribers has no default.

Preview a Template

Preview a template with sample values auto-filled for detected variables:

GET https://api.mail.land/v1/templates/{id}/preview
Preview a template
curl https://api.mail.land/v1/templates/t8x2m4q9zk7wpr/preview \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
Preview response
{
  "id": "t8x2m4q9zk7wpr",
  "name": "Order Confirmation",
  "subject": "Your order #12345 is confirmed",
  "renderedHtml": "<h1>Thanks, John!</h1><p>Order #12345 has been confirmed.</p>",
  "renderedText": "Thanks, John! Order #12345 has been confirmed."
}

Duplicate a Template

Clone an existing template:

POST https://api.mail.land/v1/templates/{id}/duplicate
Duplicate a template
curl -X POST https://api.mail.land/v1/templates/t8x2m4q9zk7wpr/duplicate \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

The duplicate receives an auto-generated name: "Order Confirmation (Copy)". If a copy already exists, the name increments: "Order Confirmation (Copy 2)", etc.

Template Categories

Organize templates into categories with optional colors.

Create a category

POST https://api.mail.land/v1/templates/categories
Create a category
curl -X POST https://api.mail.land/v1/templates/categories \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Transactional",
    "description": "Order confirmations, receipts, and shipping notifications",
    "color": "#3B82F6"
  }'
FieldConstraint
nameRequired, 1-100 characters
descriptionOptional, max 500 characters
colorOptional, hex format (#3B82F6 or #38F)

List categories

GET https://api.mail.land/v1/templates/categories
List template categories
curl https://api.mail.land/v1/templates/categories \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
Categories response
{
  "categories": [
    {
      "id": "k3v9q2m7xw4tpa",
      "name": "Transactional",
      "description": "Order confirmations, receipts",
      "color": "#3B82F6",
      "templateCount": 12,
      "createdAt": "2026-01-15T10:00:00Z"
    }
  ],
  "totalCount": 3
}

List Templates

GET https://api.mail.land/v1/templates?limit=50
List templates
curl "https://api.mail.land/v1/templates?limit=50" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Filter by category:

GET https://api.mail.land/v1/templates?categoryId=k3v9q2m7xw4tpa
List templates by category
curl "https://api.mail.land/v1/templates?categoryId=k3v9q2m7xw4tpa" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
Template list response
{
  "items": [
    {
      "id": "t8x2m4q9zk7wpr",
      "name": "Order Confirmation",
      "subject": "Your order #{{orderNumber}} is confirmed",
      "htmlContent": "...",
      "categoryName": "Transactional",
      "categoryColor": "#3B82F6",
      "createdAt": "2026-02-17T10:00:00Z"
    }
  ],
  "nextCursor": "eyJ2IjoxLCJzIjoiY3JlYXRlZGF0OmRlc2MiLCJrIjoi...",
  "hasMore": true
}

Update a Template

PUT https://api.mail.land/v1/templates/{id}

All fields are required in the update request (this is a full replacement, not a partial update):

Update a template
curl -X PUT https://api.mail.land/v1/templates/t8x2m4q9zk7wpr \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Order Confirmation v2",
    "subject": "Order #{{orderNumber}} confirmed - {{companyName}}",
    "htmlContent": "<h1>Thanks, {{firstName}}!</h1><p>Updated template content.</p>",
    "textContent": "Thanks, {{firstName}}! Updated template content.",
    "categoryId": "k3v9q2m7xw4tpa"
  }'

Delete a Template

DELETE https://api.mail.land/v1/templates/{id}
Delete a template
curl -X DELETE https://api.mail.land/v1/templates/t8x2m4q9zk7wpr \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Returns 204 No Content on success. A deleted template can no longer be retrieved, listed, or used to send: a send request referencing it, immediate or scheduled, is refused with 400 TEMPLATE_NOT_FOUND, and a schedule whose template is deleted before its due time is refused the same way at send time.

Using Templates When Sending

Reference a template by ID in your send request. The template's subject and content are used, with variables substituted:

Send with template
{
  "from": { "email": "[email protected]" },
  "to": [
    {
      "email": "[email protected]",
      "variables": {
        "firstName": "Jane",
        "orderNumber": "ORD-12345"
      }
    }
  ],
  "templateId": "t8x2m4q9zk7wpr",
  "variables": {
    "companyName": "Acme Inc"
  }
}

When templateId is set, the template's rendered subject is always used; a subject in the send request is ignored.

Plan Limits

The number of templates you can create depends on your plan. If you reach the limit, creating a new template returns 403 PLAN_LIMIT_REACHED.

Next Steps

  • REST API: send emails with templates
  • Batch Sending: use templates with batch emails
  • Scheduled Emails: use templates via /v1/transactional/send with scheduledAt (the scheduled-emails endpoint has no templateId)

On this page