@maildeno/editor ships a framework-agnostic init() entry point that mounts a <maildeno-editor> custom element inside a shadow root. This page walks through wiring it up in a plain Vite project — no Vue app, no build-time component compilation.
This is the same init() API used under the hood by every other framework guide in this section. If your stack isn’t listed elsewhere, start here — the pattern is identical everywhere except for lifecycle hooks.
|
Scaffold a project
npm create vite@latest my-editor-app -- --template vanilla-ts
cd my-editor-app
npm install
npm install @maildeno/editor
Add a container
The editor needs a container with a real, resolved height — a 0px container silently renders nothing.
<!-- index.html -->
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>Email Editor</title>
<style>
html, body { margin: 0; }
</style>
</head>
<body>
<div id="editor"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
Mount the editor
// src/main.ts
import { init } from "@maildeno/editor/init";
const handle = await init({
container: "#editor",
capabilities: { export: ["html", "mjml", "json"] },
onSave: (payload: { templateId: string | null }) => {
console.log("Saved", payload);
const target = handle?.getHtml(); // handle?.getReactEmail() or handle?.getMjml()
console.log(target);
},
onSendTestEmail: async ({ to, subject, html }) => {
await fetch("/api/send-test", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ to, subject, html }),
});
},
});
container accepts either a CSS selector string or a resolved HTMLElement.
Clean up on teardown
For a plain multi-page site, destroying on beforeunload is enough:
window.addEventListener("beforeunload", () => handle.destroy());
If you’re routing client-side (e.g. swapping the container without a full page reload), call handle.destroy() yourself before removing or replacing the container element — the editor doesn’t detect that on its own.
During Vite dev, HMR can re-execute main.ts without a full reload. If you see a duplicate editor mount while iterating on this file, guard the module with import.meta.hot?.dispose) ⇒ handle.destroy(.
|
Where to go next
-
Storage adapters — persist templates and images to your own backend instead of
localStorage -
Test email integration — implement the
/api/send-testendpoint referenced above -
@maildeno/editorreference — fullEditorHandleAPI