Перейти к основному содержимому

Переход с SendGrid

У SendGrid тело запроса устроено иначе: получатели живут в personalizations, а контент — в массиве content. У нас плоская структура, поэтому основная работа при миграции — «развернуть» payload.

примечание

Соответствия составлены по публичному Mail Send v3 API SendGrid на момент написания. Сверьтесь с их актуальной документацией, если пользуетесь свежими возможностями.

Ключи и клиент

SendGridMailInfra
Префикс ключаSG.…mi_live_… (боевой), mi_test_… (sandbox)
Переменная окруженияSENDGRID_API_KEYMAILINFRA_API_KEY
База APIhttps://api.sendgrid.com/v3https://app.mailinfra.ru/v1
Эндпоинт отправкиPOST /mail/sendPOST /v1/emails
Тестовый режимmail_settings.sandbox_modeотдельный ключ mi_test_…

Отправка письма

SendGridMailInfraКомментарий
personalizations[].to[].emailtoу нас список адресов, одно письмо = одна персонализация
personalizations[].cc / bcccc / bccвсего до 50 адресов вместе с to
personalizations[].dynamic_template_datavariablesпеременные шаблона
personalizations[].substitutionsvariablesподстановок вида -key- нет, только именованные переменные
from.email, from.namefrom, from_name
reply_to.emailreply_toу нас список, до 10 адресов
subjectsubject
content[{type:"text/plain"}]text
content[{type:"text/html"}]html
template_id (d-…)templateу нас слаг шаблона: welcome, а не UUID
categoriestagsдо 10, до 32 символов
custom_argsheaders (X-…)произвольные пары уезжают заголовками письма
attachments[].content (base64)attachments[].content_base64, либо content байтами
attachments[].typeattachments[].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 — проверяйте поштучно.

Идентификатор письма и статусы

SendGridMailInfra
X-Message-Id в заголовке ответаresult.id в теле ответа
статусы через Event Webhook или Activity APIclient.emails.get(id), client.emails.list(...), client.emails.events(id)
sg_message_id в событияхdata.email.id в вебхуке

Вебхуки

Отличий три, и они существенные.

SendGridMailInfra
Формат теламассив событий в одном POSTровно одно событие на POST
ПодписьECDSA, заголовок X-Twilio-Email-Event-Webhook-SignatureHMAC-SHA256, X-MailInfra-Signature: t=…,v1=…
Имена событийdelivered, bounce, spamreport, open, click, droppedemail.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)

Соответствие событий: bounceemail.bounced, spamreportemail.complained, droppedemail.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-пулов под управлением клиента.