/ Email API / справочник Swagger UIopenapi.yamlвойти

Email API MailerMail

Транзакционные и массовые письма из вашего кода плюс верификация адресов. Все запросы — 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} — удобно для мониторинга.

Health
curl https://api.mailermail.ru/v1/health
$body = file_get_contents(
  'https://api.mailermail.ru/v1/health'
);
Ответ200
{
  "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.

Sandbox-режим

Токены с префиксом mm_test_ — тестовые: /send и /batch проходят весь конвейер (валидацию, проверку стоп-листа, запись в лог с пометкой sandbox), но письма физически не отправляются. Ответ содержит "sandbox": true.

Заголовок токена
curl https://api.mailermail.ru/v1/stats \
  -H "Api-Token: mm_live_9f3k…"
$ch = curl_init('https://api.mailermail.ru/v1/stats');
curl_setopt_array($ch, [
  CURLOPT_HTTPHEADER => ['Api-Token: mm_live_9f3k…'],
  CURLOPT_RETURNTRANSFER => true,
]);
$res = json_decode(curl_exec($ch), true);

Ошибки

Успех — 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 ещё не развёрнута.
Ответ422 Unprocessable
{
  "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 адресов.
Ответ429 Too Many Requests
{
  "success": false,
  "errors": [
    "Rate limit exceeded: 120 requests/minute."
  ]
}

Идемпотентность

Методы /send и /batch принимают заголовок Idempotency-Key (произвольная строка ≤200 символов). Это защищает от двойной отправки при сетевых ретраях.

  • Повтор с тем же ключом в течение 24 часов возвращает первый сохранённый ответ с заголовком 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 '{ … }'
$headers = [
  'Api-Token: mm_live_9f3k…',
  'Idempotency-Key: order-4211-confirm',
  'Content-Type: application/json',
];

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

POSThttps://api.mailermail.ru/v1/send

Синхронная транзакционная отправка: письмо уходит за секунды, у каждого получателя — свой Message-ID. Требует scope send. Домен отправителя должен быть подтверждён в аккаунте (иначе 422) — он же даёт DKIM-подпись.

Задайте subject + html и/или text, либо template_id (тогда тему и тело берёт шаблон — они взаимоисключимы). Переменные {{var}} подставляются из template_variables и to[].substitutions.

Поля тела

ПолеТипОбяз.Описание
fromobjectдаОтправитель: {email, name?}. Домен — verified.
toarrayдаПолучатели {email, name?, substitutions?}, ≤50.
reply_toobjectнетАдрес для ответа {email, name?}.
subjectstringТема, ≤255. Обязательна, когда шаблон не задан.
htmlstringHTML-тело. Нужно html и/или text.
textstringТекстовое тело. Если не задано — генерируется из HTML.
categorystringнетМетка для группировки в статистике, ≤255.
attachmentsarrayнет{content(base64), filename, type?, disposition?, content_id?}, ≤10 МБ суммарно.
template_idstringнетШаблон по UUID или по слагу (взаимоисключим с subject/html/text и с template_uuid). Отправляется опубликованная версия шаблона.
template_uuidstringнетТо же, но строго UUID. Поле оставлено для действующих интеграций.
template_variablesobjectнетГлобальные переменные {{var}} (скаляры).
optionsobjectнет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>"
  }'
$ch = curl_init('https://api.mailermail.ru/v1/send');
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_HTTPHEADER => [
    'Api-Token: mm_live_9f3k…',
    'Content-Type: application/json',
  ],
  CURLOPT_POSTFIELDS => json_encode([
    'from' => ['email' => 'no-reply@shop.ru', 'name' => 'Shop'],
    'to'   => [['email' => 'ivan@example.ru']],
    'subject' => 'Заказ №4211 собран',
    'html' => '<h1>Спасибо за заказ!</h1>',
  ]),
  CURLOPT_RETURNTRANSFER => true,
]);
$res = json_decode(curl_exec($ch), true);
Ответ200
{
  "success": true,
  "message_ids": ["a1b2c3d4-…"]
}
Ответ200 частично
{
  "success": true,
  "message_ids": [],
  "skipped": [
    {"email": "old@dead.ru", "reason": "known_bounce"}
  ]
}

Массовая отправка

POSThttps://api.mailermail.ru/v1/batch

Постановка партии писем в очередь: message_ids выдаются сразу, реально отправляет фоновый воркер (латентность до ~5 минут). Объект base задаёт общие поля, поля каждого request их перекрывают. Вложения не поддерживаются — используйте /send.

