Переход с SendGrid
У SendGrid тело запроса устроено иначе: получатели живут в personalizations, а
контент — в массиве content. У нас плоская структура, поэтому основная работа при
миграции — «развернуть» payload.
Соответствия составлены по публичному Mail Send v3 API SendGrid на момент написания. Сверьтесь с их актуальной документацией, если пользуетесь свежими возможностями.
Ключи и клиент
| SendGrid | MailInfra | |
|---|---|---|
| Префикс ключа | SG.… | mi_live_… (боевой), mi_test_… (sandbox) |
| Переменная окружения | SENDGRID_API_KEY | MAILINFRA_API_KEY |
| База API | https://api.sendgrid.com/v3 | https://app.mailinfra.ru/v1 |
| Эндпоинт отправки | POST /mail/send | POST /v1/emails |
| Тестовый режим | mail_settings.sandbox_mode | отдельный ключ mi_test_… |
Отправка письма
| SendGrid | MailInfra | Комментарий |
|---|---|---|
personalizations[].to[].email | to | у нас список адресов, одно письмо = одна персонализация |
personalizations[].cc / bcc | cc / bcc | всего до 50 адресов вместе с to |
personalizations[].dynamic_template_data | variables | переменные шаблона |
personalizations[].substitutions | variables | подстановок вида -key- нет, только именованные переменные |
from.email, from.name | from, from_name | |
reply_to.email | reply_to | у нас список, до 10 адресов |
subject | subject | |
content[{type:"text/plain"}] | text | |
content[{type:"text/html"}] | html | |
template_id (d-…) | template | у нас слаг шаблона: welcome, а не UUID |
categories | tags | до 10, до 32 символов |
custom_args | headers (X-…) | произвольные пары уезжают заголовками письма |
attachments[].content (base64) | attachments[].content_base64, либо content байтами | |
attachments[].type | attachments[].content_type | |
attachments[].disposition: "inline" | — | inline-вложений нет, картинки — ссылкой |
send_at | — | отложенной отправки нет |
asm (группы отписки) | — | отписка ведётся через список подавления |
ip_pool_name | — | пулами IP управляет MailInfra |
Python
# было
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail
message = Mail(
from_email="hello@example.ru",
to_emails="user@example.com",
subject="Привет",
html_content="<p>Привет!</p>",
)
response = SendGridAPIClient(os.environ["SENDGRID_API_KEY"]).send(message)
print(response.status_code, response.headers["X-Message-Id"])
# стало
from mailinfra import MailInfra
client = MailInfra()
result = client.emails.send(
from_email="hello@example.ru",
to="user@example.com",
subject="Привет",
html="<p>Привет!</p>",
)
print(result.status, result.id)
Шаблоны
# было
message = Mail(from_email="no-reply@example.ru", to_emails="user@example.com")
message.template_id = "d-0123456789abcdef"
message.dynamic_template_data = {"user_name": "Анна"}
# стало
client.emails.send(
from_email="no-reply@example.ru",
to="user@example.com",
template="welcome", # слаг из дашборда
variables={"user_name": "Анна"},
)
Шаблоны у нас хранятся в проекте и рендерятся на сервере (Jinja) —
как их заводить. Handlebars-конструкции SendGrid
({{#if}}, {{#each}}) нужно переписать на Jinja.
Node.js
// было
import sgMail from "@sendgrid/mail";
sgMail.setApiKey(process.env.SENDGRID_API_KEY);
await sgMail.send({
to: "user@example.com",
from: "hello@example.ru",
subject: "Привет",
html: "<p>Привет!</p>",
categories: ["welcome"],
});
// стало
import { MailInfra } from "mailinfra";
const client = new MailInfra();
await client.emails.send({
to: "user@example.com",
from: "hello@example.ru",
subject: "Привет",
html: "<p>Привет!</p>",
tags: ["welcome"],
});
Массовая отправка
У SendGrid «много писем» делается несколькими personalizations в одном запросе. У нас
для этого отдельный эндпоинт — батч до 100 писем:
await client.emails.sendBatch([
{ from: "no-reply@example.ru", to: "a@example.com", template: "welcome",
variables: { user_name: "Анна" } },
{ from: "no-reply@example.ru", to: "b@example.com", template: "welcome",
variables: { user_name: "Борис" } },
]);
Разница в трактовке ответа: 202 приходит даже при частичном отказе, у каждого
элемента results своё error — проверяйте поштучно.
Идентификатор письма и статусы
| SendGrid | MailInfra |
|---|---|
X-Message-Id в заголовке ответа | result.id в теле ответа |
| статусы через Event Webhook или Activity API | client.emails.get(id), client.emails.list(...), client.emails.events(id) |
sg_message_id в событиях | data.email.id в вебхуке |
Вебхуки
Отличий три, и они существенные.
| SendGrid | MailInfra | |
|---|---|---|
| Формат тела | массив событий в одном POST | ровно одно событие на POST |
| Подпись | ECDSA, заголовок X-Twilio-Email-Event-Webhook-Signature | HMAC-SHA256, X-MailInfra-Signature: t=…,v1=… |
| Имена событий | delivered, bounce, spamreport, open, click, dropped | email.delivered, email.bounced, email.complained, email.opened, email.clicked, email.failed |
Обработчик перестаёт быть циклом по массиву:
# было
for event in request.json():
handle(event["event"], event["sg_message_id"])
# стало
from mailinfra import webhooks
event = webhooks.construct_event(
body=await request.body(),
secret=os.environ["MAILINFRA_WEBHOOK_SECRET"],
signature_header=request.headers["X-MailInfra-Signature"],
)
handle(event.type, event.email_id)
Соответствие событий: bounce → email.bounced, spamreport → email.complained,
dropped → email.failed или пропуск получателя (адрес уже в списке подавления —
такие видны в skipped_recipients ответа отправки).
Список подавления
Bounce- и spam-адреса попадают в список подавления автоматически, как и в SendGrid. Экспортируйте свои bounce/unsubscribe-списки из SendGrid и импортируйте CSV в дашборде — иначе первые же рассылки повторно ударят по тем же адресам и испортят репутацию нового домена.
Переезд «одним переключателем»
SendGrid часто используют как SMTP-релей (smtp.sendgrid.net, логин apikey).
У нас ровно такой же режим: SMTP-отправка — хост
smtp.mailinfra.ru, логин apikey, пароль — ваш ключ. В этом случае код приложения
вообще не меняется: только хост и пароль в конфиге.
Чего у нас нет
- отложенной отправки (
send_at) и групп отписки (asm); - inline-вложений и подстановок вида
-key-; - маркетинговых кампаний по спискам контактов (Marketing Campaigns);
- выделенных IP-пулов под управлением клиента.