Перейти к содержанию

Исходящая почта

Helpdesk Pro отправляет два вида писем. Клиенты получают несколько писем о важных событиях, которые читаются как личное письмо от помогающего им человека. Сотрудники получают уведомления по почте, когда не прочитали их сначала в рабочей области. Всё идёт через плагин Email для Grav, поэтому какой бы движок ни использовал ваш сайт (SMTP, Postmark, Mailgun, SES, Resend…), он отправляет и почту Helpdesk Pro.

Настройка исходящей почты

  1. Настройте плагин Email и проверьте, что он отправляет: bin/plugin email test-email --to=you@example.com.
  2. Установите email.support_address в адрес, на который пишут клиенты. Helpdesk Pro отправляет с него, и ответы приходят на него. Пока входящая почта не настроена, ответы клиентов попадают в этот ящик.
  3. Оформите письма в разделе Look and Branding (см. Фирменное оформление почты).
  4. Отправьте тест: Send a test email на экране рабочей области Operations → Email или POST /helpdesk-pro/mail/test с {"to": "you@example.com"} (MCP send_test_email, право helpdesk-pro.settings). Ответ сообщает, отправлено ли письмо, адрес отправителя и сохраняет ли ваш транспорт пользовательские заголовки и заголовки отписки.
  5. Убедитесь, что cron запускает планировщик. Ответы клиентам уходят сразу после создавшего их запроса, но повторные попытки и письма-уведомления сотрудникам требуют фонового процесса. См. Задания и cron.

Что получают клиенты

Шаблон Кому Когда
client-received Заявителю Обращение приходит из формы справочного центра (email.acknowledge_web_tickets) или по почте (email.acknowledge_email_tickets). Не более одного в час на человека, никогда для спама, никогда в ответ на автоматическое письмо. В нём перечислены пришедшие файлы, а для обращения по почте — каждый файл, который не сохранили, и почему.
client-reply Заявителю и копиям, никогда автору Сотрудник публикует публичный ответ. Заявка, которую сотрудник открыл от имени клиента, отправляет своё первое сообщение так, если Email the requester не выключено.
client-resolved Заявителю и копиям Заявка переходит в решённый статус. Ответ, опубликованный в ту же минуту (обычное «reply and mark solved»), складывается с ним, поэтому клиент получает одно письмо. При включённых оценках копия заявителя спрашивает «How did we do?».
client-triage Заявителю Новое обращение отклонено или помечено дубликатом с включённым уведомлением. Включает публичную причину, а для дубликата — ссылку на оригинал, когда клиента добавили в него.
client-magic-link Человеку, запросившему вход Ссылка для входа.

Письмо клиенту читается как личное письмо внутри фирменной карточки: тема заявки с «Re:», имя агента («Anna at Acme»), текст ответа, кнопка View request и подвал со ссылкой на обращение и ссылкой «Stop emails about this request». В нём нет номера заявки (если не включён email.subject_tag), цитируемой истории и таблицы статуса.

  • Строка под кнопкой читается «View or reply to your request», пока входящая почта не умеет принимать, и «Reply to this email or view your request» после этого, поэтому она никогда не приглашает ответить туда, где ответ потеряется.
  • Ссылка на обращение — ссылка для входа, созданная для этого получателя, поэтому клиент без пароля попадает на обращение уже с выполненным входом. При выключенном portal.magic_links или для получателя-сотрудника это обычная страница портала.
  • Ответ по почте без добавленного текста не отправляет письмо вообще.

Внутренние заметки никогда не доходят до клиента. Заметки не порождают письмо клиенту, а перед отрисовкой любого клиентского шаблона каждое сообщение и файл, которые он собирается показать, проверяются по базе данных. Внутреннее сообщение, файл в нём, удалённое сообщение или сообщение из другой заявки останавливает всё письмо: оно отменяется, записывается как ошибка и никогда не повторяется.

Клиент не получает ничего из этого, если он отключил уведомления по обращению, выключил всю почту Helpdesk по ссылке отписки или заблокирован. Сотрудники, работающие с проектом, никогда не получают клиентские письма; у них есть колокольчик.

