API подписки¶
Блок подписки отправляет на один публичный эндпоинт, и всё остальное тоже может: форма на другом сайте, скрипт, сервер, ретранслирующий подписки откуда-то, где Mailroom не работает. Эта страница — справочник по этому эндпоинту.
С базовым маршрутом по умолчанию — https://www.example.com/newsletter/subscribe. Ключа API нет: эндпоинт публичный, как и блок подписки, который его использует.
Два способа отправки¶
| Запрос | Принимается, когда |
|---|---|
JSON, Content-Type: application/json |
Всегда. Браузерная форма с другого сайта не может отправить этот тип содержимого без предварительного запроса, который браузер не разрешит, поэтому nonce не нужен. Именно это отправляет скрипт блока подписки и то, что должен отправлять скрипт или ретранслятор. |
Отправка формы (application/x-www-form-urlencoded или multipart/form-data) |
Только с nonce сайта в поле nonce, что отправляет блок подписки без JavaScript. |
Всё остальное отклоняется с 415. GET отклоняется с 405.
Поля¶
| Поле | Тип | Что делает |
|---|---|---|
email |
строка, обязательна | Адрес. |
name |
строка | Его имя. |
list |
строка | Код списка. Пустой или код, не называющий список, означает список по умолчанию. |
lists |
массив строк или одна строка через запятую | Дополнительные коды списков для той же подписки. Каждый список сохраняет своё двойное подтверждение подписки, и одно письмо подтверждения покрывает их все. Коды, не называющие список, исключаются; если не осталось ни одного, используется список по умолчанию. |
tags |
массив строк или одна строка через запятую | Коды тегов, которые на него поставить. |
consent |
логическое | Обязательно и должно быть true, когда у сайта есть Consent Sentence. Считаются true, 1, "true", "on" и "yes". |
language |
строка | Двухбуквенный код языка для сохранения у него. |
source |
строка | Как он пришёл: form или api. Всё остальное игнорируется. См. ниже. |
source_ref |
строка | Ваша заметка о том, откуда он пришёл, например footer. До 190 символов. |
pick_lists |
логическое | Отправляется формой, которая предлагает списки флажками. С ним подписка, не называющая список, отклоняется, а не попадает в список по умолчанию. |
website |
строка | Ловушка для ботов. Должна быть пустой (см. ниже). |
Ответы¶
JSON-запрос всегда получает JSON в ответ; отправка формы получает JSON, когда отправляет Accept: application/json, иначе — страницу в вашей теме с тем же предложением.
| Статус | Тело | Когда |
|---|---|---|
200 |
{"status": "subscribed", "message": "Thanks. You are on the list."} |
Он уже в списке (список, который не просит подтверждения). |
202 |
{"status": "pending", "message": "We have sent you a confirmation email. Check your inbox and press the button in it to be added to the list."} |
Письмо подтверждения в пути. Он ни в чём не состоит, пока не нажмёт кнопку. |
405 |
{"status": "error", "error": "method_not_allowed", ...} |
Это не POST. Несёт Allow: POST. |
415 |
{"status": "error", "error": "unsupported_media_type", ...} |
Отправка формы без nonce или другой тип содержимого. |
422 |
{"status": "error", "error": "invalid_email", ...} |
Адрес не является адресом. |
422 |
{"status": "error", "error": "consent_required", ...} |
У сайта есть предложение согласия, а consent не был true. |
422 |
{"status": "error", "error": "list_required", ...} |
pick_lists был отправлен без списка. |
429 |
{"status": "error", "error": "rate_limited", "message": "...", "retry_after": 1800} |
Слишком много подписок с одного адреса посетителя. Несёт Retry-After. |
В каждом теле ошибки также есть message, который можно показать посетителю как есть.
Новый адрес, уже находящийся в списке и исключённый получают одинаковый ответ, байт в байт, поэтому эндпоинт никогда не выдаёт, читает ли конкретный человек вашу почту. Исключённый адрес просто не записывается.
Ловушка — поле website. Запрос, в котором что-то есть, получает ответ 200, и ничего не записывается, поэтому бот ничего не узнаёт о том, какое поле его выдало. Не передавайте его или передавайте пустым.
Ограничение частоты¶
Каждый адрес посетителя может отправлять Signups Per Hour (rate_limits.subscribe, 10) раз в час; 0 выключает ограничение. Один вызов считается один раз, сколько бы списков он ни называл. Счётчик хранится в базе Mailroom под хешем адреса, а IPv6-адрес считается как его /64.
Как записывается источник¶
source у подписчика говорит, как он пришёл. Вызывающий может назвать его, form или api; всё остальное (admin, import и другие способы, которые пишет только сам сайт) игнорируется, и подписка читается так, будто источник не указан, с тем же ответом. Если он не указан:
- отправка формы, нёсшая nonce, —
form; - тело JSON, отправленное браузером со страницы вашего сайта (
Sec-Fetch-Site: same-originилиOrigin, называющий собственный хост сайта), —form, то есть собственный скрипт блока подписки; - любое другое тело JSON, например ретранслятор на другом сервере, —
api.
Примеры¶
curl -s -X POST https://www.example.com/newsletter/subscribe \
-H "Content-Type: application/json" \
-d '{"email": "ada@example.com", "name": "Ada", "list": "newsletter", "consent": true, "source_ref": "launch-page"}'
{"status": "pending", "message": "We have sent you a confirmation email. Check your inbox and press the button in it to be added to the list."}
Несколько списков и тегов сразу:
{"email": "ada@example.com", "lists": ["newsletter", "events"], "tags": ["webinar"], "consent": true}
За прокси или ретранслятором¶
Адрес, который считается в ограничении частоты и сохраняется с согласием человека, — это REMOTE_ADDR соединения. Когда ваш сайт стоит за прокси или балансировщиком нагрузки либо другой сервер ретранслирует ему подписки, все запросы иначе приходили бы с одного адреса, и десять подписок в час были бы лимитом всего сайта.
Trusted Proxies (security.trusted_proxies, на вкладке Public Pages) перечисляет адреса или диапазоны CIDR этих серверов:
Только запросу, чей REMOTE_ADDR есть в списке, верят в заголовке X-Forwarded-For. Mailroom читает заголовок справа, пропускает каждый доверенный шаг и берёт первый недоверенный адрес как адрес посетителя. Порт, который прокси записал на шаге (203.0.113.9:5678, [2001:db8::1]:443), отбрасывается. От всех остальных заголовок игнорируется, потому что его может отправить кто угодно.
Серверный ретранслятор¶
Скажем, www.example.com держит форму в подвале, а news.example.com держит Mailroom. Форма на www.example.com отправляет на свой сервер, который ретранслирует подписку как JSON, передавая адрес посетителя:
<?php
// На www.example.com, после чтения формы в подвале.
$payload = json_encode([
'email' => $email,
'list' => 'newsletter',
'consent' => $consentTicked, // true, только если посетитель отметил флажок
'source' => 'form', // форма, хотя отправляет её сервер
'source_ref' => 'www-footer',
]);
$ch = curl_init('https://news.example.com/newsletter/subscribe');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Accept: application/json',
'X-Forwarded-For: ' . $_SERVER['REMOTE_ADDR'],
],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$answer = json_decode((string) $body, true);
// 200 или 202: показать $answer['message']. 422 или 429: показать $answer['message'] как ошибку.
На news.example.com добавьте адрес сервера-ретранслятора в Trusted Proxies. Тогда каждый посетитель получает свои десять подписок в час, и его собственный адрес сохраняется с его согласием.
- Обращайтесь к серверу Mailroom напрямую. Если запрос ретранслятора проходит через CDN или прокси,
REMOTE_ADDR— это адрес того прокси, поэтому либо перечислите и эти адреса, либо отправляйте запрос прямо на origin. - Показывайте то же предложение согласия. Запись согласия хранит хеш собственного Consent Sentence Mailroom, поэтому форма ретранслятора должна показывать это предложение слово в слово.
- Передавайте ответ дальше.
messageнаписан для посетителя, а422или429точно говорит, что ему сообщить. - Используйте этот эндпоинт, а не админский API.
POST /api/v1/mailroom/subscribers— для администраторов: ему нужен ключ сmailroom.manage, и он записывает человека как добавленного сайтом.