В подключённый ящик пришло письмо, или письмо ушло из ящика. Письмо приходит тем же уведомлением, что и сообщение мессенджера: текст — в textMessageData, вложения — файлами. Сверх этого в messageData есть emailMessageData — тема, цепочка, адресаты, папка.
| Что | typeWebhook |
Флаг настроек |
|---|---|---|
| новое письмо в отслеживаемых папках | incomingMessageReceived |
incomingWebhook |
| своё письмо: «Отправленные», ответ из почтовой программы | outgoingMessageReceived |
outgoingMessageWebhook |
| письмо, отправленное через API | outgoingAPIMessageReceived |
outgoingAPIMessageWebhook |
| недоставка вашего письма | outgoingMessageStatus, status: "failed" |
outgoingWebhook |
| отчёт о прочтении | outgoingMessageStatus, status: "read" |
outgoingWebhook |
Отслеживаемые папки — настройка emailFolders, по умолчанию «Входящие» и «Отправленные». См. Настройки ящика.
Пример
Мария написала на info@school.ru:
{
"typeWebhook": "incomingMessageReceived",
"instanceData": {"idInstance": 7100000001, "wid": "info@school.ru", "typeInstance": "email", "channel": "email"},
"timestamp": 1791823658,
"idMessage": "c62b698304229a98aad3",
"eventId": "msg:maria@client.ru:c62b698304229a98aad3",
"senderData": {
"chatId": "maria@client.ru", "chatName": "Мария Иванова", "chatType": "user",
"sender": "maria@client.ru", "senderName": "Мария Иванова", "senderType": "user",
"senderContactName": "", "senderPhoneNumber": 0
},
"messageData": {
"typeMessage": "textMessage",
"textMessageData": {"textMessage": "Здравствуйте! Хочу записать сына на пробное занятие. Когда можно прийти?"},
"emailMessageData": {
"subject": "Запись на пробное",
"messageId": "CAF1xq7d2Lm9VbT0@mail.gmail.com",
"threadId": "c62b698304229a98aad3",
"inReplyTo": null,
"from": "maria@client.ru",
"to": ["info@school.ru"],
"cc": [],
"replyTo": [],
"auto": null,
"folder": "inbox",
"signature": "Мария Иванова\n+7 999 123-45-67",
"quoteCut": false,
"form": null,
"truncated": false,
"mailbox": "INBOX",
"uid": 1532,
"hasHtml": true
}
}
}
Поля
| Поле | Описание |
|---|---|
idMessage |
id письма в канале: 20 шестнадцатеричных знаков от Message-ID, одинаковый во всех папках |
senderData.chatId |
адрес собеседника в нижнем регистре — куда уйдёт ответ: Reply-To, если он не ваш, иначе отправитель |
textMessageData.textMessage |
текст письма без цитаты прошлых писем и без подписи |
Поля emailMessageData:
| Поле | Описание |
|---|---|
subject |
тема |
messageId |
заголовок Message-ID, без угловых скобок |
threadId |
id первого письма цепочки — общий у всех писем переписки |
inReplyTo |
Message-ID письма, на которое отвечают; null у первого письма |
from, to, cc, replyTo |
адреса из заголовков, в нижнем регистре |
auto |
вид письма, если это не письмо человека: auto_reply — автоответ, bulk — рассылка, service — уведомление сервиса; null — письмо человека или заявка |
folder |
роль папки: inbox, sent, spam, drafts, trash, archive, all, other |
mailbox, uid |
папка на сервере и номер письма в ней — по ним письмо адресуют методы почты |
signature |
отрезанная подпись: имя, должность, телефоны |
quoteCut |
true — цитата прошлых писем отрезана |
form |
разобранная заявка с формы сайта — ниже; иначе null |
truncated |
true — письмо больше предела, текст сокращён: целиком — в getEmail |
hasHtml |
у письма есть html-версия |
Остальные поля конверта — в статье Формат уведомления.
Ответ в цепочке
Письмо с заголовком In-Reply-To приходит как Ответ цитатой: stanzaId — idMessage письма, на которое ответили, participant пустой. Текст — без цитаты, quoteCut: true:
"messageData": {
"typeMessage": "quotedMessage",
"extendedTextMessageData": {"text": "Суббота подходит, спасибо!", "stanzaId": "b86fac5e3d96da636ca0", "participant": ""},
"quotedMessage": {"stanzaId": "b86fac5e3d96da636ca0", "participant": ""},
"emailMessageData": {
"subject": "Re: Запись на пробное",
"messageId": "CAF1xq7d2Lm9VbT1@mail.gmail.com",
"threadId": "c62b698304229a98aad3",
"inReplyTo": "20261013091500.4f2a@school.ru",
"from": "maria@client.ru",
"to": ["info@school.ru"],
"cc": [],
"replyTo": [],
"auto": null,
"folder": "inbox",
"signature": "",
"quoteCut": true,
"form": null,
"truncated": false,
"mailbox": "INBOX",
"uid": 1533,
"hasHtml": true
}
}
Вложения
Письмо с вложениями приходит как Файлы и альбомы: typeMessage — по первому вложению, остальные — в additionalFiles, у каждого своя ссылка. Текст письма — в caption, emailMessageData — рядом. Письмо целиком в формате .eml — getEmailRaw.
Заявка с формы сайта
Письмо совпало с правилом emailForms — в form разобранные поля. Собеседник — почта из формы; если её нет, Reply-To письма, а если нет и его — отдельный чат form:{idMessage}, ответить в который письмом нельзя:
"emailMessageData": {
"subject": "Новая заявка с сайта",
"messageId": "1791823900.77213@forms.school.ru",
"threadId": "c04ef2a6ffdb12894548",
"inReplyTo": null,
"from": "forms@school.ru",
"to": ["info@school.ru"],
"cc": [],
"replyTo": [],
"auto": null,
"folder": "inbox",
"signature": "",
"quoteCut": false,
"form": {
"rule": "site",
"fields": {"name": "Мария", "phone": "+7 999 123-45-67", "message": "Хочу на пробное"},
"missing": [],
"complete": true,
"phone": "+79991234567",
"email": null,
"name": "Мария"
},
"truncated": false,
"mailbox": "INBOX",
"uid": 1540,
"hasHtml": false
}
Поле form |
Описание |
|---|---|
rule |
имя правила из emailForms |
fields |
заполненные поля формы: ключ — имя поля правила |
missing |
обязательные поля, которых нет |
complete |
true — все обязательные поля есть |
phone |
телефон в виде +79991234567, если нашёлся |
email, name |
почта и имя из формы |
Здесь в форме нет почты, поэтому chatId — "form:c04ef2a6ffdb12894548": ответить можно по телефону в мессенджер. Правила форм — Заявки с форм сайта.
Свои письма
Письмо из «Отправленных» или ответ из почтовой программы — outgoingMessageReceived: chatId — первый получатель, sender — адрес ящика, folder: "sent".
У письма через API (sendEmail, replyEmail, sendMessage) — outgoingAPIMessageReceived, а emailMessageData короче:
"emailMessageData": {
"subject": "Re: Запись на пробное",
"messageId": "20261013091500.4f2a@school.ru",
"threadId": "c62b698304229a98aad3",
"inReplyTo": "CAF1xq7d2Lm9VbT0@mail.gmail.com",
"to": ["maria@client.ru"],
"cc": [],
"bcc": [],
"refused": {}
}
refused — адреса, которые почтовый сервер отверг сразу при отправке, с причиной.
Недоставка и прочтение
Отчёты почтовых серверов приходят не письмами, а статусами вашего письма — outgoingMessageStatus:
- недоставка —
status: "failed", причина из отчёта — вdescription, например"550 5.1.1 User unknown"; - отчёт о прочтении —
status: "read", если получатель его отправил.
Какие письма приходят
- Все письма отслеживаемых папок: рассылки и уведомления сервисов помечены в
auto, но не отсеяны. Не нужны — настройкаemailSkipBulk: "yes". - С момента подключения ящика — или с даты
sinceиз connectMailbox, не больше 200 писем. Старые письма читайте getMessages и searchMessages. - После простоя Gratio дочитывает пропущенные письма, до 200. Больше — придёт syncGap.
Как обработать
Завести обращение в helpdesk: одна цепочка — одно обращение, рассылки — мимо:
def on_email(body: dict) -> None:
if body["typeWebhook"] != "incomingMessageReceived" or body["instanceData"]["channel"] != "email":
return
data = body["messageData"]
mail = data["emailMessageData"]
if mail["auto"]: # автоответ, рассылка, уведомление сервиса
return
if data["typeMessage"] == "textMessage":
text = data["textMessageData"]["textMessage"]
elif "fileMessageData" in data:
text = data["fileMessageData"]["caption"]
else:
text = data.get("extendedTextMessageData", {}).get("text", "")
ticket = find_or_create_ticket(thread=mail["threadId"], subject=mail["subject"])
add_ticket_message(ticket, author=body["senderData"]["chatId"], text=text, letter_id=body["idMessage"])
if mail["form"]:
attach_lead(ticket, mail["form"])
Что дальше
- Почта через API — ящик, папки и письма.
- getEmail — письмо целиком.
- Почтовый ящик в helpdesk — ящик в helpdesk по шагам.
