> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lettr.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Create Template

> Create a new email template with HTML or Topol editor JSON content. Automatically creates and publishes a template version with extracted merge tags.

Creates a new email template with either HTML or JSON content. Provide `html` for raw HTML templates or `json` for visual editor (Topol) templates — these fields are mutually exclusive.

The template is automatically assigned a URL-friendly slug based on its name. You can optionally assign it to a specific project and folder using `project_id` and `folder_id`. If omitted, the template is added to your team's default project.

## Choosing the mode

`purpose` decides which of Lettr's two modes the template belongs to:

* **`transactional`** (default) — receipts, password resets, alerts; the templates you send single emails with
* **`campaign`** — marketing content, and the only kind a [campaign](/learn/campaigns/introduction) can pick

<Warning>
  **`purpose` cannot be changed afterwards.** It is not accepted by [Update Template](/api-reference/templates/update-template) — sending it there returns `200` and leaves the mode untouched. A newsletter created without `purpose` becomes a transactional template that campaigns cannot use, and the only remedy is the **Copy to Marketing** action in the app.

  If the template is going to an audience rather than to one person, pass `purpose: "campaign"` now.
</Warning>

Folders are mode-scoped, so `folder_id` must name a folder of the same mode as `purpose`; a folder of the other mode returns `422`. Leave `folder_id` out and the template goes to the main folder of its mode, so a `campaign` template appears in the marketing module and the campaign picker straight away. Use [List Folders](/api-reference/templates/list-folders) to find a folder — it is the only endpoint that returns folder IDs, and it reports each folder's mode.

## Validation

`json` must be a Topol editor design. JSON that isn't one is rejected with `422` instead of being saved as a template that breaks when opened in the editor.

## Preparation

Templates created from `json` are rendered by a background job, so the response comes back with `preparation_status: "pending"` and no HTML yet. Rendering in the background means large imports don't time out. It normally settles within seconds, and ends as `ready` or `failed` — poll [Get Template](/api-reference/templates/get-template) until `preparation_status` is `ready` before sending.

Templates created from raw `html` are `ready` immediately.


## OpenAPI

````yaml POST /templates
openapi: 3.1.0
info:
  title: Lettr API
  version: 1.0.0
  description: >-
    Lettr Email API - Send transactional emails with tracking, attachments, and
    personalization.
  contact:
    name: Lettr Support
    url: https://lettr.com
  license:
    name: Proprietary
    url: https://lettr.com/terms
servers:
  - url: https://app.lettr.com/api
    description: Production
security: []
tags:
  - name: Emails
    description: Email sending operations
  - name: Templates
    description: Email template management operations
  - name: Domains
    description: Domain management operations
  - name: Webhooks
    description: Webhook management operations
  - name: Audience
    description: 'Audience management: lists, contacts, topics, properties, and segments'
  - name: Campaigns
    description: >-
      Campaign operations: listing, stats, engagement events, and
      dispatch/scheduling
