listmonk — менеджер рассылок и списков подписчиков, который устанавливается на свой сервер. Он хранит базу и отправляет кампании, а доставку поручает SMTP-серверам, которые вы укажете: держит к каждому пул соединений и сам ограничивает скорость. Поэтому важны не только хост и пароль, но и пул, темп, отказы и отписки. Ниже — подключение к fmailer.
| Параметр | Значение |
|---|---|
| SMTP-хост | smtp.fmailer.ru |
| Порт | 587 |
| Шифрование | STARTTLS (соединение переводится в TLS до авторизации) |
| Логин | Логин токена вашего домена |
| Пароль | Пароль этого токена — показывается один раз, при создании |
Откройте Настройки → SMTP и отредактируйте существующий блок или добавьте новый:
| Поле | Значение |
|---|---|
| Включено | Да |
| Хост | smtp.fmailer.ru |
| Порт | 587 |
| Протокол аутентификации | PLAIN |
| Имя пользователя | Логин токена вашего домена |
| Пароль | Пароль этого токена |
| TLS | STARTTLS (SSL/TLS — для порта 465) |
| Пропустить проверку TLS | Выкл |
| Имя хоста HELO | По желанию — имя сервера, где работает listmonk |
Адрес отправителя кампании — и адрес по умолчанию в разделе Настройки → Общие — должен быть на подтверждённом домене, например news@mail.example.ru.
| Настройка | По умолчанию | Рекомендация |
|---|---|---|
| Макс. соединений | 10 | 10 или меньше — лимит тарифа больше соединений не поднимут |
| Повторные попытки | 2 | Оставить: повтор идёт через другое соединение из пула |
| Тайм-аут простоя | 15s | Оставить: простаивающее соединение закрывается и уходит из пула |
| Тайм-аут ожидания | 5s | Оставить |
Нажмите «Сохранить», затем «Проверить соединение» у блока — listmonk отправит тестовое письмо на указанный адрес.
Часовой лимит тарифа проверяет релей: сверх него каждое письмо получает 550 hourly limit exceeded, а после «Максимального порога ошибок» listmonk ставит кампанию на паузу. Лучше, чтобы listmonk сдерживал себя сам, — в разделе Настройки → Производительность:
1h, «Макс. сообщений» — чуть меньше часового лимита тарифа. Прогрев нового домена устроен иначе: он ничего не отклоняет, а письма сверх потолка доставляет позже. Подробнее — на странице лимитов отправки.
Если в разделе Настройки → Конфиденциальность включён заголовок List-Unsubscribe, listmonk добавляет в каждое письмо кампании пару заголовков для отписки в одно нажатие. Когда мы относим письмо к массовым, релей заменяет эту пару своей: отписка попадает в список отписавшихся домена, и следующие письма на этот адрес мы не доставляем. В listmonk подписчик при этом остаётся активным — кого обновить, подскажет вебхук unsubscribed. Ссылка отписки в самом шаблоне по-прежнему ведёт в listmonk.
Включите Настройки → Отказы → «Включить обработку отказов», задайте действия (жёсткий отказ: количество 1, действие «Чёрный список») и подключите один или оба источника:
Return-Path из пользовательских заголовков SMTP-блока. Он должен быть на подтверждённом домене, а return path домена в панели — пустым, иначе он заменит адрес listmonk. bounced. Включите вебхуки для отказов, создайте API-пользователя с ролью, в которой есть право webhooks:post_bounce, и передавайте события: # Передаёт события bounced и complained в API отказов listmonk.
import hashlib, hmac, os, time
import requests
from flask import Flask, abort, request
app = Flask(__name__)
SECRET = os.environ["WEBHOOK_SECRET"] # секрет эндпоинта вебхуков
LISTMONK = "https://lists.example.ru"
API_AUTH = ("bounce-bot", os.environ["LISTMONK_TOKEN"]) # API-пользователь listmonk
TYPES = {"bounced": "hard", "complained": "complaint"}
@app.post("/hooks/delivery")
def delivery():
raw = request.get_data(as_text=True)
ts = request.headers["X-Webhook-Timestamp"]
sig = request.headers["X-Webhook-Signature"]
expected = hmac.new(SECRET.encode(), f"{ts}.{raw}".encode(), hashlib.sha256).hexdigest()
if abs(time.time() - int(ts)) > 300 or not hmac.compare_digest(expected, sig):
abort(400)
event = request.get_json()
if event["event"] in TYPES:
requests.post(f"{LISTMONK}/webhooks/bounce", auth=API_AUTH, timeout=10, json={
"email": event["email"],
"source": "relay",
"type": TYPES[event["event"]],
}).raise_for_status()
return "", 204| Симптом | Причина и решение |
|---|---|
| Ошибка авторизации при верном пароле | Выбран протокол LOGIN или None — поставьте PLAIN. |
| Проверка соединения зависает | Исходящий порт заблокирован — используйте 8587 со STARTTLS или 8465 с SSL/TLS. |
550 from must be equal to … | Адрес отправителя или Return-Path из заголовков не на подтверждённом домене. |
Кампания на паузе, 550 hourly limit exceeded | Включите ограничение скользящего окна и возобновите кампанию. |
| Вебхук отказов отвечает 404 | Не включены вебхуки в разделе Настройки → Отказы. |
| Письма в спаме | Отправитель не на подтверждённом домене либо в базе есть неподтверждённые или давно неактивные адреса. |
PLAIN. Релей принимает AUTH PLAIN по зашифрованному соединению и не предлагает LOGIN, поэтому с вариантом LOGIN listmonk не авторизуется даже с верным паролем. Новый SMTP-блок создаётся с вариантом None — тогда логин и пароль не отправляются вовсе.
Начните со значения по умолчанию — 10 — или меньше. Дополнительные соединения помогают, только если воркеры listmonk ждут сеть. Часовой лимит тарифа они не поднимают, и рассылка быстрее лимита просто раньше упирается в отказ.
Настройки → Производительность → «Включить ограничение скользящего окна»: длительность 1h, «Макс. сообщений» — чуть меньше часового лимита тарифа. Тогда listmonk придерживает письма до конца окна, а не получает на них 550.
Да, если в разделе Настройки → Конфиденциальность включён заголовок List-Unsubscribe: listmonk ставит List-Unsubscribe и List-Unsubscribe-Post для отписки в одно нажатие. В письмах, которые мы относим к массовым, релей заменяет эту пару своей, и отписка записывается у нас, а не в listmonk.
listmonk приостанавливает кампанию, когда ошибок становится больше «Максимального порога ошибок» (Настройки → Производительность). С релеем обычная причина — 550 hourly limit exceeded. Включите скользящее окно и возобновите кампанию.
Из POP3-ящика, куда приходят письма об отказах, или через свой API вебхуков. Отказ, полученный нашими серверами при доставке, письмом не возвращается, поэтому такие отказы ловит только мост на вебхуках — пример ниже.