/** * Comment Notification Emails * * Sends email notifications to content authors when comments are * approved on their content. Used by: * - Public comment POST route (comment:afterCreate, if auto-approved) * - Admin moderation route (comment:afterModerate, when approving) */ import type { Kysely } from "kysely"; import { escapeHtml } from "../api/escape.js"; import type { Database } from "../database/types.js"; import { validateIdentifier } from "../database/validate.js"; import type { EmailPipeline } from "../plugins/email.js"; import type { EmailMessage } from "../plugins/types.js"; const NOTIFICATION_SOURCE = "emdash-comments"; const MAX_EXCERPT_LENGTH = 500; const CRLF_RE = /[\r\n]/g; export interface CommentNotificationData { commentAuthorName: string; commentBody: string; contentTitle: string; collection: string; adminBaseUrl: string; } /** * Build an email notification for a new comment. */ export function buildCommentNotificationEmail( to: string, data: CommentNotificationData, ): EmailMessage { const title = data.contentTitle || `${data.collection} item`; const subject = `New comment on "${title}"`.replace(CRLF_RE, " "); const excerpt = data.commentBody.length > MAX_EXCERPT_LENGTH ? data.commentBody.slice(0, MAX_EXCERPT_LENGTH) + "..." : data.commentBody; const adminUrl = `${data.adminBaseUrl}/admin/comments`; const text = [ `${data.commentAuthorName} commented on "${title}":`, "", excerpt, "", `View in admin: ${adminUrl}`, ].join("\n"); const html = [ `
${escapeHtml(data.commentAuthorName)} commented on “${escapeHtml(title)}”:
`, `${escapeHtml(excerpt)}`, ``, ].join("\n"); return { to, subject, text, html }; } /** * Send a comment notification to the content author if all conditions are met: * 1. Comment status is "approved" * 2. Content author exists and has an email * 3. Email provider is configured * 4. Commenter is not the content author (no self-notifications) * * Returns true if the email was sent, false if skipped. */ export async function sendCommentNotification(params: { email: EmailPipeline; comment: { authorName: string; authorEmail: string; body: string; status: string; collection: string; }; contentTitle?: string; contentAuthor?: { email: string; name: string | null }; adminBaseUrl: string; }): Promise