Файлы в письме клиенту

Публичные файлы ответа сотрудника, о котором письмо клиенту (client-reply и ответ, сложенный в client-resolved), попадают в само письмо, чтобы клиенту не нужно было входить, чтобы их увидеть:

  • Изображения (png, jpg, gif, webp) встраиваются и показываются под сообщением, не шире карточки, с именем в подписи. Изображение, вставленное в ответ, показывается там, где стояло.
  • Другие файлы — настоящие вложения, названные под сообщением как «Attached».
  • Ограничение. Файлы берутся по порядку, пока их сумма не превышает email.attach_max_mb (10 МБ). Остальные остаются ссылками, и письмо говорит об этом («2 more files are on your request:»). 0 не прикрепляет ничего, и каждый файл становится ссылкой.
  • Ссылки выполняют вход. Файл, оставленный ссылкой, — ссылка для входа для этого получателя, ведущая на файл. При выключенном portal.magic_links это обычный маршрут файла, {mount}/_file/{id}/{name}.

Файл во внутренней заметке никогда не прикрепляется, не встраивается и не связывается. Байты читаются при сборке письма от имени получателя через ту же проверку доступа, что делает маршрут файлов справочного центра.

Большинство почтовых провайдеров принимают 20–25 МБ на сообщение, но base64 добавляет треть, а некоторые корпоративные фильтры вырезают или задерживают вложения. Установите ограничение ниже или в 0, когда почтовые системы клиентов строгие.

Что получают сотрудники

Шаблон Когда
staff-notification Наступило одно уведомление: заголовок заявки ссылкой, #number · project с её статусом (и приоритетом, когда он не Normal), одно предложение («Cleo replied to “Invoice missing”»), сообщение для ответов и заметок (заметки подсвечены и помечены «Internal note»), пользовательские поля заявки и кнопка Open ticket.
staff-batch Одновременно наступило несколько уведомлений: «5 updates on 3 tickets», затем строка на заявку с тем, что произошло, и новейшим сообщением, затем Open the desk.
staff-digest Ежедневный дайджест: обращения, ждущие в сортировке, назначенные вам заявки, требующие ответа, непрочитанные уведомления и непрочитанные обновления по заявкам.
staff-test Тестовое письмо. Оно сообщает, какие заголовки сохраняет транспорт, и перечисляет оформление, в котором нарисовано.
staff-channel Канал уведомлений email публикует событие в командный ящик.

Темы писем сотрудникам несут проект и номер заявки: [Support] Invoice missing #1042. Пока входящая почта умеет принимать, письмо сотруднику об одной заявке заканчивается «Reply to this email to answer from your inbox.» или «Reply to this email to add an internal note.», когда оно показывает заметку. О том, когда они отправляются, см. Уведомления.

Фирменное оформление почты

Каждое письмо Helpdesk Pro, клиентское и для сотрудников, рисуется в одном макете: скрытый прехедер, шапка с вашим логотипом или названием компании, белая карточка с тонкой акцентной линией сверху и тихий подвал с причиной письма и ссылкой отписки. Ширина не более 600px, одна колонка, поэтому он читается на ширине телефона.

Задайте оформление в Email & Notifications → Look and Branding на странице настроек плагина или в email.brand в user/config/plugins/helpdesk-pro.yaml. Ни один шаблон не редактируется, и все письма меняются вместе.

email:
  brand:
    name: 'Acme Support'                  # пусто использует заголовок сайта
    logo: /user/images/acme-logo.png      # или https://…; пусто или `none` показывает название
    logo_dark: /user/images/acme-logo-light.png
    logo_width: 140                       # px, 40–320
    show_name: false                      # показывать название рядом с логотипом
    accent_color: '#12805c'               # ссылки, заголовки заявок, линия сверху карточки
    button_color: '#12805c'               # кнопки
    font: system                          # system | helvetica | verdana | trebuchet | georgia | custom
    font_custom: ''                       # набор шрифтов, когда font равно custom
    footer: 'Acme Ltd · 1 Main Street · [acme.com](https://acme.com)'
    appearance: auto                      # auto | light | dark
