Appearance
HTML attributes
Set these directly on the <email-builder> element:
| Attribute | Type | Required | Description |
|---|---|---|---|
api-key | string | ✅ | Your Mail Lune API key |
user-id | string | — | ID of the end user in your system. When set, the template picker shows that user's templates alongside account-shared templates. Omit only for anonymous or read-only embeds. |
template-id | string | — | Load and auto-save a specific template |
theme | 'light' | 'dark' | 'auto' | — | Default: 'auto' (follows OS preference) |
headless | boolean flag | — | Hide the toolbar entirely. See Headless mode. |
hide-save | boolean flag | — | Hide only the Save button while keeping the rest of the toolbar |
hide-export | boolean flag | — | Hide only the Export HTML button while keeping the rest of the toolbar |
Runtime config (updateConfig)
Use updateConfig() for options that can't be set as attributes, or to update config after mount without remounting:
js
const builder = document.querySelector('email-builder');
builder.updateConfig({
locale: 'fr',
accentColor: '#ef4444',
brandName: 'Acme Mailer',
mergeTags: [
{ name: 'First name', value: '{{first_name}}', sample: 'Jane' },
{ name: 'Company', value: '{{company}}', sample: 'Acme Inc.' },
],
onSave: (json) => console.log('saved', json),
onExport: (html) => sendEmail(html),
onClose: () => router.back(),
});Full SdkConfig reference
ts
interface SdkConfig {
// Required
apiKey: string;
// Strongly recommended — enables the "My Templates" tab in the picker
userId?: string;
// Optional — set via attributes or updateConfig
baseUrl?: string; // default: Mail Lune API
theme?: 'light' | 'dark' | 'auto'; // default: 'auto'
locale?: 'en' | 'sv' | 'no' | 'da' | 'fr' | 'es'; // default: 'en'
// Headless mode — hides toolbar, host supplies all controls
headless?: boolean;
// White-label (any paid plan)
accentColor?: string; // any CSS color: '#ef4444', 'hsl(0 72% 51%)'
brandName?: string; // replaces "Email Builder" in the toolbar
hideBranding?: boolean; // hides the brand mark entirely
// Merge tags — shown as insertable pills in the text editor
mergeTags?: Array<{
name: string; // display label, e.g. "First name"
value: string; // inserted text, e.g. "{{first_name}}"
sample: string; // shown in preview, e.g. "Jane"
}>;
// Host-provided templates (Agency+) — see "Custom templates" below
templates?: SdkTemplate[];
hideDefaultTemplates?: boolean; // hide Maillune's built-in starters
showSaveAsTemplate?: boolean; // show a "Save as template" toolbar button
// Callbacks — also fired as DOM events (see Events)
onSave?: (json: BlockTree) => void;
onExport?: (html: string) => void;
onClose?: () => void;
onSaveTemplate?: (doc: SavedTemplate) => void; // user saved current email as a template
}Custom templates (Agency+)
On the Agency and Enterprise plans you can feed your own templates into the editor's picker via config.templates. Lower plans ignore the array.
ts
interface SdkTemplate {
id?: string; // your stable id (one is generated if omitted)
name: string; // shown on the card
clientId?: string; // when set → grouped under the "Your templates" tab
blockTree: BlockTree; // the email document
thumbnail?: string; // optional preview image URL
tags?: string[]; // optional tag pills
}js
builder.updateConfig({
hideDefaultTemplates: true, // hide the "Mail Lune" starter tab
templates: [
// Populated clientId → "Your templates" tab (scope to one end-user)
{ id: 't1', name: 'Welcome', clientId: 'user_123', blockTree: welcomeJson },
// No clientId → shared "Templates" tab (available to everyone)
{ id: 't2', name: 'Monthly digest', blockTree: digestJson },
],
});The picker builds its tabs from what you provide:
| Tab | Contents |
|---|---|
| Your templates | templates entries with a populated clientId |
| Templates | templates entries without a clientId |
| Mail Lune | Built-in starters — hidden when hideDefaultTemplates: true |
Save as template
Set showSaveAsTemplate: true to add a Save as template button to the toolbar, or call saveAsTemplate() from your own UI. Either way you get back a document flagged template: true that you store and pass straight back via config.templates:
ts
interface SavedTemplate {
template: true;
name: string;
clientId?: string; // mirrors the current userId, so it round-trips into "Your templates"
blockTree: BlockTree;
}js
builder.updateConfig({
showSaveAsTemplate: true,
onSaveTemplate: (doc) => {
// doc.template === true, doc.clientId === current user-id
await fetch('/api/templates', { method: 'POST', body: JSON.stringify(doc) });
},
});Headless mode
Headless mode removes the entire toolbar so your application can supply its own save, export, and undo controls — matching your product's design language exactly.
Activate via attribute:
html
<email-builder headless api-key="YOUR_KEY"></email-builder>Activate via config:
js
builder.updateConfig({ headless: true });When headless is active, the shell border and border-radius are also removed so the editor tiles flush into any container.
Wire up your own toolbar:
Every toolbar action is available via the imperative API:
js
// Save
document.querySelector('#save-btn').addEventListener('click', () => builder.save());
// Export HTML
document.querySelector('#export-btn').addEventListener('click', async () => {
const html = await builder.exportHtml();
if (html) await sendToESP(html);
});
// Undo / Redo
document.querySelector('#undo-btn').addEventListener('click', () => builder.undo());
document.querySelector('#redo-btn').addEventListener('click', () => builder.redo());
// Preview
document.querySelector('#preview-btn').addEventListener('click', () => builder.togglePreview());
// Template library
document.querySelector('#templates-btn').addEventListener('click', () => builder.openTemplates());
// Viewport toggle
document.querySelector('#mobile-btn').addEventListener('click', () => builder.setViewMode('mobile'));
document.querySelector('#desktop-btn').addEventListener('click', () => builder.setViewMode('desktop'));
// Dirty state indicator
builder.addEventListener('eb:save', () => updateSaveIndicator(false));
builder.addEventListener('eb:change', () => updateSaveIndicator(true));
// or poll:
setInterval(() => updateSaveIndicator(builder.isDirty()), 500);See Methods for the full API reference.
Plan-gated features
Some options are only active when your API key is on a qualifying plan:
| Option | Required plan |
|---|---|
accentColor | Pro+ |
brandName | Pro+ |
hideBranding | Pro+ |
| Template library button | Pro+ |
| Custom fonts (Settings tab) | Pro+ |
| Export HTML button | Pro+ |
TIP
Options outside your plan are silently ignored — no errors, no crashes. This lets you safely pass any config and have features activate automatically when you upgrade.
Locale
The editor ships with English (en), Swedish (sv), Norwegian (no), Danish (da), French (fr), and Spanish (es) UI strings. To request an additional locale, contact info@maillune.com.
js
builder.updateConfig({ locale: 'sv' });