Render inbound email HTML body in admin inbox

Worker now extracts both text/plain and text/html MIME parts and sends
htmlBody in the webhook payload. Server stores it on the submission.
Admin inbox renders htmlBody in a sandboxed iframe when present, falling
back to plain text with whitespace preserved.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
nmemmert
2026-06-30 11:32:52 -04:00
parent 4b411f763f
commit 118314d0d7
3 changed files with 33 additions and 16 deletions
+16 -14
View File
@@ -84,35 +84,37 @@ function parseEmail(raw, message) {
const date = headers['date'] ?? new Date().toISOString()
const messageId = headers['message-id'] ?? ''
// Extract plain text body — skip MIME boundaries and HTML parts
const bodyLines = lines.slice(bodyStart)
const plainText = extractPlainText(raw, bodyLines)
const plainText = extractPart(raw, bodyLines, 'text/plain')
const htmlBody = extractPart(raw, bodyLines, 'text/html')
return {
from,
to,
subject,
body: plainText,
htmlBody: htmlBody || null,
date,
messageId,
source: 'inbound-email',
}
}
function extractPlainText(raw, bodyLines) {
// Look for Content-Type: text/plain section in multipart emails
const textPlainMatch = /Content-Type: text\/plain[^\r\n]*\r?\n(?:[^\r\n]+\r?\n)*\r?\n([\s\S]*?)(?=--|\z)/i.exec(raw)
if (textPlainMatch) {
return decodeEmailBody(textPlainMatch[1]).trim()
function extractPart(raw, bodyLines, contentType) {
const escaped = contentType.replace('/', '\\/')
const re = new RegExp(`Content-Type: ${escaped}[^\\r\\n]*\\r?\\n(?:[^\\r\\n]+\\r?\\n)*\\r?\\n([\\s\\S]*?)(?=--|$)`, 'i')
const match = re.exec(raw)
if (match) return decodeEmailBody(match[1]).trim()
if (contentType === 'text/plain') {
const joined = bodyLines.join('\n')
if (/<[a-z][\s\S]*>/i.test(joined)) {
return joined.replace(/<[^>]+>/g, '').replace(/&nbsp;/g, ' ').replace(/&amp;/g, '&').replace(/\s{3,}/g, '\n\n').trim()
}
return joined.trim()
}
// Fallback: join body lines, strip HTML tags if present
const joined = bodyLines.join('\n')
if (/<[a-z][\s\S]*>/i.test(joined)) {
return joined.replace(/<[^>]+>/g, '').replace(/&nbsp;/g, ' ').replace(/&amp;/g, '&').replace(/\s{3,}/g, '\n\n').trim()
}
return joined.trim()
return null
}
function decodeEmailBody(text) {