Исходящая почта¶
Helpdesk Pro отправляет два вида писем. Клиенты получают несколько писем о важных событиях, которые читаются как личное письмо от помогающего им человека. Сотрудники получают уведомления по почте, когда не прочитали их сначала в рабочей области. Всё идёт через плагин Email для Grav, поэтому какой бы движок ни использовал ваш сайт (SMTP, Postmark, Mailgun, SES, Resend…), он отправляет и почту Helpdesk Pro.
Настройка исходящей почты¶
- Настройте плагин Email и проверьте, что он отправляет:
bin/plugin email test-email --to=you@example.com. - Установите
email.support_addressв адрес, на который пишут клиенты. Helpdesk Pro отправляет с него, и ответы приходят на него. Пока входящая почта не настроена, ответы клиентов попадают в этот ящик. - Оформите письма в разделе Look and Branding (см. Фирменное оформление почты).
- Отправьте тест: Send a test email на экране рабочей области Operations → Email или
POST /helpdesk-pro/mail/testс{"to": "you@example.com"}(MCPsend_test_email, правоhelpdesk-pro.settings). Ответ сообщает, отправлено ли письмо, адрес отправителя и сохраняет ли ваш транспорт пользовательские заголовки и заголовки отписки. - Убедитесь, что 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 по обоим с секретом, хранящимся в базе данных, поэтому ссылку нельзя изменить, чтобы отписать кого-то другого. Повторное применение ссылки ничего не меняет.
Как работает отправка¶
- Событие (ответ, изменение статуса) или запасной вариант уведомления ставит строку в очередь почты: шаблон, получатель и только id. Ничего из того, что показывает шаблон, в очереди не хранится.
- Задание
mail.sendразбирает очередь. Письмо, срок которого наступил, также отправляется сразу после поставившего его в очередь запроса, поэтому клиент видит ответ сотрудника через секунды. - Каждое письмо строится при отправке: доступ получателя проверяется снова, содержимое загружается, и обе части отрисовываются.
- Неудачная отправка повторяется через 5, затем 10 минут и окончательно завершается ошибкой на третьей попытке. Письмо, чей получатель потерял доступ, выключил почту или был удалён, тихо отменяется.
- Ежедневное задание
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.* |
Как выглядит каждое письмо. См. Фирменное оформление почты. |