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:
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 sendcurl -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
| Field | Type | Description |
|---|---|---|
to | string | Recipient email address |
template | string | Name of the template to use |
Optional fields
| Field | Type | Description |
|---|---|---|
variables | object | Custom variables available as {{ variables.* }} in the template |
ignoreUnsubscribe | boolean | Send 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:
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:
| Code | Description |
|---|---|
TEMPLATE_NOT_FOUND | The specified template does not exist |
CONTACT_SUPPRESSED | The contact is suppressed (bounced or complained) |
CONTACT_UNSUBSCRIBED | The contact has unsubscribed (and ignoreUnsubscribe is not set) |
DOMAIN_NOT_VERIFIED | The sending domain is not verified |
DOMAIN_DISABLED | The sending domain is disabled |
RATE_LIMITED | Too many requests — retry after the indicated period |
Handle errors in your application:
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.