Ошибки и лимиты
Это центральная страница со всеми кодами и лимитами Public API.
Формат ответа
Все ошибки приходят с JSON-телом единого формата:
{
"error": {
"code": "domain_not_verified",
"message": "Домен ещё не подтверждён.",
"details": {
"code": "domain_not_verified",
"domain": "example.ru"
}
},
"request_id": "3d6f0dce5d1b47bf8b3f9f98c9a70fd4"
}
Передавайте request_id поддержке: по нему можно найти конкретный запрос, не
раскрывая API-ключ или содержимое письма.
Ошибки схемы запроса (422) возвращают безопасный список проблем в
details.fields:
{
"error": {
"code": "request_validation_failed",
"message": "Проверьте заполненные поля.",
"details": {
"fields": [
{
"field": "to.0",
"message": "укажите корректный email",
"code": "value_error"
}
]
}
},
"request_id": "3d6f0dce5d1b47bf8b3f9f98c9a70fd4"
}
HTTP-коды
| Код | Значение | Когда |
|---|---|---|
400 | Bad Request | Некорректное содержимое после разбора схемы: base64, шаблон, значение header и т.д. |
401 | Unauthorized | API-ключ отсутствует, невалиден, отозван или истёк |
403 | Forbidden | Прав ключа недостаточно (например, READ_ONLY пытается отправлять) |
404 | Not Found | Ресурс не найден или не принадлежит проекту/организации |
409 | Conflict | Дублирующийся ресурс (slug шаблона, имя ключа, домен и т.д.) |
422 | Unprocessable Entity | Ошибка схемы или бизнес-проверки: зарезервированный header, домен не готов и т.д. |
429 | Too Many Requests | Превышен RPS или месячный объём тарифа |
5xx | Server Error | Внутренняя ошибка — повторите запрос с backoff |
Rate Limits
При 429 API возвращает заголовок Retry-After — минимальное число секунд до
безопасной повторной попытки:
Retry-After: 1
Для секундного ограничения обычно возвращается Retry-After: 1. При исчерпании
месячного объёма значение указывает время до начала следующего календарного месяца
по UTC. Заголовки X-RateLimit-* API сейчас не предоставляет.
Лимиты по плану
| Параметр | FREE | PAID | PREMIUM |
|---|---|---|---|
| Объём отправки в месяц | 100 | 10 000 | 100 000 |
| Доменов | 1 | 5 | 50 |
| Webhook endpoints | 3 | 10 | 1 000 |
Объём считается по получателям: одно письмо с тремя адресами в to/cc/bcc
расходует три единицы. Счётчик сбрасывается в начале календарного месяца по UTC.
Для live-отправки действует секундный лимит проекта, по умолчанию 10 запросов на
API-ключ. TEST-ключи создают записи со статусом SIMULATED: реальной доставки нет,
месячный объём и live RPS не расходуются.
Лимиты на письмо
| Параметр | Значение |
|---|---|
Получателей (to + cc + bcc) | 50 |
reply_to адресов | 10 |
| Тегов | 10 |
| Длина тега | 32 символа |
Длина subject | 998 символов (RFC 5321) |
| Размер вложения | 10 МиБ |
| Вложений в письме | 10 |
| Размер письма целиком | 25 МиБ |
Писем в одном POST /v1/emails/batch | 100 |
Retry с экспоненциальным backoff
Корректная стратегия для интеграции:
import time
import httpx
def send_with_retry(payload: dict, api_key: str, max_retries: int = 3) -> dict:
"""Отправляет письмо с retry на 429 и 5xx.
Args:
payload: Тело запроса POST /v1/emails.
api_key: Plain API-ключ (mi_live_... или mi_test_...).
max_retries: Максимум повторных попыток.
Returns:
Поле `data` успешного ответа.
Raises:
RuntimeError: Если все попытки исчерпаны.
"""
delay = 1.0
for _ in range(max_retries):
r = httpx.post(
"https://api.mailinfra.ru/v1/emails",
headers={"Authorization": f"Bearer {api_key}"},
json=payload,
)
if r.status_code == 429:
wait = int(r.headers.get("Retry-After", delay))
time.sleep(wait)
delay *= 2
continue
if r.status_code >= 500:
time.sleep(delay)
delay *= 2
continue
r.raise_for_status()
return r.json()["data"]
raise RuntimeError("Max retries exceeded")
Для безопасного retry на сетевых таймаутах (ConnectError, чтение прервалось) добавляйте заголовок Idempotency-Key — повтор не приведёт к дубликату письма. См. Идемпотентность.