Шаблоны писем
Шаблон хранит тему и тело письма на сервере. В запросе отправки достаточно передать slug и переменные — рендеринг выполняется через Jinja2 в безопасной песочнице (без доступа к файловой системе и внешним I/O).
Шаблоны создаются и редактируются в дашборде: Проект → Шабло ны. У каждого шаблона есть уникальный в проекте slug и автоинкрементный version — он повышается при каждом обновлении.
Отправка по шаблону
- cURL
- Python
- TypeScript
curl -X POST https://api.mailinfra.ru/v1/emails \
-H "Authorization: Bearer mi_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"from": "no-reply@ваш-домен.ru",
"to": ["user@example.com"],
"template": "welcome",
"variables": {
"user_name": "Анна",
"app_name": "MyApp"
}
}'
httpx.post(
"https://api.mailinfra.ru/v1/emails",
headers={"Authorization": "Bearer mi_live_xxxxxxxx"},
json={
"from": "no-reply@ваш-домен.ru",
"to": ["user@example.com"],
"template": "welcome",
"variables": {"user_name": "Анна", "app_name": "MyApp"},
},
)
await fetch("https://api.mailinfra.ru/v1/emails", {
method: "POST",
headers: {
Authorization: "Bearer mi_live_xxxxxxxx",
"Content-Type": "application/json",
},
body: JSON.stringify({
from: "no-reply@ваш-домен.ru",
to: ["user@example.com"],
template: "welcome",
variables: { user_name: "Анна", app_name: "MyApp" },
}),
});
При использовании template поля subject, html, text берутся из шаблона — их можно не передавать. Можно при желании переопределить любое из них прямо в запросе.
Slug
- только строчные буквы, цифры,
-и_ - начинается с буквы или цифры
- до 100 символов
- уникален в пределах проекта
Примеры: welcome, password-reset, order_confirmation, email-verification.
Синтаксис Jinja2
Подстановка
{{ variable_name }}
{{ order.total | round(2) }}
{{ code | upper }}
Условия
{% if promo_code %}
<p>Промокод: <strong>{{ promo_code }}</strong></p>
{% endif %}
Циклы
<ul>
{% for item in order_items %}
<li>{{ item.name }} — {{ item.qty }} шт. × {{ item.price }} ₽</li>
{% endfor %}
</ul>
Экранирование HTML
В HTML-шаблонах переменные автоматически экранируются — это защита от XSS:
{# user_input = "<script>alert(1)</script>" #}
<p>{{ user_input }}</p>
{# рендерится как: <p><script>alert(1)</script></p> #}
Если значение действительно содержит доверенный HTML и нужно его вывести как есть — используйте фильтр | safe. Применяйте только к контенту, который контролируете сами.
{{ trusted_html | safe }}
Готовые шаблоны
Подтверждение email
subject: Подтвердите email — {{ app_name }}
html: <p>Ваш код: <strong>{{ code }}</strong>. Действителен {{ expires_min }} мин.</p>
text: Код: {{ code }}\nДействителен {{ expires_min }} мин.
Сброс пароля
subject: Сброс пароля
html: <p><a href="{{ reset_url }}">Сбросить пароль</a></p><p>Ссылка живёт {{ expires_hours }} ч.</p>
text: Сброс пароля: {{ reset_url }}\nСсылка живёт {{ expires_hours }} ч.
Подтверждение заказа
subject: Заказ #{{ order_number }} подтверждён
html: <h2>Заказ #{{ order_number }}</h2>
<table>
{% for item in items %}
<tr><td>{{ item.name }}</td><td>{{ item.qty }} шт.</td><td>{{ item.price }} ₽</td></tr>
{% endfor %}
</table>
<p><strong>Итого: {{ total }} ₽</strong></p>
Возможные ошибки при отправке
| Ситуация | HTTP | Код |
|---|---|---|
| Slug не найден при отправке | 422 | template_not_found |
| Не передана переменная, использованная в шаблоне | 422 | template_render_error |
| Синтаксическая ошибка в Jinja2 | 422 | template_invalid |
Управление шаблонами
CRUD шаблонов — часть Management API: операции идут на /v1/organizations/{organization_id}/projects/{project_id}/templates с сессионным токеном.
| Метод | Путь | Минимальная роль | Описание |
|---|---|---|---|
GET | /templates | VIEWER | Список шаблонов проекта |
POST | /templates | DEVELOPER | Создать шаблон |
GET | /templates/{id} | VIEWER | Один шаблон со всеми полями |
PATCH | /templates/{id} | DEVELOPER | Изменить шаблон, поднимает version |
DELETE | /templates/{id} | DEVELOPER | Удалить шаблон |
Создание
POST /v1/organizations/{organization_id}/projects/{project_id}/templates
Authorization: Bearer <session_token>
Content-Type: application/json
{
"name": "Приветственное письмо",
"slug": "welcome",
"subject_template": "Добро пожаловать, {{ user_name }}!",
"html_template": "<h1>Привет, {{ user_name }}!</h1><p>Аккаунт в {{ app_name }} создан.</p>",
"text_template": "Привет, {{ user_name }}!\n\nАккаунт в {{ app_name }} создан.",
"variables": {
"user_name": "Иван",
"app_name": "MyApp"
}
}
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
name | string | да | Человекочитаемое имя (1–200 символов) |
slug | string | да | См. правила slug; уникален в проекте |
subject_template | string | да | Тема, без переводов строк |
html_template | string | * | HTML-тело |
text_template | string | * | Текстовое тело |
variables | object | — | Подсказки и примеры значений переменных, видны в дашборде |
* Хотя бы одно из html_template / text_template должно быть указано.
variables — это документация, не значения по умолчаниюЭто пример того, какие переменные ждёт шаблон, и какие значения они принимают. При отправке передавайте все нужные переменные в теле запроса — содержимое variables шаблона не подставляется автоматически.
Ответ 201 Created:
{
"data": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"project_id": "...",
"name": "Приветственное письмо",
"slug": "welcome",
"subject_template": "Добро пожаловать, {{ user_name }}!",
"html_template": "...",
"text_template": "...",
"variables": { "user_name": "Иван", "app_name": "MyApp" },
"version": 1,
"created_at": "2026-05-12T09:00:00Z",
"updated_at": "2026-05-12T09:00:00Z"
}
}
Обновление и версионирование
PATCH /v1/.../templates/{template_id}
Authorization: Bearer <session_token>
Content-Type: application/json
{
"subject_template": "Привет, {{ user_name }} 👋",
"html_template": "<p>Новая верстка</p>"
}
При каждом успешном PATCH поле version увеличивается на 1 — это нужно, чтобы вы могли видеть в логах писем, какой версией шаблона отрендерено каждое письмо. Slug изменить нельзя — для смены slug создайте новый шаблон.
Ошибки управления
| Ситуация | HTTP | Код |
|---|---|---|
| Slug уже занят в проекте | 409 | conflict |
| Синтаксическая ошибка в Jinja2 при создании/обновлении | 422 | template_invalid |
| Шаблон не найден или не принадлежит проекту | 404 | not_found |