Put the reply text inside question notification emails
The notification only said that the school had answered and linked into the LMS, so a student had to log in to read two lines. Delivered mail plus an unread thread was the common outcome — one such thread was a pre-sale question that never converted. The mail now carries the answer itself (capped at 2000 chars, with a pointer to the thread when longer or when files are attached). Quoted blocks are HTML-escaped, which they were not before. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -78,6 +78,8 @@ export async function POST(
|
|||||||
question.user.name,
|
question.user.name,
|
||||||
question.title,
|
question.title,
|
||||||
id,
|
id,
|
||||||
|
text.trim(),
|
||||||
|
Boolean(safeFiles?.length),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const staff = await prisma.user.findMany({
|
const staff = await prisma.user.findMany({
|
||||||
|
|||||||
+29
-5
@@ -89,11 +89,19 @@ function btn(href: string, label: string) {
|
|||||||
</table>`;
|
</table>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function escapeHtml(text: string) {
|
||||||
|
return text
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/"/g, """);
|
||||||
|
}
|
||||||
|
|
||||||
function quote(text: string) {
|
function quote(text: string) {
|
||||||
return `<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="margin:16px 0;">
|
return `<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="margin:16px 0;">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="border-left:3px solid #323232;padding:10px 14px;background-color:#E8F0D8;">
|
<td style="border-left:3px solid #323232;padding:10px 14px;background-color:#E8F0D8;">
|
||||||
<p style="margin:0;font-family:'Courier New',Courier,monospace;font-size:13px;line-height:1.6;color:#323232;">${text.replace(/\n/g, "<br/>")}</p>
|
<p style="margin:0;font-family:'Courier New',Courier,monospace;font-size:13px;line-height:1.6;color:#323232;">${escapeHtml(text).replace(/\n/g, "<br/>")}</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>`;
|
</table>`;
|
||||||
@@ -335,23 +343,39 @@ export async function sendQuestionCreatedEmail(
|
|||||||
}).catch((e) => console.error("[email] sendQuestionCreatedEmail:", e));
|
}).catch((e) => console.error("[email] sendQuestionCreatedEmail:", e));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Текст ответа кладём прямо в письмо: раньше письмо звало в LMS «прочитать ответ»,
|
||||||
|
// и ради двух строк человеку надо было авторизоваться — часть студентов так и не
|
||||||
|
// открывала тред (isRead=false при доставленном письме).
|
||||||
|
const REPLY_PREVIEW_LIMIT = 2000;
|
||||||
|
|
||||||
export async function sendQuestionReplyEmail(
|
export async function sendQuestionReplyEmail(
|
||||||
to: string,
|
to: string,
|
||||||
studentName: string,
|
studentName: string,
|
||||||
questionTitle: string,
|
questionTitle: string,
|
||||||
questionId: string,
|
questionId: string,
|
||||||
|
replyText: string,
|
||||||
|
hasFiles = false,
|
||||||
) {
|
) {
|
||||||
const school = await getSchoolName();
|
const school = await getSchoolName();
|
||||||
|
const trimmed = replyText.trim();
|
||||||
|
const preview =
|
||||||
|
trimmed.length > REPLY_PREVIEW_LIMIT
|
||||||
|
? `${trimmed.slice(0, REPLY_PREVIEW_LIMIT)}…`
|
||||||
|
: trimmed;
|
||||||
|
const truncated = trimmed.length > REPLY_PREVIEW_LIMIT;
|
||||||
|
|
||||||
await getResend().emails.send({
|
await getResend().emails.send({
|
||||||
from: FROM,
|
from: FROM,
|
||||||
to,
|
to,
|
||||||
subject: `Ответ на ваш вопрос`,
|
subject: `Ответ на ваш вопрос`,
|
||||||
html: base(`
|
html: base(`
|
||||||
<p ${p}>Привет, ${studentName}!</p>
|
<p ${p}>Привет, ${studentName}!</p>
|
||||||
<p ${p}>Школа ответила на ваш вопрос:</p>
|
<p ${p}>Школа ответила на ваш вопрос «${escapeHtml(questionTitle)}»:</p>
|
||||||
${quote(questionTitle)}
|
${quote(preview)}
|
||||||
<p ${pLast}>Откройте тред чтобы прочитать ответ:</p>
|
${truncated ? `<p ${p}>Ответ длинный — целиком он в треде.</p>` : ""}
|
||||||
${btn(`${BASE_URL}/questions/${questionId}`, "Читать ответ")}
|
${hasFiles ? `<p ${p}>К ответу приложены файлы — открыть их можно в треде.</p>` : ""}
|
||||||
|
<p ${pLast}>Ответить или посмотреть переписку целиком:</p>
|
||||||
|
${btn(`${BASE_URL}/questions/${questionId}`, "Открыть тред")}
|
||||||
`, school),
|
`, school),
|
||||||
}).catch((e) => console.error("[email] sendQuestionReplyEmail:", e));
|
}).catch((e) => console.error("[email] sendQuestionReplyEmail:", e));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user