Skip to main content

MailInfra SDK для Python

Официальный клиент MailInfra — email API для разработчиков.

pip install mailinfra
from mailinfra import MailInfra

client = MailInfra()  # ключ из MAILINFRA_API_KEY

result = client.emails.send(
    from_email="hello@mail.example.ru",
    to="user@example.com",
    subject="Добро пожаловать!",
    html="<p>Привет!</p>",
)
print(result.id, result.status)

Требования: Python 3.10+.

Что клиент делает за вас

Возможность Подробности
Идемпотентность На каждую отправку ставится Idempotency-Key, и он переиспользуется при повторах — обрыв сети не превращается в два письма
Повторы 429, 408/425, 5xx и таймауты повторяются с экспоненциальным backoff и half-jitter
Rate limit Retry-After уважается как есть; слишком долгий (>60 с) не блокирует поток, а приходит как RateLimitError.retry_after
Типизированные ошибки DomainNotVerifiedError, RateLimitError, UnprocessableError.fields и ещё ~20 классов
Локальная валидация Лимиты API (50 получателей, 10 тегов, 998 символов темы, 10 МиБ вложение) проверяются до сетевого вызова
Вебхуки mailinfra.webhooks.construct_event() — проверка HMAC-подписи и разбор события
Типы Модели pydantic v2, сгенерированные из OpenAPI-спеки MailInfra; пакет типизирован (py.typed)

Аутентификация

client = MailInfra(api_key="mi_live_…")  # явно
client = MailInfra()  # из MAILINFRA_API_KEY
client = MailInfra(require_environment="live")  # упасть, если подсунули тестовый ключ

mi_live_… отправляет письма по-настоящему. mi_test_… — sandbox: письмо сохраняется со статусом SIMULATED, не доставляется и не расходует месячный объём плана. Окружение ключа доступно как client.environment ("live" / "test").

Базовый URL меняется параметром base_url= или переменной MAILINFRA_BASE_URL.

Отправка

По шаблону

client.emails.send(
    from_email="no-reply@mail.example.ru",
    to=["anna@example.com", "boris@example.com"],
    template="welcome",  # тема берётся из шаблона
    variables={"user_name": "Анна", "app_name": "MyApp"},
    tags=["onboarding"],
)

С вложением

SDK сам закодирует байты в base64:

from mailinfra import Attachment

client.emails.send(
    from_email="billing@mail.example.ru",
    to="user@example.com",
    subject="Счёт за май",
    text="Счёт во вложении.",
    attachments=[
        Attachment(
            filename="invoice.pdf",
            content=open("invoice.pdf", "rb").read(),
            content_type="application/pdf",
        )
    ],
)

Свой ключ идемпотентности

Если у вас есть естественный идентификатор операции — передайте его: повторный вызов с тем же ключом не создаст второе письмо в течение 24 часов — столько бэкенд хранит ключ. Позже тот же ключ — уже новый запрос.

result = client.emails.send(
    from_email="no-reply@mail.example.ru",
    to="user@example.com",
    template="order_paid",
    variables={"order_id": 4242},
    idempotency_key="order_4242_paid",
)
if result.replayed:
    print("Письмо уже было отправлено раньше")

Пакетная отправка

До 100 писем одним запросом:

batch = client.emails.send_batch(
    [
        {
            "from_email": "no-reply@mail.example.ru",
            "to": "a@example.com",
            "template": "welcome",
            "variables": {"user_name": "Анна"},
        },
        {
            "from_email": "no-reply@mail.example.ru",
            "to": "b@example.com",
            "template": "welcome",
            "variables": {"user_name": "Борис"},
        },
    ]
)

print(batch.accepted_count, batch.failed_count)
for item in batch.results:
    if item.error:
        print(item.index, item.error.code, item.error.message)

Важно. Батч возвращает 202, даже если часть писем отклонена — проверяйте error у каждого элемента. Idempotency-Key этот эндпоинт не принимает, поэтому SDK не повторяет его после 5xx: повтор мог бы отправить часть писем дважды. Повторяется только 429 — единственный отказ, который случается до приёма писем. Обрыв соединения и таймаут батча не повторяются: сервер мог уже закоммитить часть писем, поэтому решение о повторе за вами.

Чтение

email = client.emails.get(result.id)
print(email.status, email.delivered_at, email.final_smtp_code)

for event in client.emails.events(result.id):
    print(event.timestamp, event.type, event.data)

recent = client.emails.list(status="BOUNCED", tag="onboarding", limit=50)

Фильтры list(): status, from_email, to_email, tag, from_date, to_date, limit (1..100). Enum-поля — подклассы str, поэтому email.status == "DELIVERED" работает наравне с email.status is EmailStatus.DELIVERED.

Ошибки

