Транзакционные и массовые письма из вашего кода плюс верификация адресов. Все запросы — JSON поверх HTTPS, ответы — JSON.
Базовый адрес всех методов:
https://api.mailermail.ru/v1
Контракт построен на привычной модели транзакционных Email API (from/to/subject/html/text/attachments) — миграция с типового провайдера сводится к смене хоста и токена. Машиночитаемая спецификация — openapi.yaml (OpenAPI 3.0), интерактивная версия — Swagger UI.
Метод GET /health не требует токена и отвечает {"ok":true} — удобно для мониторинга.
curl https://api.mailermail.ru/v1/health{
"ok": true,
"ts": "2026-08-03T12:00:00+03:00"
}Все методы, кроме /health и /unsubscribe, требуют токен. Передайте его в одном из заголовков:
Api-Token: mm_live_…Authorization: Bearer mm_live_…Токены выпускаются в кабинете (Настройки → API). Каждый токен несёт scope:
verify — методы верификации (/verify, /verify/batch);send — всё остальное (отправка, шаблоны, стоп-лист, логи).Нет нужного scope → 403.
Токены с префиксом mm_test_ — тестовые: /send и /batch проходят весь конвейер (валидацию, проверку стоп-листа, запись в лог с пометкой sandbox), но письма физически не отправляются. Ответ содержит "sandbox": true.
curl https://api.mailermail.ru/v1/stats \
-H "Api-Token: mm_live_9f3k…"Успех — HTTP 2xx и "success": true. Ошибка — соответствующий код и тело {"success": false, "errors": ["…"]}. Массив errors содержит человекочитаемые сообщения.
| Код | Значение |
|---|---|
| 200 | Запрос обработан. Для /send частичные неудачи по получателям — в skipped/failed, код всё равно 200. |
| 400 | Некорректное тело (не JSON-объект). |
| 401 | Токен отсутствует, некорректен, неизвестен или отозван. |
| 403 | У токена нет нужного scope. |
| 404 | Ресурс не найден (эндпоинт, шаблон, сообщение). |
| 409 | Конфликт Idempotency-Key: такой ключ уже выполняется или использован для другого метода. |
| 413 | Тело или вложения превышают лимит. |
| 422 | Ошибка валидации полей. |
| 429 | Превышен лимит запросов. Заголовок Retry-After — секунд до сброса. |
| 502 | Полный провал отправки (инфраструктура) — ретрай уместен. |
| 503 | БД недоступна или часть API ещё не развёрнута. |
{
"success": false,
"errors": [
"Field 'to' (non-empty array) is required."
]
}На каждый токен — 120 запросов в минуту. При превышении метод отвечает 429 с заголовком Retry-After (секунд до конца текущего минутного окна).
Отдельные лимиты содержимого:
/send — до 50 получателей, вложения ≤10 МБ суммарно, тело ≤15 МБ;/batch — до 500 request'ов, до 1000 писем суммарно;/verify/batch — до 100 адресов.{
"success": false,
"errors": [
"Rate limit exceeded: 120 requests/minute."
]
}Методы /send и /batch принимают заголовок Idempotency-Key (произвольная строка ≤200 символов). Это защищает от двойной отправки при сетевых ретраях.
Idempotency-Replayed: true.409.5xx не кэшируются: ретрай с тем же ключом действительно повторит отправку.curl -X POST https://api.mailermail.ru/v1/send \
-H "Api-Token: mm_live_9f3k…" \
-H "Idempotency-Key: order-4211-confirm" \
-H "Content-Type: application/json" \
-d '{ … }'Синхронная транзакционная отправка: письмо уходит за секунды, у каждого получателя — свой Message-ID. Требует scope send. Домен отправителя должен быть подтверждён в аккаунте (иначе 422) — он же даёт DKIM-подпись.
Задайте subject + html и/или text, либо template_id (тогда тему и тело берёт шаблон — они взаимоисключимы). Переменные {{var}} подставляются из template_variables и to[].substitutions.
| Поле | Тип | Обяз. | Описание |
|---|---|---|---|
from | object | да | Отправитель: {email, name?}. Домен — verified. |
to | array | да | Получатели {email, name?, substitutions?}, ≤50. |
reply_to | object | нет | Адрес для ответа {email, name?}. |
subject | string | — | Тема, ≤255. Обязательна, когда шаблон не задан. |
html | string | — | HTML-тело. Нужно html и/или text. |
text | string | — | Текстовое тело. Если не задано — генерируется из HTML. |
category | string | нет | Метка для группировки в статистике, ≤255. |
attachments | array | нет | {content(base64), filename, type?, disposition?, content_id?}, ≤10 МБ суммарно. |
template_id | string | нет | Шаблон по UUID или по слагу (взаимоисключим с subject/html/text и с template_uuid). Отправляется опубликованная версия шаблона. |
template_uuid | string | нет | То же, но строго UUID. Поле оставлено для действующих интеграций. |
template_variables | object | нет | Глобальные переменные {{var}} (скаляры). |
options | object | нет | list_unsubscribe (bool), unsubscribe_url (https). |
curl -X POST https://api.mailermail.ru/v1/send \
-H "Api-Token: mm_live_9f3k…" \
-H "Content-Type: application/json" \
-d '{
"from": {"email": "no-reply@shop.ru", "name": "Shop"},
"to": [{"email": "ivan@example.ru"}],
"subject": "Заказ №4211 собран",
"html": "<h1>Спасибо за заказ!</h1>"
}'{
"success": true,
"message_ids": ["a1b2c3d4-…"]
}{
"success": true,
"message_ids": [],
"skipped": [
{"email": "old@dead.ru", "reason": "known_bounce"}
]
}Постановка партии писем в очередь: message_ids выдаются сразу, реально отправляет фоновый воркер (латентность до ~5 минут). Объект base задаёт общие поля, поля каждого request их перекрывают. Вложения не поддерживаются — используйте /send.
HTTP всегда 200 (кроме структурных ошибок тела): пер-письмо статусы — в массиве responses[] в порядке requests.
| Поле | Тип | Обяз. | Описание |
|---|---|---|---|
base | object | нет | Общие поля письма (как в /send, без attachments). |
requests | array | да | Массив писем (поля /send), ≤500; суммарно ≤1000 писем. |
options.send_at | string | нет | Отложить: YYYY-MM-DD HH:MM:SS UTC, окно ≤72 ч. |
options.skip_unsubscribe | bool | нет | Отключить List-Unsubscribe (в bulk он включён по умолчанию). |
curl -X POST https://api.mailermail.ru/v1/batch \
-H "Api-Token: mm_live_9f3k…" \
-H "Content-Type: application/json" \
-d '{
"base": {
"from": {"email": "news@shop.ru"},
"subject": "Скидки недели",
"html": "<p>Привет, {{name}}!</p>"
},
"requests": [
{"to": [{"email": "a@ex.ru", "substitutions": {"name": "Анна"}}]},
{"to": [{"email": "b@ex.ru", "substitutions": {"name": "Борис"}}]}
]
}'{
"success": true,
"responses": [
{"success": true, "message_ids": ["…"]},
{"success": true, "message_ids": ["…"]}
]
}Отправлять можно только с подтверждённого домена: если домен из from не проверен, /send и /batch отвечают 422. Это защита от подделки отправителя и требование почтовых провайдеров — без SPF и DKIM письма уходят в спам.
Добавление и проверка живут в кабинете намеренно: это разовое действие человека с доступом к DNS, а не то, что автоматизируют из кода. Через API домены доступны только на чтение — чтобы интеграция могла убедиться, что домен готов, до отправки, а не ловить 422 в бою.
| Поле | Тип | Обяз. | Описание |
|---|---|---|---|
verified | статус | — | Все три записи на месте — можно отправлять. |
pending_dns | статус | — | Домен добавлен, записи ещё не найдены. |
failed | статус | — | Записи были, но пропали: домен перестал проходить суточную перепроверку. Отправка с него не пойдёт, пока записи не вернутся. |
Поля spf, dkim, dmarc показывают, какая именно запись не найдена. checked_at — время последней проверки: мы перепроверяем домены раз в сутки сами.
Рекомендуем отдельный поддомен для рассылок (например mail.brand.ru): он изолирует репутацию рассылок от корпоративной почты — проблемы с одним потоком не утянут другой.
Кириллические домены можно запрашивать как есть — /domains/почта.рф и /domains/xn--80a1acny.xn--p1ai вернут одно и то же.
curl https://api.mailermail.ru/v1/domains \
-H "Api-Token: mm_live_9f3k…"{
"success": true,
"total": 1,
"domains": [
{
"domain": "mail.brand.ru",
"status": "verified",
"spf": true,
"dkim": true,
"dmarc": true,
"is_default": true,
"checked_at": "2026-08-03T06:00:11+03:00",
"created_at": "2026-07-20T12:31:00+03:00"
}
]
}curl https://api.mailermail.ru/v1/domains/mail.brand.ru \
-H "Api-Token: mm_live_9f3k…"{
"success": true,
"domain": {
"domain": "mail.brand.ru",
"status": "pending_dns",
"spf": true,
"dkim": false,
"dmarc": false,
"dns_records": [
{"type": "TXT", "name": "@",
"value": "v=spf1 include:_spf.mailermail.ru ~all"},
{"type": "TXT", "name": "mm1._domainkey",
"value": "\"v=DKIM1; k=rsa; p=MIIBIjANBg…\""},
{"type": "TXT", "name": "_dmarc",
"value": "v=DMARC1; p=none; rua=mailto:dmarc@feedback.mailermail.ru"}
]
}
}Проверка адреса без отправки письма: синтаксис, MX/A домена, ролевые и одноразовые адреса, глобальный реестр недоставляемых, подсказка опечаток. Требует scope verify. Пакетом — до 100 адресов.
| Поле | Тип | Обяз. | Описание |
|---|---|---|---|
email | string | да | Один адрес (для /verify). |
emails | array | да | Массив адресов ≤100 (для /verify/batch). |
deliverable | Домен принимает почту, признаков риска нет. |
risky | Ролевой/одноразовый адрес либо вероятная опечатка домена. |
undeliverable | Синтаксис неверен, домен мёртв или адрес в реестре отказов. |
unknown | Домен не удалось проверить (временно). |
curl -X POST https://api.mailermail.ru/v1/verify \
-H "Api-Token: mm_live_9f3k…" \
-H "Content-Type: application/json" \
-d '{"email": "ivan@example.ru"}'{
"success": true,
"email": "ivan@example.ru",
"status": "deliverable",
"checks": {
"syntax": true,
"domain": "ok",
"role": false,
"disposable": false,
"known_bounce": false,
"did_you_mean": null
}
}Хранимые шаблоны письма с переменными {{var}} в теме и теле. При отправке подставляются через template_id (или template_uuid) + template_variables/substitutions. Требует scope send. Лимит — 5000 шаблонов на аккаунт.
Хранилище общее с личным кабинетом. Шаблон, созданный по API, виден и редактируется в разделе «Шаблоны», а письмо, свёрстанное в кабинете, доступно по API — поле source показывает, откуда строка. Поэтому у шаблона есть версии: send и batch отправляют опубликованную версию, а не текущий черновик. Через API шаблон публикуется сам — при создании и при правке subject/html/text; публиковать вручную нужно только то, что правили в кабинете.
| Поле | Тип | Обяз. | Описание |
|---|---|---|---|
name | string | да | Название, ≤128. Обязательно при создании. |
subject | string | нет | Тема (с {{var}}), ≤255. |
html | string | — | HTML-тело, ≤2 МБ. Нужно html и/или text. |
text | string | — | Текстовое тело, ≤2 МБ. |
category | string | нет | Метка по умолчанию, ≤255. |
slug | string | нет | Читаемый алиас для template_id: [a-z0-9-], ≤64. Уникален внутри аккаунта (занятый — 409), в форме UUID запрещён. Переименование ломает интеграции — постоянный ключ шаблона это uuid. |
publish | bool | нет | По умолчанию true. false оставит черновик — отправить им нельзя. |
GET /templates принимает ?limit= (≤100) и ?offset=. GET /templates/{uuid} возвращает шаблон целиком (с html/text). PATCH обновляет любое подмножество полей; после патча обязано остаться хотя бы одно тело, а в ответе приходит состояние после правки (status, version). DELETE удаляет. Шаблон, за который держится рассылка, запущенная до появления версий, править и удалять нельзя (409): она читает его на каждом письме.
html/text в ответе — это черновик, а отправляется опубликованная версия. Их видно расходящимися, когда письмо правят в кабинете и не публикуют. Признак — has_unpublished_changes в GET и в ответе PATCH: если он true, уйдёт не то тело, что вы прочитали, — опубликуйте (POST /templates/{uuid}/publish). Само по себе status: "published" говорит лишь о том, что опубликованная версия существует.
curl -X POST https://api.mailermail.ru/v1/templates \
-H "Api-Token: mm_live_9f3k…" \
-H "Content-Type: application/json" \
-d '{
"name": "Подтверждение заказа",
"subject": "Заказ №{{order}} принят",
"html": "<p>{{name}}, спасибо!</p>"
}'{
"success": true,
"template": {
"uuid": "7c9e6a…",
"name": "Подтверждение заказа",
"subject": "Заказ №{{order}} принят",
"category": "",
"slug": "order-ok",
"source": "api",
"status": "published",
"version": 1
}
}# отправка по слагу: на стенде и на бою он одинаковый
curl -X POST https://api.mailermail.ru/v1/send \
-H "Api-Token: mm_live_9f3k…" \
-H "Content-Type: application/json" \
-d '{
"from": {"email": "robot@example.ru"},
"to": [{"email": "user@example.ru"}],
"template_id": "order-ok",
"template_variables": {"order": "1042", "name": "Иван"}
}'curl -X DELETE \
https://api.mailermail.ru/v1/templates/7c9e6a… \
-H "Api-Token: mm_live_9f3k…"Адреса, на которые сервис не отправит письмо. Пополняется автоматически (жёсткие отказы, жалобы, отписки) и вручную через API. Требует scope send.
GET /suppressions — список (?limit= ≤1000, ?offset=, ?reason=). С ?email= — точечная проверка одного адреса.POST — добавить {emails:[…≤1000], reason?}.DELETE — убрать (снять блокировку) {emails:[…≤1000]}.manual | Добавлен вручную (по умолчанию при POST). |
hard_bounce | Жёсткий отказ (ящик не существует). |
complaint | Жалоба на спам (FBL). |
unsubscribed | Отписка через List-Unsubscribe. |
curl -X POST https://api.mailermail.ru/v1/suppressions \
-H "Api-Token: mm_live_9f3k…" \
-H "Content-Type: application/json" \
-d '{"emails": ["a@ex.ru", "b@ex.ru"], "reason": "manual"}'{
"success": true,
"added": 2,
"reason": "manual"
}{
"success": true,
"email": "a@ex.ru",
"suppressed": true,
"reason": "hard_bounce"
}Лог отправок хранится 30 дней. Требует scope send.
GET /messages| Поле | Тип | Обяз. | Описание |
|---|---|---|---|
limit / offset | int | нет | Пагинация (limit ≤200, по умолч. 50). |
status | enum | нет | sent · sandbox · skipped · failed · queued. |
category | string | нет | Фильтр по метке. |
email | string | нет | Фильтр по получателю. |
from / to | string | нет | YYYY-MM-DD или с временем. |
GET /messages/{uuid} возвращает одно сообщение, а для писем из /batch — ещё и состояние очереди (попытки, время отправки). GET /stats?days=7|30 отдаёт агрегаты по статусам, дням и категориям + суммарный объём проверок и отправок.
curl "https://api.mailermail.ru/v1/messages?status=sent&limit=20" \
-H "Api-Token: mm_live_9f3k…"{
"success": true,
"total": 128,
"messages": [
{
"message_id": "a1b2c3-…",
"to": "ivan@example.ru",
"from": "no-reply@shop.ru",
"subject": "Заказ №4211 собран",
"status": "sent",
"created_at": "2026-08-03 12:00:01"
}
]
}curl "https://api.mailermail.ru/v1/stats?days=30" \
-H "Api-Token: mm_live_9f3k…"Публичный endpoint по стандарту RFC 8058 (One-Click). Токен не нужен — ссылку санкционирует HMAC-подпись. Его вызывают почтовые клиенты (кнопка «Отписаться» в Gmail/Yandex) и получатели.
Ссылка автоматически проставляется в заголовок List-Unsubscribe при массовой отправке (в /batch — по умолчанию; в /send — по options.list_unsubscribe). POST добавляет адрес в стоп-лист (reason=unsubscribed) и снимает подписку в кабинете; GET отдаёт человеку страницу подтверждения.
Самостоятельно формировать эти ссылки не нужно — сервис вставляет их сам. В письмах они ведут на основной домен (https://mailermail.ru/api/v1/u/…), а не на api.: получателю привычнее видеть знакомый адрес, и репутация этих ссылок уже набрана.
{
"success": true,
"unsubscribed": true
}Мы шлём POST на ваш адрес батчами до 500 событий, сборка — не реже раза в минуту. Требует scope send; до 20 вебхуков на аккаунт. Управлять ими можно и в кабинете: Настройки → Вебхуки.
| Поле | Тип | Обяз. | Описание |
|---|---|---|---|
url | string | да | Адрес приёмника. Схемы http/https, порты 80, 443, 8080 и 8443. Приватные и зарезервированные адреса (127.0.0.1, 10.x, 192.168.x, облачная метадата) отклоняются — и при создании, и перед каждой доставкой. |
events | array | нет | Типы событий (см. ниже). Поле не передано — подписка на все типы; пустой массив — ошибка 422. |
enabled | bool | нет | Создать выключенным: false. |
В заголовке MailerMail-Signature приходит hex(HMAC-SHA256(сырое тело, секрет)). Считайте подпись по сырому телу до разбора JSON и сверяйте функцией постоянного времени (hash_equals).
Секрет показывается один раз — при создании и при rotate_secret; дальше в ответах только маска. Потеряли — перевыпустите.
Ответ 2xx = событие принято; на ответ даётся 15 секунд, поэтому отвечайте сразу, а обработку делайте асинхронно. Редиректы не выполняются. Неудачные доставки повторяются с нарастающим интервалом (1, 4, 8, 16 и 32 минуты), после 5 отказов подряд вебхук ставится на паузу.
Пока вебхук на паузе, события в очередь не попадают — восстановить пропущенное нельзя, поэтому включайте его сразу после починки приёмника. История событий письма всегда доступна через GET /messages/{uuid}/events независимо от подписок.
Одно событие может прийти дважды (повтор после сетевого сбоя) — дедуплицируйте по event_id.
curl -X POST https://api.mailermail.ru/v1/webhooks \
-H "Api-Token: mm_live_9f3k…" \
-H "Content-Type: application/json" \
-d '{
"url": "https://shop.ru/hooks/mail",
"events": ["email.bounced", "email.opened"]
}'{
"success": true,
"webhook": {
"webhook_id": "7f1c…",
"url": "https://shop.ru/hooks/mail",
"events": ["email.bounced", "email.opened"],
"status": "active",
"secret_hint": "whsec_a1b2…7f9c"
},
"secret": "whsec_a1b2c3d4…"
}// приёмник на вашей стороне
$raw = file_get_contents('php://input');
$sig = $_SERVER['HTTP_MAILERMAIL_SIGNATURE'] ?? '';
if (!hash_equals(hash_hmac('sha256', $raw, $SECRET), $sig)) {
http_response_code(403);
exit;
}
http_response_code(200); // подтверждаем СРАЗУ
fastcgi_finish_request(); // обработка — после ответа
foreach (json_decode($raw, true)['events'] as $e) {
// $e['event'], $e['message_id'], $e['email']
}| Поле | Тип | Обяз. | Описание |
|---|---|---|---|
email.sent | событие | — | Письмо принято почтовым сервером получателя. Для /batch приходит в момент фактической отправки, а не постановки в очередь. |
email.failed | событие | — | Отправить не удалось (в data.reason — причина). |
email.suppressed | событие | — | Адрес в стоп-листе — письмо не отправлялось. |
email.opened | событие | — | Открытие (пиксель). В data — почтовый клиент, страна и город. |
email.clicked | событие | — | Переход по ссылке. В data.url — целевой адрес. |
email.unsubscribed | событие | — | Отписка по One-Click или по ссылке в письме. |
email.bounced | событие | — | Отказ по логам почтовой фермы: в data.bounce_type — код, в data.smtp_code — SMTP-статус. |
email.complained | событие | — | Жалоба на спам (FBL почтового провайдера). |
Поля события: event_id (уникален, для дедупликации), event, message_id, email, timestamp, category и data.
email.bounced, email.complained и email.unsubscribed приходят из внешних источников — почтовой фермы и кнопки в почтовом клиенте, — поэтому письмо для них подбирается по адресу получателя. В таких событиях стоит data.attribution: "by-address": message_id указывает на последнее письмо этому адресу, а не на гарантированно то самое.
Письма, отправленные sandbox-токеном (mm_test_), событий не порождают вовсе.
{
"events": [
{
"event_id": "3b9c…",
"event": "email.opened",
"message_id": "a1b2c3-…",
"email": "ivan@example.ru",
"timestamp": "2026-08-03T12:04:11+03:00",
"category": "orders",
"data": {
"client": "Gmail",
"country": "RU",
"city": "Москва"
}
}
]
}curl "https://api.mailermail.ru/v1/messages/a1b2c3-…/events" \
-H "Api-Token: mm_live_9f3k…"В HTML-письмо можно вшить пиксель открытия и обернуть ссылки редиректом — тогда приходят события email.opened и email.clicked. Управляют этим два флага в options:
| Поле | Тип | Обяз. | Описание |
|---|---|---|---|
options.track_read | bool | нет | Пиксель открытия. В /batch включён по умолчанию, в /send — выключен. |
options.track_links | bool | нет | Оборачивать ссылки. Значения по умолчанию те же. |
Транзакционному письму трекинг обычно не нужен — код подтверждения и счёт незачем считать пикселем, — поэтому в /send он выключен. В массовой рассылке наоборот: без статистики открытий она бессмысленна.
Добавьте атрибут data-mm-no-track к тегу <a> — эта ссылка останется нетронутой. Ссылки отписки и служебные адреса не оборачиваются никогда.
Открытия и переходы дедуплицируются на нашей стороне (окно 10 минут для открытий, 1 минута для переходов), на письмо приходится не более 200 событий трекинга — предзагрузка картинок почтовиком и антивирусные сканеры не превращаются в поток дублей.
curl -X POST https://api.mailermail.ru/v1/send \
-H "Api-Token: mm_live_9f3k…" \
-d '{
"from": {"email": "news@shop.ru"},
"to": [{"email": "ivan@example.ru"}],
"subject": "Новинки недели",
"html": "<a href=\"https://shop.ru/new\">Смотреть</a> <a href=\"https://shop.ru/help\" data-mm-no-track>Помощь</a>",
"options": {"track_read": true, "track_links": true}
}'