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.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 sendcurl -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
| 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 |
from | string | Override the from address (must use a verified domain) |
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. 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:
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:
| Code | Status | Description |
|---|---|---|
VALIDATION_ERROR | 400 | Invalid request, or the contact is unsubscribed or suppressed, the template is disabled, or the domain's sending is disabled/unhealthy |
CONTACT_NOT_FOUND | 404 | No contact exists for the to address |
TEMPLATE_NOT_FOUND | 404 | The specified template does not exist |
DOMAIN_NOT_FOUND | 404 | The sending domain has not been added |
DOMAIN_NOT_VERIFIED | 422 | The sending domain is not verified |
FORBIDDEN | 403 | Tenant 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:
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.