paths:
  /templates:
    post:
      tags:
        - Templates
      summary: Create Template
      description: >-
        Create a new email template with HTML or Topol editor JSON content.
        Automatically creates and publishes a template version with extracted
        merge tags.
      operationId: createTemplate
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateTemplateRequest'
            examples:
              with_json:
                summary: Create with Topol JSON
                value:
                  name: Welcome Email
                  json: >-
                    {"tagName":"mj-container","children":[{"tagName":"mj-text","content":"Hello
                    {{FIRST_NAME}}!"}]}
              with_html:
                summary: Create with custom HTML
                value:
                  name: Newsletter
                  html: >-
                    <html><body><p>Hello {{NAME}}, your order {{ORDER_ID}} is
                    ready!</p></body></html>
              with_project_and_folder:
                summary: Create in specific project and folder
                value:
                  name: Invoice Email
                  project_id: 5
                  folder_id: 10
                  html: >-
                    <html><body><p>Invoice for
                    {{CUSTOMER_NAME}}</p></body></html>
              marketing_template:
                summary: Create a marketing template for campaigns
                value:
                  name: October Newsletter
                  purpose: campaign
                  html: >-
                    <html><body><p>Hi {{FIRST_NAME}}, here is what shipped this
                    month.</p></body></html>
      responses:
        '201':
          description: Template created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateTemplateResponse'
              example:
                message: Template created successfully.
                data:
                  id: 123
                  name: Welcome Email
                  slug: welcome-email
                  project_id: 5
                  folder_id: 10
                  purpose: transactional
                  preparation_status: pending
                  active_version: 1
                  merge_tags:
                    - key: FIRST_NAME
                      required: true
                  created_at: '2026-01-28T12:00:00+00:00'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '404':
          description: Project or folder not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                project_not_found:
                  summary: Project not found
                  value:
                    message: >-
                      Project with ID '123' was not found or you don't have
                      access to it.
                    error_code: not_found
                no_default_project:
                  summary: No default project configured
                  value:
                    message: No default project is configured for this team.
                    error_code: not_found
                folder_not_found:
                  summary: Folder not found
                  value:
                    message: >-
                      Folder with ID '456' was not found in the specified
                      project.
                    error_code: not_found
                no_folders:
                  summary: No folders in project
                  value:
                    message: >-
                      Project with ID '123' has no folders. Please create a
                      folder first.
                    error_code: not_found
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
              examples:
                name_required:
                  summary: Name is required
                  value:
                    message: Validation failed.
                    error_code: validation_error
                    errors:
                      name:
                        - The name field is required.
                no_content:
                  summary: No content provided
                  value:
                    message: Validation failed.
                    error_code: validation_error
                    errors:
                      html:
                        - Either html or json content is required.
                both_content:
                  summary: Both html and json provided
                  value:
                    message: Validation failed.
                    error_code: validation_error
                    errors:
                      json:
                        - >-
                          The json and html fields are mutually exclusive.
                          Provide only one.
                invalid_json:
                  summary: Invalid JSON syntax
                  value:
                    message: Validation failed.
                    error_code: validation_error
                    errors:
                      json:
                        - The provided JSON is not valid.
                invalid_purpose:
                  summary: Unknown purpose
                  value:
                    message: Validation failed.
                    error_code: validation_error
                    errors:
                      purpose:
                        - The purpose must be either transactional or campaign.
                folder_purpose_mismatch:
                  summary: Folder belongs to the other module
                  value:
                    message: Validation failed.
                    error_code: validation_error
                    errors:
                      folder_id:
                        - >-
                          Folder 10 is a transactional folder and cannot hold a
                          campaign template.
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Failed to create template. Please try again later.
                error_code: send_error
        '503':
          $ref: '#/components/responses/ServiceUnavailableError'
      security:
        - bearerAuth: []
components:
  schemas:
    CreateTemplateRequest:
      type: object
      description: >-
        Request to create a new email template. Provide either `html` for custom
        HTML templates or `json` for Topol visual editor templates.
      required:
        - name
      properties:
        name:
          type: string
          maxLength: 255
          description: Name of the template
          example: Welcome Email
        project_id:
          type: integer
          minimum: 1
          description: >-
            Project ID to create the template in. If not provided, uses the
            team's default project.
          example: 5
        folder_id:
          type: integer
          minimum: 1
          description: >-
            Folder ID to create the template in. Must belong to the same module
            as `purpose`. If not provided, the first folder of that module is
            used, and one is created when the project has none.
          example: 10
        html:
          type: string
          description: >-
            HTML content for custom HTML templates. Required if `json` is not
            provided. Mutually exclusive with `json`.
          example: <html><body><p>Hello {{NAME}}!</p></body></html>
        json:
          type: string
          description: >-
            JSON content for Topol visual editor templates. Required if `html`
            is not provided. Mutually exclusive with `html`.
          example: >-
            {"tagName":"mj-container","children":[{"tagName":"mj-text","content":"Hello
            {{FIRST_NAME}}!"}]}
        purpose:
          type: string
          enum:
            - transactional
            - campaign
          default: transactional
          description: >-
            Module the template belongs to. `transactional` templates are the
            ones you send single emails with; `campaign` templates are the ones
            the campaign builder can pick. Defaults to `transactional`.
          example: campaign
    CreateTemplateResponse:
      type: object
      description: Response after successfully creating a template
      required:
        - message
        - data
      properties:
        message:
          type: string
          description: Human-readable response message
          example: Template created successfully.
        data:
          $ref: '#/components/schemas/CreatedTemplateView'
    ErrorResponse:
      type: object
      required:
        - message
        - error_code
      description: Error response for non-validation errors (400, 409, 500, 502).
      properties:
        message:
          type: string
          description: Human-readable error message
          example: The sender domain could not be determined from the email address.
        error_code:
          $ref: '#/components/schemas/ErrorCode'
    ValidationErrorResponse:
      type: object
      required:
        - message
        - error_code
        - errors
      properties:
        message:
          type: string
          description: Human-readable error message
          example: Validation failed.
        error_code:
          type: string
          description: Error code (always `validation_error` for 422 responses)
          const: validation_error
          example: validation_error
        errors:
          type: object
          description: Field-specific validation errors
          additionalProperties:
            type: array
            items:
              type: string
          example:
            from:
              - The sender email address is required.
            to:
              - At least one recipient email address is required.
    CreatedTemplateView:
      type: object
      description: View of a newly created template
      required:
        - id
        - name
        - slug
        - project_id
        - folder_id
        - purpose
        - preparation_status
        - active_version
        - merge_tags
        - created_at
      properties:
        id:
          type: integer
          description: Unique identifier for the template
          example: 123
        name:
          type: string
          description: Name of the template
          example: Welcome Email
        slug:
          type: string
          description: URL-friendly identifier for the template
          example: welcome-email
        project_id:
          type: integer
          description: ID of the project containing this template
          example: 5
        folder_id:
          type: integer
          description: ID of the folder containing this template
          example: 10
        purpose:
          type: string
          enum:
            - transactional
            - campaign
          description: >-
            Module the template belongs to. `transactional` templates are the
            ones you send single emails with; `campaign` templates are the ones
            the campaign builder can pick.
          example: campaign
        preparation_status:
          type: string
          enum:
            - pending
            - ready
            - failed
          description: >-
            How far the template has got through preparation. Creating or
            updating a template through the API defers image migration and HTML
            rendering to a background job, so the template is `pending` until
            that finishes. On a create with JSON there is no HTML at all yet -
            the editor works (it reads the JSON) but sending and the screenshot
            do not. On an update the previous render stays in place, so the
            template remains sendable, but it is still serving the *old* content
            until this reads `ready`. `failed` means preparation exhausted its
            retries and will not become ready on its own. A template created
            from raw HTML is `ready` immediately.
          example: ready
        active_version:
          type: integer
          description: Version number of the active template version
          example: 1
        merge_tags:
          type: array
          description: List of merge tags extracted from the template content
          items:
            $ref: '#/components/schemas/MergeTagView'
        created_at:
          type: string
          format: date-time
          description: When the template was created
          example: '2026-01-28T12:00:00+00:00'
    UnauthorizedResponse:
      type: object
      required:
        - message
      description: Error response for authentication failures (401).
      properties:
        message:
          type: string
          description: Human-readable error message
          example: API key is required.
    ErrorCode:
      type: string
      description: >-
        Error codes returned by the API.


        | Code | HTTP Status | Description |

        |------|-------------|-------------|

        | `validation_error` | 422 | Request validation failed. Check the
        `errors` object for field-specific messages. |

        | `invalid_domain` | 400 | The sender domain could not be determined
        from the email address. |

        | `unconfigured_domain` | 400 | The sender domain is not configured or
        approved for sending. |

        | `send_error` | 400, 500 | General error during email send preparation
        or domain registration. |

        | `retrieval_error` | 400, 500 | Failed to retrieve resources (e.g.,
        sandbox key without associated user, upstream error). |

        | `transmission_failed` | 502 | Email transmission to the upstream
        provider failed. |

        | `resource_already_exists` | 409 | The resource (e.g., domain) already
        exists. |

        | `not_found` | 404 | The specified resource was not found. |

        | `template_not_found` | 404 | The specified template, project, or
        template version was not found. |

        | `insufficient_scope` | 403 | The API key does not have the required
        scope for this endpoint. |

        | `schedule_cancellation_failed` | 409, 500 | The scheduled email can no
        longer be cancelled (already sent, already cancelled, or being injected
        right now), or the cancellation itself failed. |

        | `quota_exceeded` | 429 | Monthly sending quota exceeded. Upgrade your
        plan to continue sending. |

        | `daily_quota_exceeded` | 429 | Daily sending quota exceeded. Try again
        tomorrow. |

        | `campaign_not_sendable` | 422 | The campaign cannot be sent in its
        current state (not a draft, or missing subject/sender/content). |

        | `campaign_not_scheduled` | 422 | The campaign is not scheduled, so it
        cannot be unscheduled. |
      enum:
        - validation_error
        - idempotency_key_conflict
        - idempotency_in_progress
        - invalid_domain
        - unconfigured_domain
        - send_error
        - retrieval_error
        - transmission_failed
        - resource_already_exists
        - not_found
        - template_not_found
        - insufficient_scope
        - schedule_cancellation_failed
        - quota_exceeded
        - daily_quota_exceeded
        - campaign_not_sendable
        - campaign_not_scheduled
      example: invalid_domain
    MergeTagView:
      type: object
      description: >-
        A single merge tag. Loop merge tags (for iterating over arrays) include
        a 'children' property with nested field definitions.
      required:
        - key
        - required
      properties:
        key:
          type: string
          description: The merge tag key/name used in templates
          example: first_name
        required:
          type: boolean
          description: Whether this merge tag is required when sending emails
          example: true
        type:
          type: string
          description: The data type of the merge tag (only present for loop children)
          enum:
            - text
            - number
            - image
            - button
          example: text
        children:
          type: array
          description: >-
            Child merge tags for loop blocks. Each child represents a field
            available within the loop iteration.
          items:
            $ref: '#/components/schemas/MergeTagChild'
    MergeTagChild:
      type: object
      description: A child merge tag within a loop block
      required:
        - key
      properties:
        key:
          type: string
          description: The child merge tag key/name
          example: item_name
        type:
          type: string
          description: The data type of the child merge tag
          enum:
            - text
            - number
            - image
            - button
          example: text
  responses:
    UnauthorizedError:
      description: Unauthorized - Missing or invalid API key
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/UnauthorizedResponse'
          examples:
            missing_key:
              summary: Missing API key
              value:
                message: API key is required.
            invalid_key:
              summary: Invalid API key
              value:
                message: Invalid API key.
    ServiceUnavailableError:
      description: >-
        Service unavailable - the API key could not be verified, so the request
        was not processed. This does not mean the key is invalid; retry after
        the delay given in the Retry-After header.
      headers:
        Retry-After:
          description: Seconds to wait before retrying.
          schema:
            type: integer
            example: 5
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            message: Unable to verify your API key right now. Please retry shortly.
            error_code: auth_unavailable
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: API key for authentication

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.