A privacy-first Chrome extension (Manifest V3) that lets you insert, edit and preview custom HTML inside the Gmail compose window — built for newsletters and mailings.
Everything runs locally. No tracking, no analytics, no external requests,
no remote code. Templates are stored in chrome.storage.local on your machine.
</> button in the compose toolbar — injected next to Gmail’s
Send/attach buttons in every compose window (popup, full-screen, and inline
replies). Multiple compose windows are supported simultaneously; each button
is bound to its own compose window.<script>, <iframe>, inline event handlers (onclick=…) and
javascript: URLs are stripped. <style> blocks and style=""
attributes are kept intact — Gmail supports embedded and inline CSS (it
only strips external stylesheets), and inline CSS is the backbone of HTML
email.The project is deliberately build-free: what you clone is exactly what
Chrome loads. TypeScript would add a compile step, a node_modules tree, and
a dist/ folder for marginal benefit in a codebase of ~5 small files. With
vanilla JS you edit a file, hit reload on chrome://extensions, refresh
Gmail — done. The only third-party code is DOMPurify, vendored as a single
minified file in lib/ (Manifest V3 forbids remote code anyway).
mail-html-injector/ ← load THIS folder as unpacked extension
├── manifest.json MV3 manifest (storage permission + mail.google.com only)
├── README.md
├── .gitignore
├── icons/
│ ├── icon16.png placeholder icons (blue circle with "</>")
│ ├── icon48.png
│ └── icon128.png
├── lib/
│ └── purify.min.js DOMPurify 3.1.7, bundled locally (no CDN)
└── src/
├── content/ content scripts, loaded in order by the manifest
│ ├── sanitizer.js DOMPurify wrapper (email-friendly config)
│ ├── templates.js template CRUD on chrome.storage.local
│ ├── modal.js editor modal (Shadow DOM, code + live preview)
│ ├── content.js MutationObserver + toolbar button injection
│ └── button.css toolbar button styles (mhi- prefixed)
└── popup/
├── popup.html extension popup: info + enable/disable
├── popup.css
└── popup.js
The editor modal lives entirely inside a closed Shadow DOM, so Gmail’s CSS
cannot affect it and vice versa. The only styles injected into Gmail’s page
are for the toolbar button, all prefixed mhi-.
Gmail’s class names are obfuscated and change without notice. All selector
dependencies are concentrated in src/content/content.js and flagged with
GMAIL-DOM comments. The code prefers stable semantic anchors
(div[g_editable="true"], [role="textbox"]) and uses obfuscated classes
(.btC, td.gU.Up) only as placement hints, with a floating-button fallback
if the toolbar can’t be found. If something breaks after a Gmail update,
start there.
This extension is meant to run unpacked. It will never ask you to publish anything.
Open Chrome and navigate to chrome://extensions (type it in the
address bar).
Enable Developer mode with the toggle in the top-right corner of the page.
Click “Load unpacked” and select the project root folder — the
mail-html-injector/ folder itself, the one that directly contains
manifest.json. (Not src/, not any subfolder.)
Open Gmail (https://mail.google.com) — or, if it was already open,
reload the tab (content scripts only attach on page load). Click
Compose and verify a blue </> button appears in the bottom
toolbar of the compose window, next to Send.
Troubleshooting:
</> button
appears over the message body → Gmail renamed its toolbar classes.
The extension still works via the fallback button; to restore toolbar
placement, inspect the compose toolbar with DevTools and update the
GMAIL-DOM selectors in src/content/content.js (TOOLBAR_SELECTOR,
td.gU.Up).chrome://extensions and in its own popup toggle; (c) open DevTools on
the Gmail tab and look for [Mail HTML Injector] errors in the Console.[aria-label="Message Body"] and anchors on g_editable/role
attributes instead, which are identical in every locale.chrome://extensions. Unpacked extensions also break if the project
folder is moved or renamed — reload it from its new location with
“Load unpacked” again. (These prompts are the normal cost of unpacked
extensions; there is no way around them without the Web Store.)</> button in the compose toolbar.| Permission | Why |
|---|---|
storage |
Save your HTML templates locally (chrome.storage.local). |
https://mail.google.com/* |
Inject the button/editor into Gmail. Nothing else. |
No background service worker, no web requests, no other hosts.
file:// images, inline as
data: URIs).Source-available, all rights reserved — see LICENSE.
You may download and use this extension for free, for personal use. You may not redistribute, republish, sell, or publish modified/unmodified copies without written permission. This is intentionally not an open-source license: the code is public so you can read and run it, not copy it.
DOMPurify (lib/purify.min.js) is © Cure53 and contributors, dual-licensed
Apache 2.0 / MPL 2.0, and keeps its own license — see the header of that file.
Gmail and Google are trademarks of Google LLC. This project is not affiliated with, endorsed by, or sponsored by Google.