Skip to main content

Ошибки и лимиты

Это центральная страница со всеми кодами и лимитами Public API.

Формат ответа

Все ошибки приходят с JSON-телом единого формата:

{
"error": {
"code": "domain_not_verified",
"message": "Domain example.ru is not verified for this project",
"details": {}
}
}

Ошибки валидации (Pydantic, 422) кладут массив проблем в details:

{
"error": {
"code": "validation_error",
"message": "Request body is invalid",
"details": [
{
"loc": ["body", "to", 0],
"msg": "value is not a valid email address",
"type": "value_error.email"
}
]
}
}

HTTP-коды

КодЗначениеКогда
400Bad RequestБитый JSON или неверный формат запроса
401UnauthorizedAPI-ключ отсутствует, невалиден, отозван или истёк
402Payment RequiredПревышен лимит плана
403ForbiddenПрав ключа недостаточно (например, READ_ONLY пытается отправлять)
404Not FoundРесурс не найден или не принадлежит проекту/организации
409ConflictДублирующийся ресурс (slug шаблона, имя ключа, домен и т.д.)
422Unprocessable EntityОшибка валидации, домен не верифицирован, шаблон не найден, и т.д.
429Too Many RequestsПревышен rate limit
5xxServer ErrorВнутренняя ошибка — повторите запрос с backoff

Rate Limits

В каждый ответ добавляются заголовки:

X-RateLimit-Limit: 10
X-RateLimit-Remaining: 7
X-RateLimit-Reset: 1715461260
ЗаголовокОписание
X-RateLimit-LimitМаксимум запросов в секунду для текущего плана
X-RateLimit-RemainingОсталось в текущем окне
X-RateLimit-ResetUnix timestamp сброса счётчика

При 429 ответ дополнительно содержит Retry-After (секунды).

Лимиты по плану

ПараметрFREEPAIDPREMIUM
Писем в день10010 000100 000
Запросов в секунду110100
Доменов1550

Sandbox (mi_test_…) — 50 писем в день, не зависит от плана.

Лимиты на письмо

ПараметрЗначение
Получателей (to + cc + bcc)50
reply_to адресов10
Тегов10
Длина тега32 символа
Длина subject998 символов (RFC 5321)
Размер вложения10 МиБ
Вложений в письме10
Размер письма целиком25 МиБ
Писем в одном POST /v1/emails/batch100

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 — повтор не приведёт к дубликату письма. См. Идемпотентность.