SendLit logoSendLit Docs
Developers

Transactional email

Send a single email through the REST API.

Use POST /emails for one API-triggered message such as a receipt, invitation, password reset, OTP, or security notification. Transactional sends appear in the dashboard’s Transactional log. They do not enroll a contact in a sequence or use a broadcast audience.

A non-empty workspace mailing address and a configured ESP are still required before SendLit accepts any send. This workspace policy is separate from message content: transactional templates do not render a marketing footer or unsubscribe link.

Create a transactional template

Create a template with purpose: "transactional", or duplicate one of the built-in transactional starters from the template hub. System templates are starters and cannot be sent directly; the resulting team-owned identifier starts with tpl_.

Template responses contain a server-computed requiredVariables list:

{
    "templateId": "tpl_...",
    "purpose": "transactional",
    "requiredVariables": ["customer.name", "otp"]
}

The template hub shows these paths and provides ready-to-copy JSON and curl examples.

Send with a template

Nested paths require nested JSON values:

curl -X POST 'http://localhost:5000/emails' \
  -H 'content-type: application/json' \
  -H 'x-sendlit-apikey: sl_live_...' \
  --data '{
    "to": "reader@example.com",
    "subject": "Your sign-in code",
    "templateId": "tpl_...",
    "variables": {
      "customer": { "name": "Rajat" },
      "otp": "345987"
    },
    "idempotencyKey": "sign-in-123"
  }'

templateId must identify a team-owned transactional template. A marketing template is rejected synchronously:

{
    "error": "template_not_transactional"
}

Required and optional variables

Every unguarded Liquid value must be present before SendLit creates a transactional row or queue job:

Your code is {{ otp }} for {{ customer.name }}.

Missing values return:

{
    "error": "missing_template_variables",
    "missingVariables": ["customer.name", "otp"]
}

Use a default filter or a direct if guard for intentionally optional content:

Hello {{ first_name | default: "there" }}.
{% if promotion %}
Your code: {{ promotion.code }}
{% endif %}

address, unsubscribe_link, and subscriber.* are owned by the marketing pipeline. They are rejected both in transactional template content and as top-level request variables.

Inline HTML

Provide exactly one of templateId or html. Inline html is sent verbatim: SendLit does not run Liquid over it, and variables cannot be supplied with it. This prevents legitimate {{ or {% content in caller-rendered HTML from being modified.

On this page