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

# Create a template

> Creates a template as a draft, or published at once with `"publish": true`. `{{ KEY }}` places a variable's value, HTML-escaped in `html`; `{{{ KEY }}}` places it as is (HTML only). The variables are taken from the placeholders when not given; given, each placeholder must be declared. At most 100 templates an application (Tawked's limit). A live key of the application with the explicit `email:manage`: a full-access key does not hold it.

Guide: [Templates](https://docs.tawked.com/email/templates.md)


## OpenAPI

````yaml https://tawked.com/openapi.json post /v1/email/templates
openapi: 3.1.0
info:
  title: Tawked API
  version: 2026-09
  summary: >-
    Tawked Verify: OTP verification for Saudi mobile numbers over SMS, in two
    calls. Tawked Notifications: WhatsApp template messages from the
    application's own number. Tawked Email: transactional e-mail from the
    application's own verified domain. Tawked Chat: the conversation menus a
    connected app adds beside the conversations of its connection's application,
    and the auto-attendant it sets up there. The Partner API for platforms that
    run Tawked Verify for their merchants.
  description: >-
    The public API of Tawked, a Saudi messaging platform: Tawked Verify
    (one-time codes), Tawked Notifications (WhatsApp templates from the
    application's own number), Tawked Email (transactional e-mail from the
    application's own domain) and the Partner API. Tawked Verify is an OTP
    verification API for Saudi numbers. One call sends a one-time code by SMS to
    a Saudi mobile number, and a second call checks it. SAR 0.09 per SMS code
    sent, prepaid; a send the network refuses is refunded. Authenticate with a
    Bearer API key from the dashboard (tk_live_ for production, tk_test_ for the
    sandbox, tk_partner_ for partners, tk_partner_test_ for the partner
    sandbox). Partners (tk_partner_) provision a Verify application per merchant
    under /v1/partner/applications and name it as application on every Verify
    call. Both /v1/... and /api/v1/... answer identically. Rate limit: 120
    requests per minute per key.
  termsOfService: https://tawked.com/en/terms
  license:
    name: Proprietary (Tawked terms of service)
    url: https://tawked.com/en/terms
  contact:
    name: Tawked support
    email: support@tawked.com
    url: https://tawked.com/en/contact
servers:
  - url: https://tawked.com
    description: Production (the sandbox is a tk_test_ key on the same host)
  - url: https://tawked.com/api
    description: The same API under /api/v1/..., kept for older integrations
security:
  - bearerKey: []
  - headerKey: []
tags:
  - name: Verify
    x-group: Verify
    description: Send and check one-time codes.
  - name: WhatsApp
    x-group: Notifications
    description: >-
      Tawked Notifications: send approved WhatsApp templates through the number
      connected to an application. An application in review or approved connects
      its own number from the dashboard.
  - name: Email
    x-group: Email
    description: >-
      Tawked Email: send transactional e-mail from an address on a verified
      domain of the application, to up to 50 recipients a message, in batches or
      scheduled, and read and list what was sent. Live keys only.
  - name: Chat
    x-group: Chat
    description: >-
      Tawked Chat: the conversation menus a connected app adds beside the
      conversations of its connection's application, and reads, changes and
      removes, with `chat:menus`; and the application's auto-attendant, which a
      connected app sets up, publishes and switches on or off, with
      `chat:attendant`. A connected app's key only; a business adds its own
      menus and builds its auto-attendant in the console.
  - name: Partner
    x-group: Partner API
    description: >-
      For platforms that run Tawked Verify for their merchants: provision a
      Verify application per merchant, keep its brand and verify settings
      current, switch it off and on, and read usage and the code log. Partner
      keys (tk_partner_) only; every Verify operation then takes `application`
      with the partner's own id for the application.
  - name: Webhooks
    x-group: Webhooks
    description: >-
      The events Tawked sends to your endpoint: an application's verification
      outcomes and message statuses, and a partner account's lifecycle, balance
      and opt-in verification events. Every delivery is signed the same way and
      retried the same way.
externalDocs:
  description: Developer docs, with code samples
  url: https://docs.tawked.com
paths:
  /v1/email/templates:
    post:
      tags:
        - Email
      summary: Create a template
      description: >-
        Creates a template as a draft, or published at once with `"publish":
        true`. `{{ KEY }}` places a variable's value, HTML-escaped in `html`;
        `{{{ KEY }}}` places it as is (HTML only). The variables are taken from
        the placeholders when not given; given, each placeholder must be
        declared. At most 100 templates an application (Tawked's limit). A live
        key of the application with the explicit `email:manage`: a full-access
        key does not hold it.
      operationId: email_template_create
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: >-
            Up to 128 characters. A retry with the same key and the same body
            within 24 hours answers what the first call answered and changes
            nothing again; the same key with another body answers `409
            idempotency_key_reused`.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  description: What the console calls it, up to 120 characters.
                alias:
                  type: string
                  description: >-
                    A name a send uses in place of the id: lowercase letters,
                    digits, `-` and `_`, up to 64, never shaped like a uuid.
                    Unique in the application.
                subject:
                  type: string
                  description: >-
                    The subject, with placeholders. A value placed in it loses
                    its line breaks.
                html:
                  type: string
                  description: >-
                    The HTML body, up to 512 KiB, with placeholders. `html` or
                    `text`, or both.
                text:
                  type: string
                  description: The plain-text body, with placeholders.
                from:
                  type: object
                  description: >-
                    `{"name", "address"}` on a domain of the application: the
                    sender when a send names none.
                reply_to:
                  type: array
                  description: Addresses replies go to, when a send names none.
                variables:
                  type: array
                  description: >-
                    `[{"key": "NAME", "fallback": "عميلنا"}]`: each variable
                    with the value used when a send gives none (null: the send
                    must give it). At most 50.
                publish:
                  type: boolean
                  description: '`true` publishes the draft at once.'
              required:
                - name
                - subject
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  alias:
                    type: string
                  name:
                    type: string
                  state:
                    type: string
                  published_version:
                    type: integer
                  published:
                    type: object
                    properties:
                      id:
                        type: string
                      number:
                        type: integer
                      state:
                        type: string
                      subject:
                        type: string
                      html:
                        type: string
                      text:
                        type:
                          - string
                          - 'null'
                      from:
                        type:
                          - string
                          - 'null'
                      reply_to:
                        type: array
                        items:
                          type:
                            - string
                            - 'null'
                      variables:
                        type: array
                        items:
                          type: object
                          properties:
                            key:
                              type: string
                            fallback:
                              type:
                                - string
                                - 'null'
                      placeholders:
                        type: array
                        items:
                          type: string
                      problems:
                        type: array
                        items:
                          type:
                            - string
                            - 'null'
                      published_at:
                        type: string
                        format: date-time
                      updated_at:
                        type: string
                        format: date-time
                  draft:
                    type:
                      - string
                      - 'null'
                  created_at:
                    type: string
                    format: date-time
                  updated_at:
                    type: string
                    format: date-time
              examples:
                created:
                  summary: Created
                  value:
                    id: 0199f1f0-2d3e-7f4a-8b5c-6d7e8f9a0b12
                    alias: order-shipped
                    name: تم شحن الطلب
                    state: published
                    published_version: 1
                    published:
                      id: 0199f200-3d4e-7f5a-8b6c-7d8e9f0a1b23
                      number: 1
                      state: published
                      subject: طلبك {{ ORDER }} في الطريق
                      html: <p>مرحبًا {{ NAME }}، طلبك {{ ORDER }} في الطريق.</p>
                      text: null
                      from: null
                      reply_to: []
                      variables:
                        - key: ORDER
                          fallback: null
                        - key: NAME
                          fallback: عميلنا
                      placeholders:
                        - ORDER
                        - NAME
                      problems: []
                      published_at: '2026-10-23T09:00:00+00:00'
                      updated_at: '2026-10-23T09:00:00+00:00'
                    draft: null
                    created_at: '2026-10-20T09:00:00+00:00'
                    updated_at: '2026-10-23T09:00:00+00:00'
        '400':
          description: >-
            `invalid_json`: The request body is not valid JSON.;
            `invalid_request`: start: `to` (and `phone`) both missing or blank.
            check: `id` or `code` missing.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Error'
                  - properties:
                      error:
                        enum:
                          - invalid_json
                          - invalid_request
        '401':
          description: >-
            `unauthorized`: Missing, unknown, revoked, or mode-mismatched key;
            or an unknown/suspended partner.; `key_expired`: The key's
            `expires_at` has passed. Checked right after the key is recognised,
            before the IP allowlist or scope.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Error'
                  - properties:
                      error:
                        enum:
                          - unauthorized
                          - key_expired
        '403':
          description: >-
            `live_key_required`: A test (tk_test_) key on a management endpoint:
            domains, suppressions, metrics and templates are the account's real
            objects, managed with the application's live key, and received
            e-mail is read with it too. A test key sends, reads and cancels
            messages in test mode; a partner's test key (tk_partner_test_) has
            no e-mail at all.; `application_key_required`: A partner key. Use
            the application's own key: partner keys manage client applications
            through the Partner API.; `account_not_active`: The application is
            not approved yet, the account is banned, or a partner disabled this
            application. All four causes share this one response.;
            `ip_not_allowed`: The caller's IP does not match the key's
            `ip_allowlist` (exact IPs and/or CIDR ranges, IPv4 and IPv6).;
            `insufficient_scope`: The key was not granted this call: a custom
            key without this product, or with a level below the call, such as a
            key that checks where the call sends. The response header
            `WWW-Authenticate` names the scope the call needs. Judged right
            after the key is recognised, before `account_not_active` and the
            request's shape. This operation needs `email:manage`.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Error'
                  - properties:
                      error:
                        enum:
                          - live_key_required
                          - application_key_required
                          - account_not_active
                          - ip_not_allowed
                          - insufficient_scope
          headers:
            WWW-Authenticate:
              description: >-
                Sent with `insufficient_scope` only: the scope this operation
                needs, which the key was not granted.
              schema:
                type: string
              example: Bearer error="insufficient_scope", scope="email:manage"
        '409':
          description: >-
            Alias taken / `email_access_required`: The account has not asked for
            Email yet: ask from the application's Email area in the console.;
            `email_access_pending`: The account's request for Email waits for
            Tawked's team.; `email_product_off`: The application's Email product
            is off: turn it on in the console.; `template_limit`: The
            application has 100 templates, the most it may have (Tawked's
            limit).; `template_alias_taken`: Another template of the application
            has this alias.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Error'
                  - properties:
                      error:
                        enum:
                          - email_access_required
                          - email_access_pending
                          - email_product_off
                          - template_limit
                          - template_alias_taken
              examples:
                alias_taken:
                  summary: Alias taken
                  value:
                    error: template_alias_taken
                    field: alias
        '422':
          description: >-
            Cannot publish / `template_alias_invalid`: The alias is not
            lowercase letters, digits, `-` and `_`, up to 64, or it is shaped
            like a uuid.; `template_invalid`: The draft cannot be published:
            `problems` lists each (`placeholder_undeclared`, `subject_empty`,
            `content_empty`, `html_too_large`, `from_not_application_domain`).
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Error'
                  - properties:
                      error:
                        enum:
                          - template_alias_invalid
                          - template_invalid
              examples:
                cannot_publish:
                  summary: Cannot publish
                  value:
                    error: template_invalid
                    problems:
                      - code: placeholder_undeclared
                        key: NAME
                        severity: error
        '429':
          description: >-
            `too_many_requests`: Per-key rate limit exceeded (default 120
            requests/minute, fixed 60s window; 1,200 on the e-mail endpoints).
            Checked before authentication. The `Retry-After` header gives the
            seconds to the next window.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Error'
                  - properties:
                      error:
                        enum:
                          - too_many_requests
        '500':
          description: >-
            `internal_error`: An unhandled server error. Quote the X-Request-Id
            header if you report it.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Error'
                  - properties:
                      error:
                        enum:
                          - internal_error
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            curl -X POST https://tawked.com/v1/email/templates \
              -H "Authorization: Bearer tk_live_xxxxxxxxxxxxxxxxxxxx" \
              -H "Content-Type: application/json" \
              -d '{
                "name": "تم شحن الطلب",
                "alias": "order-shipped",
                "subject": "طلبك {{ ORDER }} في الطريق",
                "html": "<p>مرحبًا {{ NAME }}، طلبك {{ ORDER }} في الطريق.</p>",
                "variables": [
                    {
                        "key": "ORDER",
                        "fallback": null
                    },
                    {
                        "key": "NAME",
                        "fallback": "عميلنا"
                    }
                ],
                "publish": true
            }'
        - lang: javascript
          label: Node.js
          source: |-
            const res = await fetch("https://tawked.com/v1/email/templates", {
              method: "POST",
              headers: {
                Authorization: "Bearer tk_live_xxxxxxxxxxxxxxxxxxxx",
                "Content-Type": "application/json",
              },
              body: JSON.stringify({
                  "name": "تم شحن الطلب",
                  "alias": "order-shipped",
                  "subject": "طلبك {{ ORDER }} في الطريق",
                  "html": "<p>مرحبًا {{ NAME }}، طلبك {{ ORDER }} في الطريق.</p>",
                  "variables": [
                      {
                          "key": "ORDER",
                          "fallback": null
                      },
                      {
                          "key": "NAME",
                          "fallback": "عميلنا"
                      }
                  ],
                  "publish": true
              }),
            });
            const data = await res.json();
        - lang: python
          label: Python
          source: |-
            import requests

            res = requests.post(
                "https://tawked.com/v1/email/templates",
                headers={"Authorization": "Bearer tk_live_xxxxxxxxxxxxxxxxxxxx"},
                json={
                    "name": "تم شحن الطلب",
                    "alias": "order-shipped",
                    "subject": "طلبك {{ ORDER }} في الطريق",
                    "html": "<p>مرحبًا {{ NAME }}، طلبك {{ ORDER }} في الطريق.</p>",
                    "variables": [
                        {
                            "key": "ORDER",
                            "fallback": None
                        },
                        {
                            "key": "NAME",
                            "fallback": "عميلنا"
                        }
                    ],
                    "publish": True
                },
            )
            data = res.json()
components:
  schemas:
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: string
          description: A stable machine-readable code; the docs list every one.
      additionalProperties: true
  securitySchemes:
    bearerKey:
      type: http
      scheme: bearer
      description: >-
        Authorization: Bearer <api key>. An application key (tk_live_, tk_test_)
        has full access, which covers every product and any product added later,
        or custom access, chosen when the key is created: one or more of the
        scopes `verify:check`, `verify:send` (includes `verify:check`),
        `notifications:read`, `notifications:send` (includes
        `notifications:read`), `email:read`, `email:send` (includes
        `email:read`), `email:manage` (includes `email:send`),
        `webhooks:manage`, `email:inbound`, `notifications:templates` (includes
        `notifications:read`), `chat:menus`, `chat:attendant`. No key made on
        the API keys page holds `notifications:templates`, full access included:
        the three template writes answer such a key 403 `insufficient_scope`. A
        connected app's key holds it. Nor `chat:menus`: the five conversation
        menu operations answer such a key 403 `insufficient_scope`, and a
        connected app's key holds it once the client approved it. Nor
        `chat:attendant`: the five auto-attendant operations answer such a key
        403 `insufficient_scope` too, and a connected app's key holds it once
        the client approved it. `email:manage` and `webhooks:manage` are held
        only by a key someone chose them for: a full-access key, old or new,
        never holds them. `email:inbound`, which reads, deletes and forwards
        received e-mail, is opt-in on every key, full access included: a key
        holds it only when it was turned on when the key was created. A call
        outside the key's access answers 403 `insufficient_scope` and names the
        scope it needs in the `WWW-Authenticate` response header. A partner key
        (tk_partner_) always has full access. Whatever its access, a key is a
        server-side secret.
    headerKey:
      type: apiKey
      in: header
      name: x-api-key
      description: >-
        The same API key, with the same access, as a header, for clients that
        cannot set Authorization.

````

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