Fix base64-encoded HTML email bodies not rendering; v1.1.18
Inbound emails from some clients (Outlook, mobile) send htmlBody as base64. Decode it on the server before storing (future emails) and on the client before passing to the iframe srcDoc (existing stored emails). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "siteforge",
|
"name": "siteforge",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.1.17",
|
"version": "1.1.18",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -6,6 +6,18 @@ import { MAX_CONTACT_SUBMISSIONS } from '../config.js'
|
|||||||
// RFC 5322 msg-id: "<" printable-ASCII-no-whitespace ">"
|
// RFC 5322 msg-id: "<" printable-ASCII-no-whitespace ">"
|
||||||
const MESSAGE_ID_RE = /^<[\x21-\x7E]+>$/
|
const MESSAGE_ID_RE = /^<[\x21-\x7E]+>$/
|
||||||
|
|
||||||
|
function decodeHtmlBody(raw) {
|
||||||
|
if (typeof raw !== 'string' || !raw.trim()) return null
|
||||||
|
const s = raw.trim()
|
||||||
|
if (s.startsWith('<')) return s // already plain HTML
|
||||||
|
try {
|
||||||
|
const decoded = Buffer.from(s.replace(/\s+/g, ''), 'base64').toString('utf8')
|
||||||
|
return decoded.trimStart().startsWith('<') ? decoded : s
|
||||||
|
} catch {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function register(app) {
|
export function register(app) {
|
||||||
app.post('/api/inbound-email', (req, res) => {
|
app.post('/api/inbound-email', (req, res) => {
|
||||||
const secret = process.env.INBOUND_EMAIL_SECRET
|
const secret = process.env.INBOUND_EMAIL_SECRET
|
||||||
@@ -47,7 +59,7 @@ export function register(app) {
|
|||||||
name: fromName || fromEmail,
|
name: fromName || fromEmail,
|
||||||
email: fromEmail,
|
email: fromEmail,
|
||||||
message: [subject ? `Subject: ${subject}` : '', body ?? ''].filter(Boolean).join('\n\n'),
|
message: [subject ? `Subject: ${subject}` : '', body ?? ''].filter(Boolean).join('\n\n'),
|
||||||
htmlBody: typeof htmlBody === 'string' && htmlBody.trim() ? htmlBody.trim() : null,
|
htmlBody: decodeHtmlBody(htmlBody),
|
||||||
messageType: 'general',
|
messageType: 'general',
|
||||||
subscribe: false,
|
subscribe: false,
|
||||||
archived: false,
|
archived: false,
|
||||||
|
|||||||
+6
-1
@@ -82,6 +82,11 @@ function formatDate(value: string | null | undefined): string {
|
|||||||
return Number.isNaN(d.getTime()) ? '—' : d.toLocaleString()
|
return Number.isNaN(d.getTime()) ? '—' : d.toLocaleString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function decodeHtmlBody(raw: string): string {
|
||||||
|
if (!raw || raw.trimStart().startsWith('<')) return raw
|
||||||
|
try { return atob(raw.replace(/\s+/g, '')) } catch { return raw }
|
||||||
|
}
|
||||||
|
|
||||||
function formatShortDate(value: string | null | undefined): string {
|
function formatShortDate(value: string | null | undefined): string {
|
||||||
if (!value) return '—'
|
if (!value) return '—'
|
||||||
const d = new Date(value)
|
const d = new Date(value)
|
||||||
@@ -671,7 +676,7 @@ function EmailClient({ onLogout }: { onLogout: () => void }) {
|
|||||||
<div className="em-detail-body">
|
<div className="em-detail-body">
|
||||||
{selected.htmlBody ? (
|
{selected.htmlBody ? (
|
||||||
<iframe
|
<iframe
|
||||||
srcDoc={selected.htmlBody}
|
srcDoc={decodeHtmlBody(selected.htmlBody!)}
|
||||||
sandbox="allow-same-origin"
|
sandbox="allow-same-origin"
|
||||||
className="em-detail-iframe"
|
className="em-detail-iframe"
|
||||||
onLoad={e => {
|
onLoad={e => {
|
||||||
|
|||||||
Reference in New Issue
Block a user