HTTP всегда 200 (кроме структурных ошибок тела): пер-письмо статусы — в массиве responses[] в порядке requests.

Поля тела

ПолеТипОбяз.Описание
baseobjectнетОбщие поля письма (как в /send, без attachments).
requestsarrayдаМассив писем (поля /send), ≤500; суммарно ≤1000 писем.
options.send_atstringнетОтложить: YYYY-MM-DD HH:MM:SS UTC, окно ≤72 ч.
options.skip_unsubscribeboolнетОтключить 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": "Борис"}}]}
    ]
  }'
$payload = [
  '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' => 'Борис']]]],
  ],
];
Ответ200
{
  "success": true,
  "responses": [
    {"success": true, "message_ids": ["…"]},
    {"success": true, "message_ids": ["…"]}
  ]
}

Домены отправителя

GET/domains
GET/domains/{domain}

Отправлять можно только с подтверждённого домена: если домен из from не проверен, /send и /batch отвечают 422. Это защита от подделки отправителя и требование почтовых провайдеров — без SPF и DKIM письма уходят в спам.

Как подключить домен

  1. В кабинете: Настройки → Домены отправителя → Добавить. Мы сгенерируем пару ключей DKIM.
  2. Опубликуйте три TXT-записи в DNS вашего домена (их отдаёт и этот метод, и карточка домена в кабинете).
  3. Нажмите «Проверить DNS». Записи расходятся до 48 часов — если проверка не прошла сразу, повторите позже.

Добавление и проверка живут в кабинете намеренно: это разовое действие человека с доступом к 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…"
$ch = curl_init('https://api.mailermail.ru/v1/domains');
curl_setopt_array($ch, [
  CURLOPT_HTTPHEADER => ['Api-Token: mm_live_9f3k…'],
  CURLOPT_RETURNTRANSFER => true,
]);
$res = json_decode(curl_exec($ch), true);

// можно ли слать с этого домена
$ok = false;
foreach ($res['domains'] as $d) {
    if ($d['domain'] === 'mail.brand.ru') {
        $ok = ($d['status'] === 'verified');
    }
}
Ответ200
{
  "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"
    }
  ]
}
Один домен + записи DNS
curl https://api.mailermail.ru/v1/domains/mail.brand.ru \
  -H "Api-Token: mm_live_9f3k…"
Ответ200
{
  "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"}
    ]
  }
}

Верификация адресов

POSThttps://api.mailermail.ru/v1/verify
POSThttps://api.mailermail.ru/v1/verify/batch

Проверка адреса без отправки письма: синтаксис, MX/A домена, ролевые и одноразовые адреса, глобальный реестр недоставляемых, подсказка опечаток. Требует scope verify. Пакетом — до 100 адресов.

Поле тела

ПолеТипОбяз.Описание
emailstringдаОдин адрес (для /verify).
emailsarrayдаМассив адресов ≤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"}'
$payload = ['email' => 'ivan@example.ru'];
// POST на /api/v1/verify с заголовком Api-Token
Ответ200
{
  "success": true,
  "email": "ivan@example.ru",
  "status": "deliverable",
  "checks": {
    "syntax": true,
    "domain": "ok",
    "role": false,
    "disposable": false,
    "known_bounce": false,
    "did_you_mean": null
  }
}

Шаблоны

GET/templates
POST/templates
GETPATCHDELETE/templates/{uuid}
POST/templates/{uuid}/publish

Хранимые шаблоны письма с переменными {{var}} в теме и теле. При отправке подставляются через template_id (или template_uuid) + template_variables/substitutions. Требует scope send. Лимит — 5000 шаблонов на аккаунт.

Хранилище общее с личным кабинетом. Шаблон, созданный по API, виден и редактируется в разделе «Шаблоны», а письмо, свёрстанное в кабинете, доступно по API — поле source показывает, откуда строка. Поэтому у шаблона есть версии: send и batch отправляют опубликованную версию, а не текущий черновик. Через API шаблон публикуется сам — при создании и при правке subject/html/text; публиковать вручную нужно только то, что правили в кабинете.

Поля создания / обновления

ПолеТипОбяз.Описание
namestringдаНазвание, ≤128. Обязательно при создании.
subjectstringнетТема (с {{var}}), ≤255.
htmlstringHTML-тело, ≤2 МБ. Нужно html и/или text.
textstringТекстовое тело, ≤2 МБ.
categorystringнетМетка по умолчанию, ≤255.
slugstringнетЧитаемый алиас для template_id: [a-z0-9-], ≤64. Уникален внутри аккаунта (занятый — 409), в форме UUID запрещён. Переименование ломает интеграции — постоянный ключ шаблона это uuid.
publishboolнетПо умолчанию 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>"
  }'