from mailinfra import (
    DomainNotVerifiedError,
    RateLimitError,
    UnprocessableError,
    MailInfraError,
)

try:
    client.emails.send(...)
except DomainNotVerifiedError:
    ...  # домен ещё не подтверждён в DNS
except UnprocessableError as exc:
    for field in exc.fields:  # [{"field": "to", "message": "…"}]
        print(field["field"], field["message"])
except RateLimitError as exc:
    print("повторить через", exc.retry_after)
except MailInfraError as exc:  # базовый класс всех остальных
    print(exc.code, exc.message, exc.request_id)

У любой ошибки есть .code, .message, .details, .status_code и .request_id — последний стоит логировать: с ним поддержка найдёт запрос в своих логах.

Конкретный класс всегда наследует класс своего HTTP-статуса: DomainNotVerifiedError — это и UnprocessableError (422), SandboxTrialExhaustedError — и PermissionDeniedError (403). Ловить можно на любом уровне: MailInfraError, класс статуса или конкретная причина.

Неизвестный код ошибки (новый на бэкенде) приходит как класс по HTTP-статусу, а не падает — обновление API не ломает старый SDK.

Настройка повторов

from mailinfra import MailInfra, RetryPolicy

client = MailInfra(
    max_retries=4,  # быстрый вариант
    timeout=10.0,
)

client = MailInfra(  # полный контроль
    retry_policy=RetryPolicy(
        max_retries=3,
        initial_backoff=0.5,
        backoff_factor=2.0,
        max_backoff=8.0,
        max_retry_after=60.0,  # выше этого не ждём, отдаём RateLimitError
    ),
)

Отключить авто-идемпотентность: MailInfra(auto_idempotency=False). Свой HTTP-клиент (прокси, mTLS, пул): MailInfra(http_client=httpx.Client(...)).

Асинхронный клиент

from mailinfra import AsyncMailInfra

async with AsyncMailInfra() as client:
    result = await client.emails.send(
        from_email="hello@mail.example.ru",
        to="user@example.com",
        subject="Привет",
        text="Привет!",
    )

API и поведение совпадают с синхронным клиентом.

Вебхуки

from fastapi import FastAPI, Request
from mailinfra import webhooks

app = FastAPI()


@app.post("/mailinfra/events")
async def handle(request: Request) -> dict[str, str]:
    event = webhooks.construct_event(
        body=await request.body(),  # именно сырые байты
        secret=os.environ["MAILINFRA_WEBHOOK_SECRET"],
        signature_header=request.headers["X-MailInfra-Signature"],
    )

    if event.type == "email.bounced":
        mark_bounced(event.email_id, event.data["event"])

    return {"status": "ok"}

construct_event проверяет HMAC-SHA256 подпись и её возраст (по умолчанию 5 минут, как на стороне MailInfra) и только потом разбирает JSON. Нужен просто флаг — есть webhooks.verify(...) -> bool.

Тело обязательно брать до разбора JSON: пересериализованный JSON даст другие байты и подпись не сойдётся.

Что пока не покрыто

v1 покрывает публичный API писем (/v1/emails*) — всё, что доступно по API-ключу проекта. Управление доменами, шаблонами, списком подавления, вебхуками и ключами — это Management API на сессионном токене пользователя; для машинной интеграции у него пока нет подходящего credential, поэтому в SDK этих ресурсов нет. Следите за CHANGELOG.

Ссылки

Лицензия

MIT

Metadata

Release files for mailinfra 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mailinfra 0.1.0
File Size Uploaded Provenance Actions
mailinfra-0.1.0.tar.gz 56.5 kB With provenance Details for mailinfra-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for mailinfra 0.1.0
File Interpreter ABI Platform Actions
mailinfra-0.1.0-py3-none-any.whl Python 3 none any Details for mailinfra-0.1.0-py3-none-any.whl

Total release size: 99.7 kB

Release files / mailinfra-0.1.0.tar.gz

Download URL mailinfra-0.1.0.tar.gz
Size 56.5 kB
Tags Source
SHA-256 checksum
How to use checksums
2ed4024ad6ed08194512892a2aaeb42e46c4a905b148bd71291f47b61cde1691
BLAKE2b-256 checksum
How to use checksums
512f8decedc85da625563e8dda18cf69bc70e8186bf67eda6460802032209f5b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 27, 2026.

Transparency log

Release files / mailinfra-0.1.0-py3-none-any.whl

Download URL mailinfra-0.1.0-py3-none-any.whl
Size 43.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b274b09db3713a484cd13371e9b5a4ffeeb6ca17f893d822a2e98c8810634d34
BLAKE2b-256 checksum
How to use checksums
5aeb7cdadff143a0c668d9e611f3faafe10a0341f35bd40a648d2cacf8b587fd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 27, 2026.

Transparency log

Security for mailinfra 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for mailinfra-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 27, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page