# Vue Mail Designer — full documentation > A visual drag & drop email builder for Vue 3. Published as `@naturaldevcr/vue-mail-designer` on npm. This file concatenates every guide and reference page verbatim, for feeding to an LLM in one shot. Source of truth: https://naturaldevcr.github.io/vue-mail-designer/ --- # Introduction Source: https://naturaldevcr.github.io/vue-mail-designer/guide/introduction **Vue Mail Designer** (`@naturaldevcr/vue-mail-designer`) is a Vue 3 component for visual email editing: drag blocks onto a canvas, edit them with a properties inspector, and get email-client-compatible HTML (Outlook included) plus a re-editable design JSON. ## Who is this for? For embedding an email editor inside your own application (a marketing SaaS, a CRM, a campaign builder) without depending on an external service. You control: - **Where images are stored** — you implement `uploadImage` and optionally `mediaLibrary` against your own storage. - **Which variables can be inserted** — `mergeTags` defines the variables available in the text editor. - **Which blocks appear** — the `tools` prop hides, reorders, or limits blocks in the palette. - **The editor's look** — `theme`, `appearance`, and `locale` (English by default, Spanish as the built-in alternative, or your own partial dictionary over English). - **Optional writing assistance** — enable Chrome built-in AI tools for rewrite, write, summarize, and translate in the rich text editor. ## What does it generate? Two outputs, both under your control: 1. **Email HTML** (`exportHtml()` or the `export-html` event) — tables with inline styles, MSO ghost tables for Outlook, a media query to stack columns on mobile. Meant to be pasted straight into your sending provider (SES, SendGrid, Postmark, etc.). 2. **Design JSON** (`EmailDocument`, via `getDesign()`/`loadDesign()` or `v-model:design`) — the full editable model, to save in your database and reopen in the editor later. The right rail's **Export** tab provides HTML, JSON, JSON import, Unlayer import, PNG, and version actions. Social icons in exported email HTML use hosted HTTPS image URLs rather than `data:` URIs; provide `socialIconUrlBuilder` when your delivery system requires self-hosted assets. ## Localization English is the default public language option: ```vue ``` If you only need to rename a few labels, pass a partial dictionary. Missing keys still fall back to English. ```vue ``` ## Images panel The builder uses one unified **Images** panel: - **Gallery** appears first when you provide `mediaLibrary`, shows your uploaded assets, and is selected by default. - **Search** runs `imageSearch` (or the built-in `openverseSearch`) and shows external image results. When `mediaLibrary` is not provided, Search is the only Images subtab. Click a thumbnail to open the preview dialog, then choose **Add** to insert it as a new Image block or replace the currently selected Image block. You can also drag thumbnails directly from Search or Gallery onto the canvas, onto an existing Image block, or onto a Gallery block slot. ## Next steps - [Installation](/guide/installation) - [Quickstart](/guide/quickstart) - [Chrome AI tools](/guide/chrome-ai) - [Props reference](/reference/props) Point it at [llms-full.txt](/llms-full.txt) for the complete documentation in a single file, or [llms.txt](/llms.txt) for a linked index — [llmstxt.org](https://llmstxt.org) convention. --- # Installation Source: https://naturaldevcr.github.io/vue-mail-designer/guide/installation ## Requirements - Vue `^3.5.0` - Pinia `^2.2.0` or `^3.0.0` - Node `>=20` (only to develop/build your app) ## Package ```bash pnpm add @naturaldevcr/vue-mail-designer vue pinia ``` ```bash npm install @naturaldevcr/vue-mail-designer vue pinia ``` ```bash yarn add @naturaldevcr/vue-mail-designer vue pinia ``` `vue` and `pinia` are peer dependencies — the library doesn't bundle them, to avoid duplicating them if your app already uses them. ## Styles The component ships its own CSS, with `--vmd-*` variables for theming. Import it once in your app: ```ts import '@naturaldevcr/vue-mail-designer/style.css' ``` ## Next step [Quickstart](/guide/quickstart) — mount the editor and export your first HTML. --- # Quickstart Source: https://naturaldevcr.github.io/vue-mail-designer/guide/quickstart A minimal component with image upload and HTML export: ```vue ``` - `design` is a `v-model`: start with `undefined` (the editor creates a blank document) or load a saved one. - `uploadImage` is the only strictly necessary storage prop — without it, the Image block can't upload new files (you can still paste a URL by hand). - Call `exportHtml()`/`exportJson()` via `ref`, or listen for `export-html`/`update:design` — see the [events and methods reference](/reference/events). ## With a media library If you also want a "Gallery" tab that lists, uploads, deletes, and renames files from your own bucket: ```ts const mediaLibrary = { async list(cursor?: string) { return { items: [], nextCursor: undefined } }, async upload(file: File) { return { id: 'x', url: '...', thumbnailUrl: '...', name: file.name } }, async delete(id: string) {}, async rename(id: string, name: string) { return { id, url: '...', thumbnailUrl: '...', name } }, } ``` ```vue ``` Without this prop, the "Gallery" tab doesn't appear. See [Blocks](/guide/blocks) and the full [props reference](/reference/props). --- # Autosave Source: https://naturaldevcr.github.io/vue-mail-designer/guide/autosave `EmailBuilder` can persist the current `EmailDocument` for you through the `autosave` prop. The library stores only the design JSON snapshot. You choose where it goes: browser storage with a stable key, or your own remote adapter. ## Local storage Use `type: 'local'` when you want the browser to keep a draft between mounts: ```vue ``` Use a key that stays stable for the same host record (`campaign:${campaignId}:draft`, `template:${templateId}:draft`, and so on). A random key per mount creates a new draft every time, so restore cannot find the previous save. `storage` is optional for local autosave. When omitted, the builder uses `window.localStorage`. Pass `storage` only when you need a different `Storage` object. ## Custom adapters Use `type: 'custom'` when the draft belongs in your own backend: ```vue ``` `load` is optional. A save-only adapter is valid when you want remote persistence without automatic restoration: ```ts const autosave: AutosaveOptions = { enabled: true, storage: { type: 'custom', async save(document) { await fetch('/api/campaigns/spring-launch/autosave', { method: 'PUT', headers: { 'content-type': 'application/json' }, body: JSON.stringify(document), }) }, }, } ``` The public storage shapes are: ```ts type AutosaveStorage = | { type: 'local' key: string storage?: Storage } | { type: 'custom' load?: () => Promise | EmailDocument | undefined save: (document: EmailDocument) => Promise | void } ``` ## Save modes and defaults `autosave.mode` controls when the builder writes a snapshot: | Mode | Behavior | Default `delay` | |---|---|---| | `'change'` | Saves every design change, one snapshot at a time, without overlapping saves. | `0` | | `'debounce'` | Waits until changes stop, then saves only the latest snapshot. This is the default mode. | `1000` ms | | `'interval'` | Saves the latest dirty snapshot on a repeating interval, and skips ticks when nothing changed. | `5000` ms | `delay` is optional for every mode. If you omit it, the builder uses the defaults above. ## Restore and precedence `restore` defaults to `false` (off). Set `restore: true` to enable restoration when autosave is configured: - Local storage always tries to read from the configured key. - Custom storage reads only when you provide `load`. When `restore` is off, no saved draft replaces the initial design. `restorePrecedence` decides whether a found draft replaces the current design after restoration is enabled: | Value | Result | |---|---| | `'initial-design'` | Default. The current builder design stays authoritative even if a saved draft exists. | | `'saved-design'` | A found draft is applied to the builder, then emitted through `autosave-restored`. | This matters when you mount the builder with an existing `design` prop. If your host application treats that design as the source of truth, keep the default `'initial-design'`. If the autosaved draft should win, set `'saved-design'`. If a slow restore is still loading and the user edits the design first, the live edit wins and the late restore is ignored. ## Events and status Autosave adds four events: | Event | Payload | When | |---|---|---| | `autosave-status` | `AutosaveStatusPayload` | Whenever the autosave status changes. | | `autosave-saved` | `AutosaveSavedPayload` | After a save succeeds. | | `autosave-restored` | `AutosaveRestoredPayload` | After a saved draft is applied to the builder. | | `autosave-error` | `AutosaveErrorPayload` | After a load or save failure. | The status payload exposes: ```ts type AutosaveStatus = | 'disabled' | 'idle' | 'restoring' | 'saving' | 'saved' | 'error' ``` You can also read the current status through the exposed `getAutosaveStatus()` method: ```vue ``` `AutosaveStatusPayload` is `{ status: AutosaveStatus; error?: unknown }`. `AutosaveSavedPayload` and `AutosaveRestoredPayload` include the design snapshot plus `savedAt` or `restoredAt` timestamps. `AutosaveErrorPayload` is `{ operation: 'load' | 'save'; error: unknown }`. ## Errors and cleanup Autosave failures do not unmount the editor or clear the current design. Instead: - load failures emit `autosave-error` with `operation: 'load'` and move the status to `'error'` - save failures emit `autosave-error` with `operation: 'save'` and move the status to `'error'` - a later successful save moves the status back to `'saved'` When the component unmounts, the builder disposes the autosave controller, clears pending timers, and ignores late completions from older saves or restores. Replacing the `autosave` prop also reconfigures the controller and cancels pending work from the previous configuration. ## Ownership of remote data For `type: 'custom'`, the library only calls your `load` and `save` functions with `EmailDocument` snapshots. Your host application still owns: - authentication and authorization - request retries and backoff - conflict resolution between multiple editors - retention, expiration, and deletion of remote drafts - choosing when a draft should be cleared after publish or send There is no public autosave clear API. If you need explicit cleanup, handle it in your own backend or by removing the local-storage key you chose. --- # Blocks Source: https://naturaldevcr.github.io/vue-mail-designer/guide/blocks The palette includes: **Heading**, **Text** (rich editor), **Image**, **Button**, **Divider**, **Spacer**, **Social**, **Menu**, **HTML**, **Video**, **Table**, **Gallery**, and **Timer** (countdown). ## Common properties Most blocks share, in their inspector: - **Padding** — available on every editable block, including Spacer and registered custom blocks. It is linked by default (a single value for all 4 sides); a link-icon button unlinks it to edit **Top**, **Right**, **Bottom**, and **Left** separately. - **Alignment** — left/center/right, where applicable. - **Hide per device** — `hideDesktop`/`hideMobile`, per block and per row. The exported HTML uses classes + a media query, no JS. ## Timer Countdown to a date. Two modes: - **Integrator-provided dynamic image**: you pass a function that generates the counter image (typically an external service like countdownmail). The callback receives the complete `TimerBlock`, so the provider can use its `endDate` and return a recipient-specific or campaign-specific image URL. - **Static box**: without that function, it shows a styled days/hours/minutes/seconds snapshot — works in any client, with no JavaScript animation. Email clients cannot run a reliable JavaScript or CSS countdown inside an exported message. A live timer therefore needs a remotely served image (GIF or dynamically generated image), just as other email builders do. Configure the provider with `timerImageUrlBuilder`: ```ts const timerImageUrlBuilder = (block: TimerBlock) => `https://your-domain.example/email-timer.gif?end=${encodeURIComponent(block.endDate)}` ``` ```vue ``` An explicit `TimerBlock.imageUrl` always takes precedence over the callback. If neither is available, export uses the static fallback; the editor still renders its local countdown live while editing. The Timer inspector lets you customize the static box background, border color and thickness, corner radius, number color, label color, font family, and each unit label. These settings are stored in the design JSON and are applied to both the live canvas and exported HTML. Existing timers keep their previous appearance through schema defaults. ## Table and Gallery - **Table** — rows/columns of simple text cells, with configurable padding and font size. - **Gallery** — a grid of 2 to 4 images; each item accepts dragging an image onto it (from the Images/Gallery tab, or by moving an image already placed on the canvas) to replace it. ## Custom blocks Besides the built-in blocks, you can register your own — see [Custom blocks](/guide/custom-blocks). Custom blocks also receive the shared outer padding control. It is stored in the block's `style.padding` and is applied in both the canvas and exported HTML. ## Corner radius in Outlook The Image block's `borderRadius` renders with CSS `border-radius`, and additionally with VML `` for Outlook desktop on fixed-width buttons — see [Email compatibility](/guide/email-compatibility). --- # Backgrounds Source: https://naturaldevcr.github.io/vue-mail-designer/guide/backgrounds ## Body background The **email body's background color and image** are edited in the inspector's **Body** tab (`settings.backgroundColor` / `settings.backgroundImage`). It's the background shown behind the entire document. ## Row and column background **Rows are transparent by default** so the body background shows through. Each row and each column can have its own background color and image, independent of the body. For a row background image: - **`url`** — the image. - **`repeat`** — `no-repeat` / `repeat` / `repeat-x` / `repeat-y`. - **`size`** — `auto` (natural size, not stretched), `cover` (fills the container, may crop), or `contain` (fits entirely, may leave bands). - **`position`** — standard CSS position (e.g. `center`, `top center`). - **Container width** — "Content" (bounded to the body's `contentWidth`, centered) or "Full width" (bleeds to the email's edges, independent of content width). When importing an Unlayer template, `size` almost never arrives as a CSS keyword — Unlayer puts the file's byte size there instead. The importer detects this and falls back to `auto` (natural size), the same thing Unlayer itself exports when it doesn't send an explicit `background-size`. ## Outlook Row background support in Outlook desktop is partial — see [Email compatibility](/guide/email-compatibility). --- # Rich text editor Source: https://naturaldevcr.github.io/vue-mail-designer/guide/rich-text The **Text** block uses a rich editor (Tiptap) with: - Bold, italic, underline, strikethrough - Lists (bullet and numbered) - Paragraph alignment - Text color and font size - Links - Variables (merge tags) — see below - Clear formatting ## Merge tags `mergeTags` defines the variables the user can insert from the editor toolbar: ```ts const mergeTags: MergeTagDef[] = [ { name: 'First name', value: 'first_name' }, { name: 'Company', value: 'company' }, ] ``` It also accepts groups, shown as optgroups: ```ts const mergeTags = [ { name: 'Contact', tags: [{ name: 'First name', value: 'first_name' }] }, { name: 'Account', tags: [{ name: 'Plan', value: 'plan_name' }] }, ] ``` In the exported HTML, each variable is emitted as `{{value}}` — your sending platform's engine is the one that replaces them at send time. The library performs no replacement of its own. ## Link color and underline By default, links inside a text block inherit `linkColor`/`linkUnderline` from the document (**Body** tab). A given text block can opt out of that inheritance and set its own link color/underline from its inspector. ## Special links `specialLinks` adds predefined links to the editor's selector (for example, an unsubscribe link resolved by your sending platform): ```ts const specialLinks = [{ name: 'Unsubscribe', href: '{{unsubscribe_url}}' }] ``` ## Chrome AI tools The editor can optionally show Chrome built-in AI actions for rewriting, writing, summarizing, and translating text. Enable them with the public `ai` prop; browser availability is detected at runtime and generated content is applied only after the user chooses **Apply**. See [Chrome AI tools](/guide/chrome-ai) for configuration and browser requirements. --- # Chrome AI tools Source: https://naturaldevcr.github.io/vue-mail-designer/guide/chrome-ai The rich text editor can expose optional Chrome built-in AI tools. These APIs are browser-provided and availability depends on the user's browser, device, and AI model access. The library does not provide a server-side fallback or require an API key. ## Enable the menu Import the public `AiLanguage` type when configuring Translate targets: ```vue ``` `ai.enabled` controls whether the AI button is rendered in the rich text toolbar. It is `false` unless you enable it. `languages` is the list of target languages offered by Translate; each entry needs a language code and a display label. The AI menu follows the builder's `locale` setting, so its labels and error messages are available in English and Spanish. See [Localization](/reference/props#locale) for the language configuration. ## Available actions - **Rewrite** revises the selected text. It supports tone and length options. - **Write** generates new text from a prompt. It does not require an existing selection and supports tone, length, and plain-text/Markdown format options. - **Summarize** summarizes the selected text with a summary type and length. - **Translate** translates the selected text into one of the configured target languages. The editor uses the browser's Language Detector when available and falls back to the current builder locale for the source language. Rewrite, Summarize, and Translate require selected text. Actions whose browser APIs are unavailable are disabled. Translate also checks the selected source/target pair before allowing the request to run. ## Review and apply results AI output is shown in an editable result field and does not change the email content immediately. Choose **Apply** to replace the selection, or insert generated text when there is no selection. Choose **Discard** or close the menu to leave the editor unchanged. Requests can show model-download progress. Chrome manages the on-device model and normally downloads it only the first time it is needed; it may download again after an update or storage eviction. The library reuses Summarizer sessions for the same summary configuration during the page lifetime, while failed sessions are discarded so the next request can retry. Writer, Rewriter, and Translate sessions are released after each request. ## Browser availability Chrome built-in AI APIs are experimental and may be unavailable, require model download, or support only some language pairs. Treat the AI menu as an optional enhancement: keep your editor usable when the APIs are missing, and do not assume that enabling the prop makes every action available. The public demo includes a deterministic local preview fallback when the browser does not expose these APIs. This keeps the interaction available for evaluation; it is demo-only and is not included in the package or used as a production AI provider. See the [`ai` prop](/reference/props#ai) for the public configuration shape. --- # AI template generation Source: https://naturaldevcr.github.io/vue-mail-designer/guide/ai-template-generation `EmailBuilder` can expose an AI workflow for generating complete email designs. The feature is provider-agnostic: you provide the `generate` function and decide whether it calls OpenAI, Anthropic, Kimi, OpenCode, a local model, or your own backend. The library never stores API keys and never imports a provider SDK. Keep credentials on your server whenever possible. ## Enable the feature ```vue ``` The AI action appears in the builder header and as an `AI` tab in the side panel only when `enabled` is `true`. Both entry points use the same workflow and provider callback. ## Explicit create and edit modes The user must choose one of these modes; the library never infers the operation from the prompt: - **Create template** starts from a new `EmailDocument`. - **Modify current design** sends a cloned copy of the current document and asks the provider to return the modified design. The user also chooses whether to request 1, 2, or 3 proposals. The default is 1. Results are shown as email previews and nothing changes until the user selects **Use this design**. Discarding, closing, or regenerating does not mutate the current design. Applying a proposal replaces the document through the normal `update:design`, `change`, and autosave flows. ## Dynamic context Context is resolved when the user clicks **Generate**, not when `EmailBuilder` mounts. Pass a plain/reactive object or a function: ```ts const aiTemplates: AiTemplateOptions = { enabled: true, context: { brandName: 'Hotel Poco a Poco', language: 'es', }, generate, } ``` ```ts const aiTemplates: AiTemplateOptions = { enabled: true, context: async () => ({ userId: currentUser.id, campaignId: campaign.value.id, brand: await loadCurrentBrand(campaign.value.brandId), }), generate, } ``` This is useful for campaign-specific copy, current permissions, locale, brand rules, merge-tag policies, or any data that can change while the editor stays mounted. ## Provider request The callback receives: ```ts type AiTemplateRequest = { mode: 'create' | 'edit' prompt: string count: 1 | 2 | 3 currentDesign?: EmailDocument context: Record designer: { schemaVersion: 1 supportedBlocks: BlockType[] customBlocks: AiTemplateCustomBlock[] mergeTags: MergeTagItem[] } } ``` `currentDesign` exists only in edit mode. `designer` describes the blocks and merge tags available in this editor instance, so your adapter can build model instructions without hard-coding a particular provider. Custom block descriptors omit the runtime `render` function; the model should use their `type`, fields, and data shape. Your backend can turn this structured request into the provider-specific system/developer prompt. Instruct the model to return only the requested proposal envelope and valid `EmailDocument` JSON. The package validates every returned design with its schema before previewing it and rejects custom block types that are not registered in the current editor. ## Provider response Return one to three proposals: ```ts type AiTemplateProposal = { title: string description?: string design: EmailDocument } ``` The package rejects an empty response, malformed design JSON, and unknown custom block types. The `generate` callback may return fewer proposals than requested. ## Errors Listen to `ai-templates-error` when the host needs telemetry or an error boundary: ```vue ``` The payload is: ```ts type AiTemplateErrorPayload = { operation: 'context' | 'generate' | 'validate' error: unknown } ``` The visible message is intentionally generic. The original error is available only through this event and is never logged by the library. ## Security guidance - Do not put OpenAI, Anthropic, Kimi, or other provider API keys in the `EmailBuilder` props or browser bundle. - Authenticate and authorize the host backend before it forwards a request to a model provider. - Treat prompts, context, current designs, and generated HTML as untrusted data. - Keep `html` blocks disabled in your provider instructions unless your application explicitly needs them. - Review generated links, merge tags, image URLs, and copy before sending an email. The demo includes a deterministic local adapter so the UI can be evaluated without network access or credentials. It is not a model-quality fallback. --- # Custom blocks Source: https://naturaldevcr.github.io/vue-mail-designer/guide/custom-blocks Besides the built-in blocks, you can register your own blocks that appear in the palette with a generic inspector (generated from `fields`) and your own render in the exported HTML. The generic inspector also includes the shared outer padding control, so custom blocks support the same Top/Right/Bottom/Left spacing behavior as built-in blocks. ```ts import type { CustomBlockDef } from '@naturaldevcr/vue-mail-designer' const promoBlock: CustomBlockDef = { type: 'promo-banner', label: 'Promo banner', icon: '🏷️', // optional defaultData: { text: 'Special offer', color: '#dc2626' }, fields: [ { key: 'text', label: 'Text', type: 'text' }, { key: 'color', label: 'Background color', type: 'color' }, ], render: (data) => `
${data.text}
`, } ``` ```vue ``` ## `CustomField` Each entry in `fields` is a simple control in the inspector, tied to a `data` key: | `type` | Control | |---|---| | `text` | text input | | `number` | numeric input | | `color` | color picker | | `textarea` | textarea | ## `render(data)` Receives the block's current `data` (starts at `defaultData`, updated as the user edits the `fields`) and returns the raw HTML that goes into the export. `render(data)` generates raw HTML as-is. If that `data` can come from a JSON imported from outside your control, escape the values before interpolating them — the library exports `escapeHtml` for that: ```ts import { escapeHtml } from '@naturaldevcr/vue-mail-designer' render: (data) => `
${escapeHtml(String(data.text))}
` ``` --- # Importing from Unlayer Source: https://naturaldevcr.github.io/vue-mail-designer/guide/unlayer-import From the **Export → Import from Unlayer…** menu you can paste an Unlayer design JSON, or the URL of a template from their studio (e.g. `https://studio.unlayer.com/create/black-friday-laptop-deals`). The design is converted to our format (`EmailDocument`) and a list of warnings is shown for anything that couldn't be mapped. ## What gets warned about, not imported - Unlayer-specific responsive styles (`_override.mobile`) — the importer doesn't yet generate equivalent mobile-only rules. - Display conditions (`displayCondition`). - Referenced Google Fonts — you need to load them yourself in your platform. - Images served from Unlayer's CDN — they belong to Unlayer; you should replace them with your own assets. The converter warns about this automatically. ## Programmatic usage ```ts import { unlayerToDocument, unlayerSlugFromUrl } from '@naturaldevcr/vue-mail-designer' const { document, warnings } = unlayerToDocument(unlayerJson) // document: EmailDocument, ready to load with loadDesign()/v-model:design // warnings: string[] of anything that couldn't be mapped const slug = unlayerSlugFromUrl('https://studio.unlayer.com/create/black-friday-laptop-deals') // 'black-friday-laptop-deals' ``` ## Importing by URL from the browser The browser can't hit Unlayer's API directly due to CORS. Pass your own `unlayerFetch` that resolves against your backend/proxy: ```ts async function unlayerFetch(slug: string): Promise { const res = await fetch(`/api/unlayer-proxy/${slug}`) return res.json() } ``` ```vue ``` This repo's demo app uses a Vite proxy at `/unlayer-api` as a reference. ## Known fidelity notes The importer was verified field-by-field against real templates from Unlayer's studio (not just the documented shape of the JSON). A few examples of non-obvious mappings it already covers: - Image width lives in `src.maxWidth`/`src.autoWidth`, not in `values.width` (which stock templates ship as `null`). - Menu padding separates `containerPadding` (block) from `padding` (each item, individually). - `backgroundColor: ""` means "no color assigned", not a real color — a naive check would interpret it as a valid string and overwrite the factory `transparent`. - `backgroundImage.size` is, in practice, the file's byte size, not a CSS keyword — it falls back to `auto` (natural size), same as Unlayer's own export. --- # Email compatibility Source: https://naturaldevcr.github.io/vue-mail-designer/guide/email-compatibility The exported HTML is built for email clients, not browsers: it uses tables with inline styles, avoids `flex`/`grid`/`position`, and adds conditional ghost tables for Outlook (Word engine). ## Techniques used - **Presentation tables** (`role="presentation"`) for all layout — columns, padding, alignment. - **MSO ghost tables** (``) so Outlook desktop computes pixel widths where other clients use `%`/`max-width`. - **A single media query** to stack columns on mobile (`@media (max-width: 480px)`) and for the per-device hide classes. - **VML** (``) on fixed-width buttons, to get rounded corners in Outlook desktop too — the only way to achieve that there. - **`font-size:0;line-height:0`** on dividers and separators, to avoid the whitespace gap that `display:inline-block` leaves in inline layout. ## Known limitations - Doesn't import existing HTML — JSON only (your own, or Unlayer's). - Row backgrounds: partial support in Outlook desktop (no full-bleed VML yet). - Merge tags are emitted as `{{value}}` — your sending platform's engine replaces them; the library interpolates nothing. - Columns can't be reordered within a row (rows and blocks can be reordered). - `theme` only accepts `'light' | 'dark'` (no `'auto'`). - Column border/radius: supported in the model and the exported HTML, but no dedicated inspector control yet. - The Image block's `borderRadius` uses CSS `border-radius` — looks right in the builder and in most clients, but Outlook desktop (Word engine) ignores it. - The timer doesn't animate without an integrator-provided dynamic image service — without one, it shows a static box with the days remaining. ## See also - [Backgrounds](/guide/backgrounds) — background image/color per row and column. - [Importing from Unlayer](/guide/unlayer-import) — what gets warned about when converting a template. --- # Limitations Source: https://naturaldevcr.github.io/vue-mail-designer/guide/limitations A summary of what the library **doesn't** do today, so you know upfront whether it fits before integrating it: - **No HTML import** — the importer only reads JSON (your own, or Unlayer templates). There's no parser turning arbitrary HTML into blocks. - **No backend of its own** — image upload, media library, and Unlayer URL-import proxying are functions you implement. The library assumes no particular storage — not Firebase, not S3, nothing specific. - **Columns aren't reorderable relative to each other** — within a row, column order is fixed; you can reorder rows and blocks within a column. - **`theme` has no `'auto'` mode** — only `'light' | 'dark'`, no system-preference detection. - **No UI for column border/radius** — the model and the export support them, but the inspector doesn't have a control for them yet. - **Outlook desktop**: - Row background has partial support (no full-bleed VML). - Image `borderRadius` is ignored (the button's does have a VML fallback). - **Timer has no animation of its own** — needs an external dynamic-image service; without one, it falls back to a static box with the days remaining. - **Merge tags have no replacement engine** — emitted as literal `{{value}}`; the actual replacement is done by your sending platform when the email is sent. If any of these blocks you, or you find a fidelity difference when importing a real Unlayer template, [open an issue](https://github.com/NaturalDevCR/vue-mail-designer/issues). --- # Props Source: https://naturaldevcr.github.io/vue-mail-designer/reference/props All optional. | Prop | Type | Description | |------|------|-------------| | `design` | `EmailDocument` | The document's design (`v-model:design`). Without it, the editor starts blank. | | `mergeTags` | `MergeTagItem[]` | Variables insertable in text: `{ name, value }`, or groups `{ name, tags: MergeTagDef[] }` (shown as optgroups). | | `templates` | `EmailTemplate[]` | Extra templates, in addition to the built-in defaults. | | `uploadImage` | `(file: File) => Promise` | Upload handler; returns the final URL. Without this prop, the Image block can't upload new files. | | `imageSearch` | `(query: string) => Promise` | Search handler for the Search subtab in the unified Images panel; defaults to `openverseSearch` (Openverse, CC0/CC-BY). | | `mediaLibrary` | `MediaLibraryOptions` | Enables the Gallery subtab in the unified Images panel: `{ list: (cursor?) => Promise<{ items: MediaItem[], nextCursor? }>, upload: (file) => Promise, delete: (id) => Promise, rename: (id, name) => Promise }`. Without this prop, only Search is shown. You implement each function against your own storage — the library assumes no particular backend. | | `timerImageUrlBuilder` | `(block: TimerBlock) => string \| undefined` | Optional email-safe timer image provider. Called during live preview and HTML export when the timer has no explicit `imageUrl`; return a remotely served GIF or generated image URL. Without it, exported timers are static snapshots because email clients cannot run a live countdown. | | `socialIconUrlBuilder` | `(kind: SocialNetworkKind) => string \| undefined` | Optional provider for hosted email social icon URLs. Defaults to HTTPS icon URLs; return self-hosted assets for production control. Empty or throwing callbacks use the default URL. | | `unlayerFetch` | `(slug: string) => Promise` | Handler to load an Unlayer template by URL/slug; returns the design JSON. Defaults to hitting Unlayer's public API (fails via CORS without your own proxy). | | `theme` | `'light' \| 'dark'` | Builder UI theme (doesn't affect the email canvas). | | `showHeader` | `boolean` | Whether to show the builder header. Defaults to `true`; when `false`, the entire builder header is hidden. | | `locale` | `'en' \| 'es' \| LocaleDict` | Public UI language option. English (`'en'`) is the default, Spanish (`'es'`) is the built-in alternative, and a `LocaleDict` is merged on top of English so you can override only the keys you want. | | `appearance` | `Appearance \| ThemeAppearance` | Builder colors. A flat object (`{ accent, panel, border, background, foreground, muted }`) applies to both modes. The union Appearance or ThemeAppearance also accepts `{ light?: Appearance, dark?: Appearance }` for mode-specific values; omitted fields keep that mode's defaults. | | `ai` | `AiOptions` | Optional Chrome built-in AI tools for the rich text editor: `{ enabled: boolean, languages?: AiLanguage[] }`. The menu is rendered only when enabled; `languages` configures Translate targets. Browser API availability is checked at runtime. | | `aiTemplates` | `AiTemplateOptions` | Optional provider-agnostic AI template generation: `{ enabled, context?, generate }`. The user explicitly chooses create/edit mode and 1–3 proposals; the callback is owned by your application. See [AI template generation](/guide/ai-template-generation). | | `autosave` | `AutosaveOptions` | Optional autosave config: `{ enabled, storage, mode?, delay?, restore?, restorePrecedence? }`. Supports local browser storage or your own custom adapter. See [Autosave](/guide/autosave). | | `tools` | `Partial>` | Per-block palette config: `{ enabled?, position?, usageLimit? }` to hide, reorder, or limit instances of a block type. | | `fonts` | `FontDef[]` | List of available fonts (`{ label, value, url? }`); the ones with `url` (Google Fonts) are loaded both in the canvas and in the exported HTML. Defaults to a curated list. | | `specialLinks` | `SpecialLink[]` | Predefined links insertable from the text editor (`{ name, href }`) — for example, an unsubscribe link resolved by your sending platform. | | `customBlocks` | `CustomBlockDef[]` | Integrator-defined custom blocks — see [Custom blocks](/guide/custom-blocks). | ## Locale English is the default builder language: ```vue ``` You can also pass a partial dictionary to customize just a few labels while keeping English for every missing key: ```vue ``` ## Images panel The builder has one **Images** panel with two subtabs: - **Gallery** shows your uploaded assets from `mediaLibrary` and is the first, default subtab when configured. - **Search** shows results from `imageSearch` (or `openverseSearch` if you do not provide one). Without `mediaLibrary`, Search is the only subtab. Clicking a thumbnail opens a preview dialog first. Select **Add** to insert a new Image block or replace the currently selected Image block. You can also drag thumbnails straight from Search or Gallery onto the canvas, onto an existing Image block, or onto a Gallery block slot. See also [Events](/reference/events) and [Methods](/reference/methods). ## Export tab The right rail's **Export** tab contains the native HTML, JSON, JSON import, Unlayer import, PNG, and version actions. The same document operations are available programmatically through the component ref; see [Methods](/reference/methods). ## Chrome AI See [Chrome AI tools](/guide/chrome-ai) for the complete configuration, action behavior, browser-availability notes, and Apply/Discard flow. ## AI template generation See [AI template generation](/guide/ai-template-generation) for the provider callback, dynamic context, proposal validation, preview/apply flow, errors, and API-key guidance. ## Autosave `autosave` is fully optional. When enabled, the builder persists the current `EmailDocument` through one of these public storage shapes: ```ts type AutosaveStorage = | { type: 'local' key: string storage?: Storage } | { type: 'custom' load?: () => Promise | EmailDocument | undefined save: (document: EmailDocument) => Promise | void } ``` `AutosaveOptions` is: ```ts type AutosaveOptions = { enabled: boolean storage: AutosaveStorage mode?: 'change' | 'debounce' | 'interval' delay?: number restore?: boolean restorePrecedence?: 'initial-design' | 'saved-design' } ``` - `restore` defaults to `false` (off), so a saved draft does not replace the initial design unless restoration is enabled - `mode` defaults to `'debounce'` - `delay` defaults to `1000` for `'debounce'`, `5000` for `'interval'`, and `0` for `'change'` - `restorePrecedence` defaults to `'initial-design'` - save-only custom adapters are supported because `load` is optional See [Autosave](/guide/autosave) for local-storage usage, restore rules, events, cleanup, and host-owned remote data behavior. For example, use the per-mode form to configure a dark builder with distinct light and dark palettes: ```vue ``` --- # Events Source: https://naturaldevcr.github.io/vue-mail-designer/reference/events | Event | Payload | When | |---|---|---| | `update:design` | `EmailDocument` | On every design change — the event that powers `v-model:design`. | | `change` | `EmailDocument` | Same as `update:design`, for when you'd rather not use `v-model`. | | `export-html` | `string` | When calling `exportHtml()` via `ref` — delivers the generated HTML. | | `ai-templates-error` | `AiTemplateErrorPayload` | When dynamic context resolution, the host provider callback, or returned proposal validation fails. `operation` is `'context' | 'generate' | 'validate'`. | | `autosave-status` | `AutosaveStatusPayload` | When autosave changes status: `'disabled'`, `'idle'`, `'restoring'`, `'saving'`, `'saved'`, or `'error'`. | | `autosave-saved` | `AutosaveSavedPayload` | After a save succeeds. Includes the saved design snapshot and `savedAt`. | | `autosave-restored` | `AutosaveRestoredPayload` | After a saved draft is applied to the builder. Includes the restored design snapshot and `restoredAt`. | | `autosave-error` | `AutosaveErrorPayload` | After a load or save failure. Includes `operation: 'load' | 'save'` and the thrown `error`. | ```vue ``` For the full request/response contract and provider security guidance, see [AI template generation](/guide/ai-template-generation). See also [Autosave](/guide/autosave) for restore behavior and the full autosave payloads, and [Methods](/reference/methods) for `getAutosaveStatus()` and export helpers. --- # Methods (ref) Source: https://naturaldevcr.github.io/vue-mail-designer/reference/methods Accessed by mounting the component with `ref`: ```vue ``` | Method | Signature | Description | |---|---|---| | `exportHtml` | `(): string` | Full email HTML, ready for your sending provider. | | `exportJson` | `(): string` | The current `EmailDocument`, serialized to JSON. | | `getDesign` | `(): EmailDocument` | The current `EmailDocument`, unserialized. | | `loadDesign` | `(doc: EmailDocument): void` | Replaces the current document with `doc` — resets the undo/redo history. | | `getAutosaveStatus` | `(): AutosaveStatus` | Current autosave lifecycle state: `'disabled'`, `'idle'`, `'restoring'`, `'saving'`, `'saved'`, or `'error'`. | | `exportImage` | `(): Promise` | PNG of the design, as a data URL. **Limitation:** cross-origin images (CORS) can prevent the capture. | The right rail's **Export** tab exposes the same HTML, JSON, import, PNG, and version workflows for users who prefer the built-in UI. Use the ref methods when the host application needs to save or load designs from its own backend. ## Versions From the **Export → Versions…** menu, the user can save, load, and delete named versions of the design — in memory for the session, not persisted by the library (save them yourself if you need them across sessions). ## Autosave status Use `getAutosaveStatus()` when your host UI needs a synchronous status read in addition to the autosave events: ```vue ``` Pair it with `autosave-status` when you want reactive updates. See [Autosave](/guide/autosave) for the full lifecycle.