Переход с Resend
API устроены похоже, поэтому миграция — это в основном переименование полей и замена обработки ошибок. Ниже — что меняется в коде.
Соответствия составлены по публичному API Resend на момент написания. Если у вас свежая версия их SDK — сверьтесь с их документацией: поля могли измениться.
Ключи и клиент
| Resend | MailInfra | |
|---|---|---|
| Префикс ключа | re_… | mi_live_… (боевой), mi_test_… (sandbox) |
| Переменная окружения | RESEND_API_KEY | MAILINFRA_API_KEY |
| База API | https://api.resend.com | https://app.mailinfra.ru/v1 |
| Тестовый режим | отдельные тестовые адреса (delivered@resend.dev) | отдельный ключ mi_test_…, письмо получает статус SIMULATED |
// было
import { Resend } from "resend";
const resend = new Resend(process.env.RESEND_API_KEY);
// стало
import { MailInfra } from "mailinfra";
const client = new MailInfra(); // ключ из MAILINFRA_API_KEY
Отправка письма
Имена полей в основном совпадают:
| Resend | MailInfra | Комментарий |
|---|---|---|
from | from | адрес на подтверждённом домене |
to, cc, bcc | to, cc, bcc | строка или массив; всего до 50 адресов |
subject | subject | до 998 символов |
html, text | html, text | нужно хотя бы одно, либо template |
replyTo / reply_to | reply_to | до 10 адресов |
headers | headers | системные заголовки переопределять нельзя |
tags: [{name, value}] | tags: ["строка"] | разный формат — см. ниже |
attachments: [{filename, content}] | attachments: [{filename, content}] | у нас content — байты, которые SDK кодирует сам; либо готовый content_base64 |
attachments: [{path}] | — | скачивания по URL нет: приложите содержимое |
scheduled_at | — | отложенной отправки нет; планируйте на своей стороне |
React-компонент (react) | — | рендерите HTML сами либо используйте серверные шаблоны |
// было
const { data, error } = await resend.emails.send({
from: "hello@example.ru",
to: ["user@example.com"],
subject: "Привет",
html: "<p>Привет!</p>",
tags: [{ name: "category", value: "welcome" }],
});
if (error) {
console.error(error);
}
// стало
const result = await client.emails.send({
from: "hello@example.ru",
to: ["user@example.com"],
subject: "Привет",
html: "<p>Привет!</p>",
tags: ["welcome"], // просто строки, до 10, до 32 символов
});
Теги
У Resend тег — пара «имя/значение». У нас это плоские строки (до 10 тегов, до 32
символов каждый), по ним фильтруются письма в дашборде, в emails.list() и в вебхуках.
Пары приводите к строке так, как удобно искать: ["category:welcome"] или просто
["welcome"].
Ошибки вместо { data, error }
Главное различие в коде: Resend-SDK возвращает объект с полем error и ничего не
бросает. MailInfra бросает типизированные исключения.
// было
const { data, error } = await resend.emails.send({ /* … */ });
if (error) {
if (error.name === "validation_error") { /* … */ }
return;
}
use(data.id);
// стало
import { UnprocessableError, RateLimitError, MailInfraError } from "mailinfra";
try {
const result = await client.emails.send({ /* … */ });
use(result.id);
} catch (error) {
if (error instanceof UnprocessableError) {
for (const field of error.fields) {
console.error(field.field, field.message);
}
} else if (error instanceof RateLimitError) {
// SDK уже подождал Retry-After и повторил; сюда попадаем, если не помогло
} else if (error instanceof MailInfraError) {
console.error(error.code, error.requestId);
} else {
throw error;
}
}
Если хочется сохрани ть прежний стиль — оберните вызов один раз:
async function send(params) {
try {
return { data: await client.emails.send(params), error: null };
} catch (error) {
return { data: null, error };
}
}
Пакетная отправка
| Resend | MailInfra |
|---|---|
resend.batch.send([...]), до 100 писем | client.emails.sendBatch([...]), до 100 писем |
Разница в трактовке ответа: у нас 202 приходит даже при частичном отказе, и у каждого
элемента results может быть своё error. Проверяйте их поштучно —
подробнее.
Идемпотентность
Resend принимает заголовок Idempotency-Key; у нас он тоже поддерживается, и SDK
ставит его сам на каждую отправку. Если вы передавали ключ вручную — просто
передайте его вторым аргументом:
await client.emails.send({ /* … */ }, { idempotencyKey: "order_4242_paid" });
Статусы письма
| Resend | MailInfra |
|---|---|
GET /emails/{id} | client.emails.get(id) |
| — | client.emails.list({...}) — выборка по статусу, тегу, адресату, интервалу |
| — | client.emails.events(id) — полная история событий письма |
статусы sent, delivered, bounced, … | QUEUED, SENDING, SENT, DELIVERED, BOUNCED, COMPLAINED, FAILED, SIMULATED |
Вебхуки
Самое заметное отличие — подпись.
| Resend | MailInfra | |
|---|---|---|
| Подпись | Svix (svix-id, svix-timestamp, svix-signature) | X-MailInfra-Signature: t=…,v1=…, HMAC-SHA256 |
| Проверка |