Kraiter
Guides

Transactional Email

Send one-off emails via the API with template rendering and variable injection.

Transactional emails are one-off messages triggered by a specific action or event — password resets, order confirmations, account notifications, and similar. Unlike sequence emails, transactional emails are sent immediately via the API and are not part of an automated workflow.

Sending a transactional email

Use the send endpoint to deliver a single email to a contact:

SDK
const send = await kraiter.send({
  to: 'alice@example.com',
  template: 'order-confirmation',
  variables: {
    orderId: 'ORD-12345',
    orderTotal: '£79.99',
    items: [
      { name: 'Widget Pro', quantity: 2, price: '£29.99' },
      { name: 'Gadget Plus', quantity: 1, price: '£20.01' },
    ],
  },
});

console.log(send.sendId); // Unique identifier for this send
cURL
curl -X POST https://api.kraiter.com/send \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "alice@example.com",
    "template": "order-confirmation",
    "variables": {
      "orderId": "ORD-12345",
      "orderTotal": "£79.99",
      "items": [
        { "name": "Widget Pro", "quantity": 2, "price": "£29.99" },
        { "name": "Gadget Plus", "quantity": 1, "price": "£20.01" }
      ]
    }
  }'

Required fields

FieldTypeDescription
tostringRecipient email address
templatestringName of the template to use

Optional fields

FieldTypeDescription
variablesobjectCustom variables available as {{ variables.* }} in the template
ignoreUnsubscribebooleanSend even if the contact has unsubscribed (default: false)

Template variable injection

When sending a transactional email, two sets of variables are available in the template:

Contact properties

Contact properties are automatically available under {{ contact.* }}:

{{ contact.email }}
{{ contact.properties.name }}
{{ contact.properties.plan }}

These are populated from the contact's stored data. If the contact does not exist, Kraiter auto-creates them with the provided email address.

Custom variables

Custom variables are passed in the variables field and are available under {{ variables.* }}:

{{ variables.orderId }}
{{ variables.orderTotal }}

You can pass any JSON-serialisable data as variables, including strings, numbers, booleans, arrays, and objects.

Using variables in templates

A typical transactional template combines both:

<mjml>
  <mj-body>
    <mj-section>
      <mj-column>
        <mj-text>
          Hi {{ contact.properties.name }},
        </mj-text>
        <mj-text>
          Your order {{ variables.orderId }} has been confirmed.
          Total: {{ variables.orderTotal }}
        </mj-text>
        <mj-table>
          {% for item in variables.items %}
          <tr>
            <td>{{ item.name }}</td>
            <td>{{ item.quantity }}</td>
            <td>{{ item.price }}</td>
          </tr>
          {% endfor %}
        </mj-table>
      </mj-column>
    </mj-section>
  </mj-body>
</mjml>

The ignoreUnsubscribe flag

By default, Kraiter will not send to contacts who have unsubscribed. For transactional emails that the recipient must receive regardless of their subscription preference (e.g. password resets, security alerts, legal notices), set ignoreUnsubscribe to true:

SDK
await kraiter.send({
  to: 'alice@example.com',
  template: 'password-reset',
  variables: {
    resetLink: 'https://app.example.com/reset/abc123',
  },
  ignoreUnsubscribe: true,
});

Important: Even with ignoreUnsubscribe: true, Kraiter will never send to a suppressed contact. Suppression is a technical block (the address bounced or filed a complaint), and sending to it would damage your sender reputation. See the Unsubscribe guide for more on the distinction.

Response format

A successful send returns the send details:

{
  "sendId": "snd_abc123def456",
  "to": "alice@example.com",
  "template": "order-confirmation",
  "status": "queued",
  "createdAt": "2025-06-15T10:30:00Z"
}

The sendId can be used to track the delivery status of the email. See the Delivery guide for more on send lifecycle.

Error handling

The send endpoint returns errors in a consistent format:

{
  "error": {
    "code": "CONTACT_SUPPRESSED",
    "message": "Cannot send to suppressed contact alice@example.com"
  }
}

Common error codes:

CodeDescription
TEMPLATE_NOT_FOUNDThe specified template does not exist
CONTACT_SUPPRESSEDThe contact is suppressed (bounced or complained)
CONTACT_UNSUBSCRIBEDThe contact has unsubscribed (and ignoreUnsubscribe is not set)
DOMAIN_NOT_VERIFIEDThe sending domain is not verified
DOMAIN_DISABLEDThe sending domain is disabled
RATE_LIMITEDToo many requests — retry after the indicated period

Handle errors in your application:

SDK
try {
  await kraiter.send({
    to: 'alice@example.com',
    template: 'order-confirmation',
    variables: { orderId: 'ORD-12345' },
  });
} catch (error) {
  if (error.code === 'CONTACT_SUPPRESSED') {
    console.log('Contact is suppressed, cannot send');
  } else if (error.code === 'TEMPLATE_NOT_FOUND') {
    console.log('Template does not exist');
  } else {
    throw error;
  }
}

Best practices

  • Use templates for all transactional emails. Do not hardcode email content in your application. Templates give you version history and the ability to update content without code changes.
  • Only use ignoreUnsubscribe for essential emails. Password resets, security alerts, and legal notices qualify. Marketing follow-ups do not.
  • Include meaningful variables. Pass enough context to make the email useful — order details, account information, action links.
  • Handle errors gracefully. Suppressed and unsubscribed contacts are expected conditions, not bugs. Handle them without crashing your application.