Chrome Extension HTML Email P (2026)
Chrome Extension HTML Email Preview: A Developer Guide
Creating a Chrome extension that previews HTML emails directly in the browser solves a common problem for developers and email marketers. Instead of sending test emails to yourself or using third-party services, you can render HTML email templates instantly within Chrome. This guide walks you through building a functional HTML email preview extension from scratch.
Why Build an Email Preview Extension
Email development presents unique challenges. Email clients render HTML differently, and testing requires sending emails or using external preview tools. A Chrome extension that previews HTML email locally gives you instant feedback on your templates without leaving your development environment.
The extension works well for developers building email templates, marketers testing campaign designs, and QA teams verifying email rendering across different contexts.
Extension Architecture
A Chrome extension for email preview operates through several interconnected components. The manifest defines permissions and entry points. Content scripts inject functionality into web pages. Background scripts handle messaging and storage. Popup interfaces provide user controls.
For an HTML email preview extension, you need three primary capabilities: reading HTML content from the active tab, rendering that content in a preview pane, and handling inline styles that email clients commonly use.
Setting Up the Manifest
Every Chrome extension requires a manifest file. For an email preview tool, you’ll need specific permissions to access tab content:
{
"manifest_version": 3,
"name": "HTML Email Preview",
"version": "1.0.0",
"description": "Preview HTML emails directly in Chrome",
"permissions": [
"activeTab",
"scripting",
"storage"
],
"host_permissions": [
"<all_urls>"
],
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
},
"background": {
"service_worker": "background.js"
}
}
The activeTab permission lets your extension access the current page when the user invokes it. The scripting permission enables JavaScript execution within pages for extracting and rendering content.
Building the Popup Interface
The popup provides controls for preview configuration. Users can adjust viewport width, toggle desktop and mobile views, and copy the rendered HTML:
<!-- popup.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
* { box-sizing: border-box; }
body {
width: 320px;
padding: 16px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
margin: 0;
}
h3 { margin: 0 0 12px 0; font-size: 16px; }
.controls { display: flex; flex-direction: column; gap: 12px; }
.viewport-options { display: flex; gap: 8px; }
.viewport-btn {
flex: 1;
padding: 8px;
border: 1px solid #ddd;
background: #f5f5f5;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
}
.viewport-btn.active {
background: #0066cc;
color: white;
border-color: #0066cc;
}
textarea {
width: 100%;
height: 120px;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-family: monospace;
font-size: 11px;
resize: vertical;
}
.action-btn {
padding: 10px;
background: #0066cc;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
}
.action-btn:hover { background: #0055aa; }
.preview-frame {
margin-top: 12px;
border: 1px solid #ddd;
border-radius: 4px;
overflow: hidden;
}
iframe {
width: 100%;
height: 400px;
border: none;
}
</style>
</head>
<body>
<h3>Email Preview</h3>
<div class="controls">
<div class="viewport-options">
<button class="viewport-btn" data-width="375">Mobile</button>
<button class="viewport-btn active" data-width="600">Desktop</button>
<button class="viewport-btn" data-width="100%">Full</button>
</div>
<textarea id="htmlInput" placeholder="Paste HTML email code here..."></textarea>
<button id="renderBtn" class="action-btn">Render Preview</button>
<div class="preview-frame">
<iframe id="previewFrame"></iframe>
</div>
</div>
<script src="popup.js"></script>
</body>
</html>
Implementing Popup Logic
The popup script handles user interactions and manages the preview rendering. It applies email-specific styling and handles viewport resizing:
// popup.js
document.addEventListener('DOMContentLoaded', () => {
const htmlInput = document.getElementById('htmlInput');
const renderBtn = document.getElementById('renderBtn');
const previewFrame = document.getElementById('previewFrame');
const viewportBtns = document.querySelectorAll('.viewport-btn');
let currentWidth = 600;
// Load saved HTML from storage
chrome.storage.local.get('lastHtml', (result) => {
if (result.lastHtml) {
htmlInput.value = result.lastHtml;
renderPreview(result.lastHtml);
}
});
// Render button handler
renderBtn.addEventListener('click', () => {
const html = htmlInput.value;
if (html.trim()) {
chrome.storage.local.set({ lastHtml: html });
renderPreview(html);
}
});
// Viewport toggle handlers
viewportBtns.forEach(btn => {
btn.addEventListener('click', () => {
viewportBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
currentWidth = btn.dataset.width;
applyViewportWidth();
});
});
function renderPreview(html) {
const wrappedHtml = wrapEmailHtml(html);
const blob = new Blob([wrappedHtml], { type: 'text/html' });
const url = URL.createObjectURL(blob);
previewFrame.src = url;
// Clean up blob URL after load
previewFrame.onload = () => URL.revokeObjectURL(url);
}
function wrapEmailHtml(content) {
return `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
margin: 0;
padding: 20px;
background: #f5f5f5;
min-height: 100vh;
}
.email-container {
max-width: ${currentWidth};
margin: 0 auto;
background: white;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
/* Reset common email client quirks */
table { border-collapse: collapse; mso-table-lspace: 0; mso-table-rspace: 0; }
img { max-width: 100%; height: auto; }
a { text-decoration: underline; }
</style>
</head>
<body>
<div class="email-container">
${content}
</div>
</body>
</html>`;
}
function applyViewportWidth() {
const container = previewFrame.contentDocument.querySelector('.email-container');
if (container) {
container.style.maxWidth = currentWidth;
}
}
// Extract HTML from active tab
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
if (tabs[0]) {
chrome.tabs.sendMessage(tabs[0].id, { action: 'getSelection' }, (response) => {
if (response && response.html) {
htmlInput.value = response.html;
renderPreview(response.html);
}
});
}
});
});
Content Script for Page Extraction
A content script enables extracting HTML directly from web pages. This proves useful when viewing email templates hosted online or in development environments:
// content.js
class EmailExtractor {
constructor() {
this.init();
}
init() {
// Listen for messages from popup
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === 'getSelection') {
const selection = window.getSelection().toString();
const selectedHtml = selection ? selection : document.body.innerHTML;
sendResponse({ html: selectedHtml });
}
if (request.action === 'getPageHtml') {
sendResponse({ html: document.documentElement.outerHTML });
}
return true;
});
// Highlight editable regions for email templates
this.highlightEditableRegions();
}
highlightEditableRegions() {
// Find common email template containers
const selectors = [
'.email-template',
'[data-email]',
'.newsletter',
'email-body',
'#email-content'
];
const style = document.createElement('style');
style.textContent = `
[data-email-preview="true"] {
outline: 2px dashed #0066cc !important;
outline-offset: 2px !important;
cursor: pointer !important;
}
`;
document.head.appendChild(style);
selectors.forEach(selector => {
document.querySelectorAll(selector).forEach(el => {
el.setAttribute('data-email-preview', 'true');
});
});
}
}
new EmailExtractor();
Background Script for Tab Management
The background script coordinates between popup and content scripts, handling extension lifecycle events:
// background.js
chrome.runtime.onInstalled.addListener(() => {
// Initialize default settings
chrome.storage.local.set({
lastHtml: '',
viewportWidth: 600,
darkMode: false
});
});
// Handle messages between popup and content scripts
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === 'analyzeEmail') {
// Perform basic HTML analysis
const analysis = {
hasTables: (request.html || '').includes('<table'),
hasInlineStyles: (request.html || '').includes('style='),
hasImages: (request.html || '').includes('<img'),
linksCount: (request.html.match(/<a /g) || []).length,
length: (request.html || '').length
};
sendResponse(analysis);
}
return true;
});
Adding Email Client Simulation
Power users benefit from seeing how emails render across different clients. Add client simulation through CSS transforms:
// Extend popup.js with client simulation
const clientSimulations = {
outlook: {
name: 'Microsoft Outlook',
css: `
font-family: 'Calibri', sans-serif;
mso-line-height-rule: exactly;
`
},
gmail: {
name: 'Gmail (Web)',
css: `
.gmail-fix { display: none !important; }
`
},
apple: {
name: 'Apple Mail',
css: `
-webkit-font-smoothing: antialiased;
`
}
};
function applyClientSimulation(client) {
const simulation = clientSimulations[client];
if (!simulation) return;
const frame = document.getElementById('previewFrame');
const doc = frame.contentDocument;
const style = doc.createElement('style');
style.id = 'client-simulation';
style.textContent = simulation.css;
const existing = doc.getElementById('client-simulation');
if (existing) existing.remove();
doc.head.appendChild(style);
}
Testing Your Extension
Load your extension in Chrome by navigating to chrome://extensions/, enabling Developer mode, and clicking “Load unpacked”. Test with various email templates:
- newsletters with multiple columns
- transactional emails with inline styles
- responsive templates with media queries
- emails containing embedded images and fonts
Verify that the preview renders consistently and that viewport switching works correctly across different template types.
Conclusion
Building a Chrome extension for HTML email preview combines several practical skills: manifest configuration, content script injection, iframe rendering, and user interface design. The core pattern remains consistent, extract HTML content, wrap it with appropriate styling, and render in a controlled preview environment.
Start with the basic preview functionality, then add features like viewport switching, client simulation, and HTML analysis as needed. Users appreciate quick, reliable previews that integrate smoothly into their existing workflow.
Try it: Browse 155+ skills in our Skill Finder.
Related Reading
- AI Email Writer Chrome Extension: A Developer’s Guide
- AI Inbox Organizer Chrome Extension: A Developer’s Guide to Intelligent Email Management
- How to Check if Your Email Has Been Compromised in a Data Breach
Built by theluckystrike. More at zovo.one
Get started → Generate your project setup with our Project Starter.