REST API подходит, когда нужны шаблоны, защита от повторной отправки и разбор ответа в коде. База: https://api.fmailer.ru. Все запросы — POST с телом в JSON.
Отдельного заголовка авторизации нет: логин и пароль токена передаются в теле запроса, в объекте auth. Используется тот же токен, что и для SMTP.
Тело запроса:
{
"recipient": "client@example.ru",
"subject": "Подтверждение заказа №4417",
"body": "<p>Заказ принят. Доставка — завтра до 18:00.</p>",
"text": "Заказ принят. Доставка — завтра до 18:00.",
"sender": "Магазин <noreply@mail.example.ru>",
"idempotency_key": "order-4417",
"mass_mail": false,
"tags": {
"stream": "order",
"lang": "ru"
},
"auth": {
"username": "<логин токена>",
"password": "<пароль токена>"
}
}| Поле | Описание |
|---|---|
recipient | адрес получателя (один) |
to, cc, bcc | массивы адресов вместо recipient; каждый адрес — отдельное письмо |
subject | тема письма |
body | HTML-версия письма |
text | текстовая версия; необязательна — если не передать, соберём её из HTML |
sender | отправитель; домен должен быть подтверждён |
idempotency_key | ключ защиты от повторной отправки |
mass_mail | true для рассылок — добавляет заголовки отписки в один клик |
attachments | массив файлов: filename и base64 в content |
tags | ваши метки: {"stream": "receipt"}; в письмо не попадают, но по ним можно фильтровать |
auth | логин и пароль токена |
Письмо всегда уходит как multipart/alternative: HTML-часть и текстовая. body — это HTML, text — текстовая альтернатива. Если text не передан, мы соберём его из HTML сами: уберём теги, раскодируем сущности, вынесем адреса ссылок после их текста.
Передавайте text, когда важны формулировки. Автоматическая версия не знает, что в вёрстке было оформлением, и не увидит фразы, которую вы написали бы для читателя без стилей. Обе части уходят в любом случае, а почтовые провайдеры сравнивают их между собой при оценке письма. Пустая строка в text не отправляется — сработает версия из HTML.
Для маркетинговых писем ставьте mass_mail: true. Это добавляет заголовки отписки в один клик (List-Unsubscribe и List-Unsubscribe-Post), которых Apple, Gmail и Yahoo требуют от массовых отправителей: без них рассылку могут отклонить или отправить в спам.
Без флага мы определяем тип письма сами, но это эвристика — она может принять рассылку за транзакционное письмо. Флаг работает в одну сторону: true помечает письмо рассылкой, а false просто оставляет решение за нами и не отключает заголовки отписки.
Вместо темы и текста передаётся tpl — slug шаблона, созданного в панели, язык и параметры для подстановки:
{
"recipient": "client@example.ru",
"tpl": "order-confirmed",
"lang": "ru",
"sender": "Магазин <noreply@mail.example.ru>",
"idempotency_key": "order-4417",
"mass_mail": false,
"params": {
"order": "4417",
"delivery": "завтра до 18:00"
},
"auth": {
"username": "<логин токена>",
"password": "<пароль токена>"
}
}Чтобы увидеть первое письмо, DNS не нужен. У каждого аккаунта есть песочница — учётные данные на общем домене, которым владеем мы: настоящий DKIM, настоящая доставка, тот же API и тот же SMTP. Интеграцию можно собрать и проверить, пока кто-то ещё ищет доступ к вашей DNS-панели.
Отправлять можно только на адрес своего аккаунта — и это не ограничение, а смысл: общий домен, доступный любому зарегистрировавшемуся, иначе был бы открытым релеем. Адрес аккаунта должен быть подтверждён, и есть небольшой суточный лимит. Всё остальное работает как обычно: те же вебхуки, тот же журнал событий, та же история доставки.
Когда понадобится писать кому-то ещё, добавьте свой домен и подтвердите его. В интеграции изменятся только учётные данные и адрес в From.
recipient — это один адрес. to, cc и bcc принимают массивы и являются той же самой формой во множественном числе: используйте либо одно написание, либо другое, но не оба сразу. Они работают во всех методах на этой странице, включая каждый элемент пакета.
Каждый адрес — отдельное письмо. Отправка на двух получателей в to, одного в cc и одного в bcc — это четыре записи в логе, четыре Message-ID, четыре вебхука доставки и четыре письма в счёт лимитов тарифа. Ровно так же всегда работала отправка по SMTP с несколькими получателями в одной транзакции, и именно это делает возможными отписки, повторы и статистику по каждому получателю отдельно.
Заголовки при этом одинаковые во всех копиях: весь список to, весь список cc и никогда — Bcc. Скрытый получатель — это адрес, которого нет ни в одном заголовке письма; прочитать его можно только в массиве emails ответа, поэтому там и есть поля recipient и kind. Адрес, названный дважды — в to и в cc, — это одно письмо: побеждает первое упоминание.
До 100 получателей на письмо — тот же потолок, что и на SMTP. Если задан idempotency_key, первый получатель сохраняет ваш ключ как есть, а каждая следующая копия получает производный (ваш-ключ#…), поэтому повтор запроса воспроизводит все копии, а не только первую. Производные ключи строим мы — вам их составлять не нужно.
attachments — массив файлов: filename и байты в base64 в поле content. content_type необязателен: мы определим его по имени файла, иначе поставим application/octet-stream. Переносы строк внутри content допустимы — именно так их расставляет любой кодировщик base64. Письмо уходит как multipart/mixed, внутри которого остаётся обычная пара HTML + текст, поэтому читатель получает и письмо, и файл.
До 20 файлов, по 10 МБ каждый и 10 МБ суммарно на письмо; всё тело запроса вместе с base64 — не больше 16 МБ. Помните, что base64 добавляет примерно треть к реальному размеру файла.
Вложение хранится и отправляется отдельно для каждого получателя — одна запись, одно письмо, одна копия файла. PDF на 5 МБ для двадцати человек — это 100 МБ, и на это произведение тоже есть потолок. Если упёрлись в него, дайте ссылку: письма такого размера всё равно отклоняет или обрезает изрядная часть почтовых служб.
Исполняемые типы файлов мы отклоняем — и по расширению, и по заявленному content_type: .exe, .msi, .jar, .js, .vbs, .bat, .scr, .lnk, .iso и остальные из опубликованного списка Gmail. Именно этот список и применяется к вашей почте на приёме, поэтому отказ при отправке — это разница между понятной ошибкой и отказом, который засчитается домену. Архивы разрешены: заглянуть внутрь мы не можем, а получатель — может.
Приложить файл по URL нельзя. Это означало бы, что наш сервер открывает соединение к хосту, который назвал держатель отправляющего токена, — и сделать это безопасно куда сложнее, чем кажется. Если файл у вас есть, отправьте его.
tags — объект ваших собственных меток на письме: {"stream": "password-reset", "lang": "en"}. Они отвечают на вопрос, на который не отвечает ничто другое в письме: к какому потоку оно относится. method говорит, как письмо отправлено, mass_mail — рассылка это или нет; ни то, ни другое не отличит сброс пароля от чека и от счёта, потому что вашу классификацию знаете только вы.
Имя метки — от 1 до 40 символов: латиница, цифры, подчёркивание и дефис. Значение — произвольный текст до 256 символов, двоеточия допустимы. До 10 меток на письмо.
В письмо метки не попадают. Это не заголовок: показывать вашу внутреннюю классификацию вашим же получателям, класть её под подпись DKIM и под оценку принимающей стороны — не то, о чём вы просили. Если метка нужна именно в письме, для этого остаются заголовки X-.
На нашей стороне их никто не читает. Доставка, throttling, репутация и антиспам к меткам слепы — и это намеренно: значение, которое выбирает отправитель, не должно влиять на то, как обходятся с его почтой.
Нужны они для чтения почты обратно:
GET /api/emails/emails/?tag=stream:password-reset и тот же параметр у /api/emails/logs/. Повторите параметр, чтобы добавить условие — ?tag=stream:receipt&tag=lang:en это и, а не или; ?tag=stream без значения означает «есть такая метка». tags в каждом вебхуке доставки — обработчик маршрутизирует событие, не запрашивая письмо. tags в отчёте аналитики и фильтр tags в запросе к нему, сужающий все числа на странице. Так и задаётся вопрос «как за неделю отработал поток password-reset». До 100 писем в одном запросе. Объект auth переезжает на верхний уровень и читается один раз на всю пачку, всё остальное — по письму, в массиве emails. Письмо в пачке — это либо обычное (subject и body), либо шаблонное (tpl), и их можно смешивать: рассылка на получателей с разными языками — это свой tpl и lang в каждом элементе.
{
"auth": {
"username": "<логин токена>",
"password": "<пароль токена>"
},
"emails": [
{
"recipient": "first@example.ru",
"subject": "Заказ принят",
"body": "<p>Доставка — завтра до 18:00.</p>",
"sender": "Магазин <noreply@mail.example.ru>",
"idempotency_key": "order-4417"
},
{
"recipient": "second@example.ru",
"tpl": "order-confirmed",
"lang": "ru",
"params": {
"order": "4418"
},
"sender": "Магазин <noreply@mail.example.ru>",
"idempotency_key": "order-4418"
}
]
}Ошибка в одном письме отклоняет всю пачку — HTTP 400 с индексом проблемного элемента. Так же отклоняется пачка, которая не помещается в остаток часового или месячного лимита тарифа: тот же Hourly limit exceeded / Monthly limit exceeded, что и при обычной отправке, вместо наполовину ушедшей рассылки.
Ответ плоский и упорядоченный: по одному элементу на письмо, в том порядке, в котором перечислены элементы и их получатели. В каждом — message_id, который потом придёт в вебхуках доставки. Элемент с cc или bcc — это несколько писем, поэтому и элементов ответа будет несколько: различайте их по recipient и kind. Потолок пачки считает письма, а не элементы — по той же причине, по которой их считает тариф.
{
"ok": true,
"accepted": 2,
"rejected": 0,
"emails": [
{
"ok": true,
"message_id": "<uuid@mail.example.ru>",
"uuid": "uuid",
"recipient": "first@example.ru",
"kind": "to",
"idempotency_key": "order-4417",
"replayed": false
},
{
"ok": true,
"message_id": "<uuid@mail.example.ru>",
"uuid": "uuid",
"recipient": "second@example.ru",
"kind": "to",
"idempotency_key": "order-4418",
"replayed": false
}
]
}Ставьте idempotency_key каждому письму. Это единственный способ безопасно повторить пачку: ключи привязаны к письму, поэтому повтор всего запроса после обрыва связи вернёт уже принятые письма (replayed: true, без второй отправки и без повторного расхода лимитов) и поставит в очередь только те, что не дошли. Два письма в одной пачке не могут иметь одинаковый ключ — иначе вместо двух писем молча ушло бы одно.
Успешная отправка:
{
"ok": true,
"emails": [
{
"ok": true,
"message_id": "<uuid@mail.example.ru>",
"uuid": "uuid",
"recipient": "client@example.ru",
"kind": "to",
"idempotency_key": "order-4417",
"replayed": false
}
]
}Ошибка — ok будет false, а причина в полях ошибки:
{
"ok": false,
"error_code": "код ошибки",
"error": "описание ошибки"
} Тот же токен, которым вы отправляете, умеет читать. Пять GET-методов, все ограничены одним доменом — тем, которому принадлежит токен. Параметра domain здесь нет и быть не может.
| Метод | На какой вопрос отвечает |
|---|---|
GET /external/emails/ | Что я отправил и чем каждое письмо закончилось. |
GET /external/emails/<uuid>/ | Одно письмо, вместе с ушедшим HTML. |
GET /external/emails/<uuid>/events/ | Всё, что с ним происходило, от старого к новому. |
GET /external/events/ | Весь поток событий домена — то, что опрашивают вместо вебхуков. |
GET /external/stats/ | Итоги, доли и разбивка по меткам за период. |
Аутентификация — HTTP Basic, тем же логином и паролем токена, что и SMTP:
curl https://api.fmailer.ru/external/emails/ \
-u "$TOKEN_USERNAME:$TOKEN_PASSWORD"
curl "https://api.fmailer.ru/external/stats/?start=2026-08-01&end=2026-08-28" \
-u "$TOKEN_USERNAME:$TOKEN_PASSWORD" Basic, а не bearer, потому что это ровно то, чем credential и является — логин и пароль. Отсюда и следствие: этот токен открывает /external/ и больше ничего. Это не ключ от аккаунта: домены, биллинг и команда ему недоступны.
Списки постраничные и принимают ожидаемые фильтры: status, method, message_id, recipient, subject, created_at_after / created_at_before и те же ?tag=. У /external/stats/ — start, end и снова ?tag=.
Два ограничения. Методы событий — платная возможность тарифа, та же, что показывает отложенные доставки в панели; без неё ответ HTTP 403, при этом список писем не ограничен. И история хранится ровно столько, сколько предусмотрено тарифом: старые записи удаляются, а /external/stats/ не отказывает в слишком длинном периоде, а обрезает его и возвращает использованный в window_days.
Ссылка отписки в письме теперь открывает центр настроек на вашем собственном хосте stats. — с вашим логотипом, названием компании и фирменным цветом. Ни страница, ни её адрес нас не называют. Особенно это важно для white-label доменов, где наш хост в теле письма был ровно той утечкой, ради предотвращения которой всё и делается.
Заведите темы — «Новости продукта», «Статусы заказов» — в настройках отписки домена и укажите одну при отправке: topic: "product-news". Получатель, отказавшийся от темы, перестаёт получать её и продолжает получать всё остальное, включая письма со сбросом пароля. Если тем нет, страница остаётся такой же, какой была: одна кнопка, отключающая всё.
Две особенности, которые стоит учесть:
topic останавливает только полная отписка. Именно так работают все письма, отправленные до сегодняшнего дня, и это не меняется. Slug темы нельзя изменить после создания — на него уже ссылаются записанные отказы. Чтобы вывести тему из обращения, отключите её: отказы, записанные по ней, продолжают действовать. А отписка в один клик, которую показывают Gmail и Apple, всегда означает отказ от всего: интерфейса у неё нет, и ничего более узкого она выразить не может.
Если включить для домена отслеживание ссылок, каждый <a href> в отправляемом HTML переписывается через ваш собственный хост stats.. Переход по такой ссылке записывается как клик, после чего идёт редирект на настоящий адрес.
По умолчанию выключено, в отличие от отслеживания открытий. Пиксель добавляет невидимую картинку, а это меняет то, что видит получатель при наведении на ссылку, и то, что запрашивает корпоративный сканер ссылок, — такое включают сами, а не за вас.
Ссылку отписки мы не переписываем никогда. Это не мелочь: шлюз безопасности, который заранее открывает все ссылки в письме, иначе отписал бы получателя до того, как тот его прочитал, — а поскольку подавление действует на адрес целиком, вместе с рассылками перестали бы приходить и письма со сбросом пароля. mailto:, tel: и якоря внутри страницы тоже не трогаем.
Машинный запрос — сканер, прокси приватности — получает редирект, но не считается кликом: используется та же классификация, что и для пикселя открытий. Клик при этом отмечает письмо как открытое: это более веское доказательство, чем пиксель, и так статистика открытий продолжает работать у клиентов, блокирующих картинки.
Что вы получаете:
clicked_at у письма — момент первого перехода.clicked в журнале на каждый клик, с адресом ссылки: повторы тоже, ведь вопрос звучит как «по какой ссылке и сколько раз». clicked с адресом в data. Одно ограничение: переписывание ссылок работает для писем, отправленных через этот API, а не через SMTP. По SMTP приходит уже готовый MIME, части которого могут быть закодированы, и правка адресов внутри них — верный способ испортить тело письма.
Когда спрашивают «а что вы мне вообще прислали», откройте письмо в панели и создайте ссылку на письмо: публичный адрес, по которому открывается ровно это письмо — его можно вставить в ответ клиенту.
Ссылку нельзя подобрать, у неё есть срок (по умолчанию 7 дней, максимум 30) и её можно отозвать в любой момент; запись о том, кто её создал и сколько раз открывали, остаётся и после отзыва. Сама страница нарочно простая: письмо, кому и когда оно ушло, и никакого брендирования.
Адрес ссылки — на вашем собственном хосте stats., том же, что использует пиксель и ссылка отписки, так что пересылаемая ссылка не несёт наших названий. Отсюда и единственное условие: если DNS-запись stats так и не опубликована, страницу негде обслуживать, и мы откажем в создании ссылки, а не выдадим неработающую.
Считайте живую ссылку публичной: пока не истёк срок, письмо целиком прочитает любой, у кого она есть. Это особенно важно для писем с одноразовыми ссылками и кодами — такие лучше отзывать сразу после разговора.
Два отказа стоит обрабатывать отдельно. При превышении часового или месячного объёма тарифа запрос отклоняется с HTTP 400 и текстом Hourly limit exceeded либо Monthly limit exceeded — такой запрос имеет смысл повторить с выдержкой. Если отправка с домена остановлена, ответ будет HTTP 403 с постоянным полем code = domain_banned и причиной в reason; повторы здесь не помогут.
Ни то ни другое не происходит, когда вы отправляете быстрее разгона домена: такие письма принимаются как обычно и уходят чуть позже. См. лимиты отправки и защиту от спама.
idempotency_key — ваш собственный идентификатор события, например номер заказа. Повторный запрос с тем же ключом не создаёт второе письмо, поэтому ретраи при обрыве связи безопасны. Ключ должен быть уникальным для каждого письма, которое действительно нужно отправить.