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.transactional({
  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/api/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
fromstringOverride the from address (must use a verified domain)
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. The recipient must already exist as a contact — if no contact matches the to address, the send is rejected with a CONTACT_NOT_FOUND error. Create the contact first (see the Contacts guide).

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.transactional({
  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 (HTTP 200) returns the message identifiers. There is no success flag — reaching this response means the email was accepted for delivery:

{
  "messageId": "0100018f2a3b4c5d-6e7f8a9b-0000",
  "status": "sent",
  "sendId": "snd_abc123def456"
}

messageId is the identifier assigned by SES; sendId is the send record in Kraiter, which you can use to track delivery status. See the Delivery guide for more on send lifecycle.

Error handling

Any failure is returned as an error response (and thrown as a MailerError by the SDK). Errors use a consistent envelope:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Contact is suppressed due to bounce or complaint. Use ignoreUnsubscribe for critical emails.",
    "details": {}
  }
}

The details object carries structured context for 4xx errors; for 5xx errors in production it is omitted.

Common outcomes from the send endpoint:

CodeStatusDescription
VALIDATION_ERROR400Invalid request, or the contact is unsubscribed or suppressed, the template is disabled, or the domain's sending is disabled/unhealthy
CONTACT_NOT_FOUND404No contact exists for the to address
TEMPLATE_NOT_FOUND404The specified template does not exist
DOMAIN_NOT_FOUND404The sending domain has not been added
DOMAIN_NOT_VERIFIED422The sending domain is not verified
FORBIDDEN403Tenant sending is paused, or (in sandbox) the address is not whitelisted

Suppressed and unsubscribed contacts both surface as VALIDATION_ERROR (400) with a descriptive message rather than distinct codes, so inspect statusCode and message:

SDK
try {
  await kraiter.send.transactional({
    to: 'alice@example.com',
    template: 'order-confirmation',
    variables: { orderId: 'ORD-12345' },
  });
} catch (error) {
  // The SDK throws MailerError on any failure — inspect statusCode/message
  if (error.statusCode === 404) {
    console.log('Contact or template not found');
  } else if (error.statusCode === 400) {
    console.log('Send rejected (suppressed, unsubscribed, or invalid):', error.message);
  } 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.