Skip to content

HTML attributes

Set these directly on the <email-builder> element:

AttributeTypeRequiredDescription
api-keystringYour Mail Lune API key
user-idstringID 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-idstringLoad and auto-save a specific template
theme'light' | 'dark' | 'auto'Default: 'auto' (follows OS preference)
headlessboolean flagHide the toolbar entirely. See Headless mode.
hide-saveboolean flagHide only the Save button while keeping the rest of the toolbar
hide-exportboolean flagHide 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:

TabContents
Your templatestemplates entries with a populated clientId
Templatestemplates entries without a clientId
Mail LuneBuilt-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:

OptionRequired plan
accentColorPro+
brandNamePro+
hideBrandingPro+
Template library buttonPro+
Custom fonts (Settings tab)Pro+
Export HTML buttonPro+

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' });