Ключ По умолчанию Что делает
email.brand.name пусто Название компании в шапке, подписях, имени отправителя («Anna at Acme Support») и тегах тем для сотрудников. Пусто использует заголовок сайта, иначе «Helpdesk».
email.brand.logo пусто Изображение шапки. Путь на этом сайте делается абсолютным от адреса сайта; адрес https:// используется как есть. Пусто или none даёт текстовую шапку с названием. Используйте PNG или JPG вдвое шире отрисовки: Gmail не показывает SVG.
email.brand.logo_dark пусто Светлая версия логотипа для почтовых приложений в тёмном режиме. Без неё тёмный логотип на прозрачном фоне может исчезнуть на тёмном фоне.
email.brand.logo_width 140 Ширина, в которой рисуется логотип, ограничена 40–320px.
email.brand.show_name false Печатать название рядом с логотипом тоже, для логотипа, который является только символом. Без логотипа название показывается всегда.
email.brand.accent_color #1f6feb Ссылки, темы заявок, имена файлов и линия сверху карточки. В тёмном режиме для ссылок используется более светлый оттенок.
email.brand.button_color #1f6feb Каждая кнопка. Текст кнопки белый или почти чёрный, когда цвет светлый.
email.brand.font system Один из пяти наборов, которые есть в каждом почтовом клиенте: system (шрифт интерфейса читателя), helvetica, verdana, trebuchet, georgia или custom.
email.brand.font_custom пусто Набор шрифтов, когда font равно custom, например Inter, Arial, sans-serif. Сохраняются только имена, пробелы, запятые и дефисы.
email.brand.footer пусто Необязательная строка под каждым письмом в Markdown: адрес, подпись, ссылка.
email.brand.appearance auto Светлый или тёмный вид (см. ниже).