$payload = [
  'name' => 'Подтверждение заказа',
  'subject' => 'Заказ №{{order}} принят',
  'html' => '<p>{{name}}, спасибо!</p>',
];
Ответ201
{
  "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": "Иван"}
  }'
$payload = [
  'from' => ['email' => 'robot@example.ru'],
  'to' => [['email' => 'user@example.ru']],
  'template_id' => 'order-ok', // uuid тоже принимается
  'template_variables' => ['order' => '1042', 'name' => 'Иван'],
];
Удаление
curl -X DELETE \
  https://api.mailermail.ru/v1/templates/7c9e6a… \
  -H "Api-Token: mm_live_9f3k…"
// DELETE /api/v1/templates/{uuid}
// с заголовком Api-Token

Стоп-лист (suppressions)

GET/suppressions
POSTDELETE/suppressions

Адреса, на которые сервис не отправит письмо. Пополняется автоматически (жёсткие отказы, жалобы, отписки) и вручную через API. Требует scope send.

  • GET /suppressions — список (?limit= ≤1000, ?offset=, ?reason=). С ?email= — точечная проверка одного адреса.
  • POST — добавить {emails:[…≤1000], reason?}.
  • DELETE — убрать (снять блокировку) {emails:[…≤1000]}.

Причины (reason)

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"}'
$payload = [
  'emails' => ['a@ex.ru', 'b@ex.ru'],
  'reason' => 'manual',
];
Ответ200
{
  "success": true,
  "added": 2,
  "reason": "manual"
}
Ответ200 проверка ?email=
{
  "success": true,
  "email": "a@ex.ru",
  "suppressed": true,
  "reason": "hard_bounce"
}

Логи и статистика

GET/messages
GET/messages/{uuid}
GET/stats

Лог отправок хранится 30 дней. Требует scope send.

Фильтры GET /messages

ПолеТипОбяз.Описание
limit / offsetintнетПагинация (limit ≤200, по умолч. 50).
statusenumнетsent · sandbox · skipped · failed · queued.
categorystringнетФильтр по метке.
emailstringнетФильтр по получателю.
from / tostringнет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…"
$url = 'https://api.mailermail.ru/v1/messages'
     . '?status=sent&limit=20';
// GET с заголовком Api-Token
Ответ200
{
  "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…"
$url = 'https://api.mailermail.ru/v1/stats?days=30';

Отписка

GETPOSThttps://api.mailermail.ru/v1/unsubscribe

Публичный 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.: получателю привычнее видеть знакомый адрес, и репутация этих ссылок уже набрана.

Ответ200 One-Click POST
{
  "success": true,
  "unsubscribed": true
}

Вебхуки

GETPOST/webhooks
GETPATCHDELETE/webhooks/{uuid}
POST/webhooks/{uuid}/rotate_secret

Мы шлём POST на ваш адрес батчами до 500 событий, сборка — не реже раза в минуту. Требует scope send; до 20 вебхуков на аккаунт. Управлять ими можно и в кабинете: Настройки → Вебхуки.

Поля создания

ПолеТипОбяз.Описание
urlstringдаАдрес приёмника. Схемы http/https, порты 80, 443, 8080 и 8443. Приватные и зарезервированные адреса (127.0.0.1, 10.x, 192.168.x, облачная метадата) отклоняются — и при создании, и перед каждой доставкой.
eventsarrayнетТипы событий (см. ниже). Поле не передано — подписка на все типы; пустой массив — ошибка 422.
enabledboolнетСоздать выключенным: 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"]
  }'
$ch = curl_init('https://api.mailermail.ru/v1/webhooks');
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_HTTPHEADER => [
    'Api-Token: mm_live_9f3k…',
    'Content-Type: application/json',
  ],
  CURLOPT_POSTFIELDS => json_encode([
    'url' => 'https://shop.ru/hooks/mail',
    'events' => ['email.bounced', 'email.opened'],
  ]),
  CURLOPT_RETURNTRANSFER => true,
]);
$res = json_decode(curl_exec($ch), true);
// $res['secret'] — сохраните, больше не покажем
Ответ201
{
  "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_readboolнетПиксель открытия. В /batch включён по умолчанию, в /send — выключен.
options.track_linksboolнетОборачивать ссылки. Значения по умолчанию те же.

Транзакционному письму трекинг обычно не нужен — код подтверждения и счёт незачем считать пикселем, — поэтому в /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}
  }'