Feishu Cloud Document to HTML Email: Architecture Redesign and Implementation
The article details a complete redesign of Feishu’s cloud‑document‑to‑HTML‑email converter, introducing IoC/DI architecture, inline‑style utilities, and specialized renderers for headings, lists, tables, images, code blocks, and equations, resulting in high‑fidelity Outlook‑compatible emails while shrinking the core code to under three hundred lines.
This article describes the redesign of an internal tool that converts Feishu cloud documents into HTML email format, addressing compatibility issues with various email clients, especially Windows Outlook.
Background : The original converter produced low‑fidelity output and could not handle many rich‑text blocks. A major overhaul was needed to improve block restoration, support Outlook’s limited HTML/CSS, and enable collaborative development.
System Architecture Redesign : The new design adopts Inversion of Control (IoC) and Dependency Injection (DI) to decouple preprocessing and rendering of document blocks. Core creation is handled by createDocTranspiler , which registers preprocessors and renderers via a switch‑case mechanism.
Style Conversion Utilities : To generate inline styles compatible with email clients, a utility converts React CSSProperties objects to CSS strings. Example implementation: const isUnitlessNumber = { fontWeight: true, lineClamp: true, lineHeight: true, /* ... */ }; const cssPropertyPrefixes = ['Webkit','ms','Moz','O']; Object.keys(isUnitlessNumber).forEach(p => { cssPropertyPrefixes.forEach(prefix => { isUnitlessNumber[`${prefix}${p.charAt(0).toUpperCase()}${p.slice(1)}`] = isUnitlessNumber[p]; }); }); export function addCSSPropertyUnit(property, value) { if (typeof value === 'number' && !isUnitlessNumber[property]) { return `${value}px`; } return value; } export function convertCSSPropertiesToInlineStyle(style) { const upperCaseReg = /[A-Z]/g; const inline = Object.keys(style).map(prop => `${prop.replace(upperCaseReg, m => `-${m.toLowerCase()}`)}: ${addCSSPropertyUnit(prop, style[prop])};`).join(' '); return inline ? `style="${inline}"` : ''; }
Heading Rendering : Headings (h1‑h9) are rendered as <div> with generated inline styles. Sample snippet: case BlockType.HEADING1: { const blockH1 = block as HeadingBlock; const align = blockH1.heading1.style.align; const styles = makeHeadingStyles({ type: block.block_type, align }); text += `<div ${styles.headingStyles}>${transpileTextElements(blockH1.block_id, blockH1.heading1.elements, isPreview)}</div>`; text += renderChildBlocks(blockH1.block_id); break; }
List Rendering : Both ordered and unordered lists are rebuilt with custom markers to match Feishu’s visual style. Marker rendering logic: export const listMarkRender = (type, block) => { const { depth = 1, order = 1 } = block; if (type === ListType.BULLET) { const styles = makeMarkerStyles(ListType.BULLET); const markers = ['•','◦','▪']; const marker = markers[(depth - 1) % 3]; return `<span ${styles.markContainerStyle}>${marker}</span>`; } else { const styles = makeMarkerStyles(ListType.ORDERED); const markerGenerator = n => { const cycle = (depth - 1) % 3; if (cycle === 0) return n; if (cycle === 1) return String.fromCharCode(96 + n); return toRoman(n); }; return `<span ${styles.markContainerStyle}>${markerGenerator(order)}.</span>`; } };
Table Rendering : Tables are rendered using pure <table> , <tr> , and <td> tags to satisfy Outlook’s lack of CSS support. The renderer tracks merged cells and generates appropriate colspan and rowspan attributes while applying inline width styles.
Image Handling : Images are resized based on max dimensions (820 px width, 780 px height) and whether they reside inside a table. The algorithm: function restrictImageSize(width, height, maxWidth = 820, maxHeight = 780) { if (width >= height - 50) { return width > maxWidth ? [maxWidth, Math.ceil(height * maxWidth / width)] : [width, height]; } else { return height > maxHeight ? [Math.ceil(width * maxHeight / height), maxHeight] : [width, height]; } }
Code Block Rendering : Code blocks are transformed into a two‑column table (line numbers + code) using <pre> tags for whitespace preservation. The renderer groups split lines, escapes HTML entities, and builds the final HTML structure.
Inline Equation Processing : Equations are converted to SVG via MathJax, then rasterized to PNG using a canvas, uploaded to CDN, and referenced as image attachments in the email. The pipeline includes enrichEquationElements , svgToImgThenUpload , and allSvgsToImgThenUpload functions.
Result : After the redesign, the tool achieves high‑fidelity conversion of Feishu documents—including headings, lists, tables, images, code blocks, and equations—into HTML emails that render correctly across major clients, with a reduced codebase (core transpiler ~158 lines, final rendering ~138 lines).
The article also discusses fallback rendering for unsupported blocks, user guidance, and future optimization plans.
DeWu Technology
A platform for sharing and discussing tech knowledge, guiding you toward the cloud of technology.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.