Цвета должны быть в hex (#abc или #aabbcc); всё остальное откатывается к значению по умолчанию. Чтобы увидеть результат в настоящем ящике, используйте Send a test email: тест рисуется в сохранённом оформлении и перечисляет использованные настройки.

Светлый и тёмный вид

email.brand.appearance решает, как письмо выглядит в почтовых приложениях с тёмным режимом:

Значение Что происходит
auto (по умолчанию) Одно письмо, которое почтовое приложение каждого читателя показывает светлым или тёмным в соответствии с его настройкой. Apple Mail, iOS Mail и Outlook.com следуют prefers-color-scheme и получают тёмный фон, тёмную карточку, светлый текст, более светлые ссылки и logo_dark, когда он задан.
light Тёмные стили опускаются, и почтовые приложения просят не затемнять письмо.
dark Каждое письмо рисуется тёмным для всех, в том числе inline, поэтому остаётся тёмным в приложениях, отбрасывающих блок <style>. Использует logo_dark, когда он задан.

Приложения Gmail и Outlook всё равно могут перекрашивать письмо в своём тёмном режиме, что бы здесь ни было указано. Макет это переживает, потому что каждая панель и кнопка несут собственный фон.

Одно оформление на сайт

У проекта может быть свой почтовый псевдоним, но оформление принадлежит сайту: клиент видит один справочный центр, а ссылки для входа и дайджесты не принадлежат ни одному проекту. Сайт, который хочет оформление на проект, может задать его в событии onHelpdeskMailBuild (см. Расширение), где vars.brand заполняется до запуска слушателей.

Переопределение шаблонов почты

Всё сверх настроек — собственное переопределение шаблонов Grav. Файл по тому же пути в вашей теме побеждает:

user/themes/<your-theme>/templates/emails/helpdesk/client-reply.html.twig
user/themes/<your-theme>/templates/emails/helpdesk/partials/layout.html.twig

Скопируйте файл плагина из user/plugins/helpdesk-pro/templates/emails/helpdesk/ как отправную точку. Переопределите partials/layout.html.twig, чтобы изменить оформление всех писем сразу. Фоновый процесс загружает тему перед отрисовкой, поэтому ваша копия используется и для почты, которую отправляет процесс.

Каждое письмо — пара <name>.html.twig и <name>.txt.twig. Текстовая часть имеет то же содержимое в том же порядке и каждую ссылку HTML-части.

Блоки макета — preheader, header, content, footer и brand_footer. Новое письмо от дополнения или темы расширяет emails/helpdesk/partials/layout.html.twig, заполняет preheader и content и включает partials/button.html.twig с url и label для своей кнопки (style: 'ghost' для обведённой, align: 'center', margin).

Partial Что рисует
partials/layout.html.twig Рамку
partials/button.html.twig Кнопку
partials/client-footer.html.twig, .txt.twig Подвал клиента
partials/staff-footer.html.twig, .txt.twig Подвал сотрудника
partials/staff-message.html.twig Одно сообщение в письме сотруднику; заметка подсвечена и помечена
partials/ticket-row.html.twig Одну заявку в пакете и дайджесте
partials/ticket-meta.html.twig #number · project с плашками статуса и приоритета
partials/pill.html.twig Плашку статуса или приоритета
partials/files.html.twig, .txt.twig Файлы сообщения
partials/csat.html.twig «How did we do?» с тремя кнопками
partials/staff-fields.html.twig Значения пользовательских полей заявки под уведомлением сотруднику

Переменные шаблонов

Каждый шаблон получает subject, site (название компании), recipient.name, brand (проверенное оформление: name, logo, logo_dark, logo_width, show_name, accent, accent_dark, button, button_text, font, footer_html, footer_text, site_url) и links (ticket и portal для клиентов, desk для сотрудников и unsubscribe).

Клиентские шаблоны также получают inbound (доходят ли ответы по почте до службы поддержки), agent, ticket.subject, ticket.status (словами клиента), message и reply (каждое с body_html, body_text, author_name, attachments) и для сортировки — decision, reason и duplicate_url.

Шаблоны сотрудников получают item (первое уведомление: sentence, count, message, link, ticket), items, groups (по заявке: ticket, link, items, latest), counts для дайджеста, inbound и reply_as (note, когда ответ на это письмо становится внутренней заметкой, иначе reply). Каждый ticket — {id, subject, project, status, status_label, priority, priority_label}.

attachments каждого сообщения — список {id, message_id, name, url, size, visibility}. В клиентской почте файлы ответа также несут mode (inline, attached или link), а встроенное изображение — cid, width и in_body.

Тела сообщений — сохранённый очищенный HTML, печатаемый через |raw. Всё остальное явно экранируется через |e, поэтому сайт, где Twig не экранирует автоматически, тоже в безопасности.

Заголовки

Заголовок Значение
Message-ID hd.{ticket}.{kind}{row}.{random}@{domain}: вид m для ответа, n для уведомления, a для автоматической почты. Домен — от адреса поддержки.
In-Reply-To Новейшее письмо в цепочке этого получателя по заявке.
References Сначала <hd.{ticket}.root@{domain}>, затем до девяти недавних id. Виртуальный корень связывает все письма об одной заявке в цепочку в Gmail и Apple Mail.
From Адрес поддержки с именем «{agent} at {site}» в ответах клиентам и «{site} Helpdesk» в остальных случаях (просто «{site}», когда заголовок сайта уже содержит Helpdesk, Help или Support). Адрес никогда не меняется, поэтому выравнивание SPF и DKIM не затрагивается.
Reply-To Опускается, пока входящая почта выключена. Когда включена — адрес ответа с токеном для этого получателя (см. Адреса ответа).
List-Unsubscribe Подписанная ссылка отказа в один клик: ticket:{id} в клиентской почте, all в почте сотрудникам. При включённой входящей почте также адрес mailto: отписки в письмах об одной заявке.
List-Unsubscribe-Post List-Unsubscribe=One-Click (RFC 8058), чтобы Gmail и Apple Mail могли показать свою кнопку отписки.
Auto-Submitted auto-generated в уведомлениях, пакетах, дайджестах, подтверждениях, письмах сортировки и решения; не ставится в письмах с ответом человека.
X-Auto-Response-Suppress All, что останавливает автоответы Exchange «вне офиса» на вашу почту.
X-Helpdesk-Ref {ticket}.{signature} для диагностики.

Каждый отправленный Message-ID сохраняется с его заявкой, человеком и аудиторией до передачи письма транспорту, поэтому повторная попытка использует его же, а ответы можно сопоставить при приходе входящей почты. HTML-часть каждого письма о заявке также заканчивается невидимым маркером ref:{ticket}.{signature} — совпадением последней надежды для ответа, пришедшего без заголовков группировки.

Транспорты, превращающие сообщение в вызов API, иногда отбрасывают заголовки. Тестовое письмо сообщает, что сохраняет ваш транспорт: когда пользовательские заголовки отброшены, группировка откатывается к теме; когда отброшены заголовки отписки, почтовые приложения не показывают кнопку отписки (ссылки в подвале продолжают работать).

Ссылки в подвале и в List-Unsubscribe ведут в справочный центр:

Маршрут Что делает
GET {mount}/unsubscribe?p=&s=&sig= Страница подтверждения с одной кнопкой. Открытие ссылки ничего не меняет, потому что сканеры ссылок открывают ссылки в письмах.
POST {mount}/unsubscribe Применяет ссылку: ticket:{id} отключает уведомления по этому обращению для человека; all выключает почту Helpdesk (режим почты сотрудника становится off, клиент перестаёт получать любую почту Helpdesk). Почтовые приложения отправляют сюда List-Unsubscribe=One-Click; вход не нужен, потому что подпись и есть доказательство.

p — человек, s — область, а sig — HMAC по обоим с секретом, хранящимся в базе данных, поэтому ссылку нельзя изменить, чтобы отписать кого-то другого. Повторное применение ссылки ничего не меняет.

Как работает отправка

  1. Событие (ответ, изменение статуса) или запасной вариант уведомления ставит строку в очередь почты: шаблон, получатель и только id. Ничего из того, что показывает шаблон, в очереди не хранится.
  2. Задание mail.send разбирает очередь. Письмо, срок которого наступил, также отправляется сразу после поставившего его в очередь запроса, поэтому клиент видит ответ сотрудника через секунды.
  3. Каждое письмо строится при отправке: доступ получателя проверяется снова, содержимое загружается, и обе части отрисовываются.
  4. Неудачная отправка повторяется через 5, затем 10 минут и окончательно завершается ошибкой на третьей попытке. Письмо, чей получатель потерял доступ, выключил почту или был удалён, тихо отменяется.
  5. Ежедневное задание maintenance.prune удаляет отправленные, неудачные и отменённые строки старше jobs.retention_days. Message-ID остаются для группировки и сопоставления ответов.

Ссылки в письме создаются при его сборке. Фоновый процесс, запущенный из cron, не может знать адрес вашего сайта, поэтому каждый веб-запрос записывает его, а процесс использует последний записанный. Установите email.site_url, когда почта уходит до того, как кто-либо посетил сайт, или когда сайт доступен по адресу, которым клиенты не пользуются (внутреннее имя за прокси). Если адрес неизвестен, письмо всё равно уходит с относительными ссылками и без List-Unsubscribe, а журнал говорит «email links are relative: the site address is not known yet».

Настройки

На вкладке Email & Notifications настроек плагина:

Ключ По умолчанию Что делает
email.support_address пусто Адрес, на который пишут клиенты. С него отправляется почта, и на него приходят ответы. Его домен используется в Message-ID.
email.from_address пусто Только когда почта должна уходить с другого адреса. Пусто использует адрес поддержки, затем From плагина Email.
email.site_url пусто Адрес, с которого начинаются ссылки в письмах, например https://example.com. Пусто использует адрес последнего посещения сайта.
email.from_name_format {agent} at {site} Имя отправителя в ответах клиентам.
email.subject_tag false Добавляет видимый [#123] в темы клиентских писем. Группировке он никогда не нужен.
email.acknowledge_web_tickets true Отправлять client-received для обращений из справочного центра.
email.acknowledge_email_tickets true Отправлять client-received для обращений, пришедших по почте.
email.attach_max_mb 10 Сколько файлов ответа сотрудника клиентское письмо несёт само. 0 оставляет каждый файл ссылкой.
email.brand.* Как выглядит каждое письмо. См. Фирменное оформление почты.