Debloq
Documentation/ v2.0.4

Everything to embed Debloq.

Add a drag-and-drop email builder to your own product. This guide covers embedding the editor, customizing how it looks and behaves, connecting it to your storage and sending, and reading the finished email.

Before you start

You need:

  • A licence key from your Debloq dashboard.
  • Your site’s domain added to the project the key belongs to. The key works only on those domains, plus localhost for development.

Some options below belong to specific plans. If your plan doesn’t include an option, the editor ignores it and the matching method rejects. Everything else works the same on every plan.

Embed the editor

With a script tag

<div id="editor" style="height: 80vh"></div>

<script src="https://editor.debloq.io/stable/embed.js"></script>
<script>
  const editor = Debloq.createEmailEditor('#editor', {
    licenseKey: 'YOUR_LICENSE_KEY'
  })
</script>

The editor fills the element you give it, so give that element a height.

With npm

npm install debloq-email-editor-react     # or -vue, or -angular

See React, Vue and Angular.

Content Security Policy

If your site sends a Content-Security-Policy header, allow the editor:

frame-src https://editor.debloq.io;
script-src https://editor.debloq.io;

script-src is needed only for the script tag; the npm packages bundle the loader. Without frame-src, the editor area stays blank and the browser console reports a CSP violation.

What you get back

createEmailEditor returns a handle:

Member Use
mounted A Promise that resolves with the editor API when the editor is ready, or null if it can’t open.
ready(fn) Calls fn(api) when the editor is ready. Returns a function that removes the listener.
api The editor API once ready, otherwise null.
update(options) Changes options after start.
destroy() Removes the editor from the page.
const api = await editor.mounted
if (api) {
  const { html } = await api.exportHtml()
}

Every editor method returns a Promise.

Load and save designs

A design is a JSON object describing the email. Store it in your database as is, and pass it back later to keep editing. You don’t need to read or change its contents.

Open a saved design

Debloq.createEmailEditor('#editor', {
  licenseKey: 'YOUR_LICENSE_KEY',
  modelValue: savedDesign            // object or JSON string
})

Without modelValue, the editor opens an empty email. To open a different design later:

await api.loadDesign(otherDesign)

Save

const { design, html, text } = await api.exportHtml()
await fetch('/api/emails/42', {
  method: 'PUT',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ design, html, text })
})

Save design to reopen the email later, and html and text to send it.

Autosave

Debloq.createEmailEditor('#editor', {
  licenseKey: 'YOUR_LICENSE_KEY',
  modelValue: savedDesign,
  onChange: ({ design }) => saveToServer(design)
})

onChange runs shortly after the author stops editing. See Events.

Customize the look

Theme

Match the editor to your product.

theme: 'dark'          // 'light', 'dark' or 'auto' (follows the user's system)
theme: {
  mode: 'auto',
  accent: '#0f766e',                 // any CSS colour, or var(--your-brand)
  fontFamily: 'Inter, sans-serif',   // font of the editor interface
  radius: 2,                         // corner radius in px; keep it small
  tokens: { '--panel': '#fafafa' }   // override individual colours
}

The theme styles the editor interface only; it never changes the email. Change it at any time with api.setTheme(theme) or editor.update({ theme }).

The top-left of the editor toolbar shows the Debloq logo. Replace or hide it:

brandLogo: false                                      // hide
brandLogo: 'Acme Mail'                                // your product name
brandLogo: { text: 'Acme', icon: '🚀' }               // name with an emoji or glyph
brandLogo: { logo: 'https://acme.com/logo.svg', href: 'https://acme.com' }  // image with a link

Brand kit

Give authors one-click access to your customers’ brand colours and fonts in every colour and font picker.

brand: {
  colors: ['#4f46e5', { name: 'Ink', value: '#111827' }],
  fonts: ['Poppins', 'Merriweather']
}

Update it at any time with api.setBrand(brand).

Fonts

Add fonts to the font picker, alongside the built-in web-safe and Google fonts.

fonts: [
  'Bebas Neue',                                          // a Google font by name
  { name: 'Brand Sans', stack: "'Brand Sans', Arial, sans-serif", url: 'https://acme.com/fonts.css' },
  { family: 'Roboto Slab', weights: [400, 700] }         // a Google font with weights
]

Exported emails link web fonts and fall back to the stack in clients that don’t support them.

Customize the toolbar

The editor has its own top toolbar with your logo, Templates, Preflight and Export buttons.

chrome: false

Hides that toolbar so you can put these actions in your own UI, using openTemplates(), openPreflight(), openExport() and exportHtml().

When the toolbar is hidden, a small bar at the top of the canvas keeps the device switcher, undo/redo and preview. Control it with canvasToolbar:

canvasToolbar: false    // hide it too, and build these controls yourself
canvasToolbar: true     // show it even when chrome is on

See Control the editor from your app for building your own controls.

Keyboard shortcuts (undo, redo, Esc to close dialogs, Alt+↑/↓ to move a block) work while the editor has focus.

Merge tags

Authors insert personalization tags from System Tags in the text toolbar.

mergeTags: [
  { label: 'First name', value: '{{ first_name }}' },
  { label: 'Order number', value: '{{ order.number }}' }
]

Use your sending platform’s syntax; the editor inserts value exactly as written and leaves it unchanged in the export. Without this option, the editor offers first name, last name, full name, email, company and unsubscribe link.

Ready-made links offered wherever authors add a link (text, buttons, menus).

specialLinks: [
  { label: 'Unsubscribe', value: '{{ unsubscribe_url }}' },
  { label: 'View in browser', value: '{{ view_in_browser_url }}' },
  { label: 'My account', value: 'https://acme.com/account' }
]

Without this option the editor offers Unsubscribe, View in browser and Manage preferences. Pass [] to remove them. Update with api.setSpecialLinks(list).

UTM tracking

Tag every outbound link in the exported email.

utm: { source: 'newsletter', medium: 'email', campaign: 'spring-sale', term: '', content: '' }

Only http and https links are tagged; merge tags, mailto: and tel: links are left alone. Update with api.setUtm(config); pass null to stop tagging.

Countdown timers

Timer blocks can show a live countdown that keeps ticking in the inbox. Switch a Timer block to Live mode and it exports an image from Debloq’s countdown service, which redraws the remaining time each time the email is opened.

Debloq hosts and runs the countdown service for you; there is nothing to configure. If live timers aren’t included in your plan, the timer is exported as a fixed snapshot of the time remaining at export.

Templates

Replace the template gallery with your own templates.

templates: [
  {
    id: 'welcome',
    name: 'Welcome',
    description: 'Greet new customers',
    accent: '#0f766e',
    design: welcomeDesign            // a design saved earlier
  },
  {
    id: 'receipt',
    name: 'Receipt',
    build: () => fetch('/api/templates/receipt').then((r) => r.json())
  }
]

Give each template either a design or a build function that returns one. build runs in your page, so it can call your own API. Without this option the editor shows its built-in templates. Open the gallery from your own button with api.openTemplates().

Custom components

Add your own blocks to the palette, such as a product card or a signature. Authors drag them in and edit them like any built-in block.

customComponents: [
  {
    type: 'product-card',
    label: 'Product',
    icon: '<svg viewBox="0 0 24 24">…</svg>',
    group: 'content',
    defaults: {
      title: 'Running shoe',
      price: '$99',
      image: 'https://acme.com/shoe.png',
      url: 'https://acme.com/shoe'
    },
    fields: [
      { key: 'title', label: 'Title', type: 'text' },
      { key: 'price', label: 'Price', type: 'text' },
      { key: 'image', label: 'Image', type: 'image' },
      { key: 'url', label: 'Link', type: 'linkurl' }
    ],
    template: `
      <img src="{{ props.image }}" width="200" alt="{{ props.title }}" style="display:block">
      <h3 style="margin:8px 0">{{ props.title }}</h3>
      <p style="margin:0">{{ props.price }} · <a href="{{ props.url }}">Buy now</a></p>
    `
  }
]
Property Description
type Required. A unique id: lowercase letters, digits and hyphens, starting with a letter. It can’t match a built-in block.
template Required. The block’s email HTML.
label Name shown in the palette. Default: type.
icon Inline SVG shown in the palette.
group Palette tab. 'content' or 'layouts' adds it to that tab; any other name creates a tab. Default 'Custom'.
defaults Starting values for new blocks. Strings, numbers and booleans.
fields Settings the author can edit. See below.
text Plain-text version of the block, using the same placeholders. Default: the template with its tags removed.

Template. {{ props.key }} is replaced with the block’s current value for key, HTML-escaped. Any other {{ … }}, such as {{ first_name }}, is left as written for your sending platform. The same template is used on the canvas and in the exported email, so write email-safe HTML with inline styles. Scripts, event handlers and unsafe links are removed from the canvas preview only; the export contains your template as written.

Fields. Each field edits one value in the settings panel:

{ key: 'title', label: 'Title', type: 'text', group: 'content' }
type Control Extra properties
text One-line text placeholder maxlength
textarea Multi-line text rows
linkurl Link, with the special links placeholder
number Number min max step suffix
slider Slider min max step suffix
color Colour picker allowTransparent
select Drop-down options: [['s', 'Small'], ['l', 'Large']] or [{ value, label }]
toggle On/off switch
align Left, centre, right
image Image picker, using your image options
note Help text; no value text

group puts the field under Content, Style or Layout ('content', 'style', 'layout'; default 'content'). Keys can’t start with _.

Every custom component also gets background colour, border, rounded corners, alignment and padding controls, applied to the cell around your template. To replace one, add a field with the same key (backgroundColor, borderRadius or align).

The editor reads customComponents when it opens. Definitions with an invalid or duplicate type or no template are skipped with a warning in the browser console. Saved designs keep the block’s values; keep the type stable so they still open. Available on plans that include custom components.

Images

Uploads

Send uploaded images to your own storage.

imageUpload: async (file) => {
  const body = new FormData()
  body.append('file', file)
  const res = await fetch('/api/uploads', { method: 'POST', body })
  const { url } = await res.json()
  return url                          // the public URL of the image
}

Without it, or if your function throws or returns nothing, the image is embedded in the design itself, which many email clients won’t display. It is also used to convert icons into images before export, so they show in every email client.

Your media library

Show your customer’s existing images in the image picker.

imageLibrary: async () => {
  const res = await fetch('/api/media')
  return res.json()                   // [{ url, thumb?, alt?, name? }, …]
}

Stock photos

Add a stock photo search, for example backed by Unsplash through your server.

stockSearch: async (query) => {
  const res = await fetch(`/api/stock?q=${encodeURIComponent(query)}`)
  return res.json()                   // [{ url, thumb?, alt?, name? }, …]
},
onStockSelect: async (image) => {
  // Called when the author inserts a stock photo, e.g. to report a download.
  await fetch('/api/stock/used', { method: 'POST', body: JSON.stringify(image) })
}

Change these later with api.setImageLibrary(fn), api.setStockSearch(fn) and api.setStockPick(fn); pass null to remove.

Saved blocks, swatches and uploads

The editor keeps three small libraries for the author. By default they last only for the current session. Connect them to your backend to keep them per user:

savedBlocksStore: {
  load: () => fetch('/api/me/saved-blocks').then((r) => r.json()),
  save: (list) => fetch('/api/me/saved-blocks', { method: 'PUT', body: JSON.stringify(list) })
},
brandSwatchStore: { load, save },    // colours saved from the colour picker
assetStore: { load, save }           // recently uploaded images

load() returns the list (or a Promise of it). save(list) receives the whole list every time it changes. Store the items as given.

Send test emails

Add a Send test action to the export dialog.

onSendTest: async ({ to, html, text, design }) => {
  await fetch('/api/send-test', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ to, html, text })
  })
}

to is the address the author entered. If your function throws, the author sees that the test failed. Without this option the action is hidden.

Draft recovery

Protect authors from losing work if the tab closes before they save.

Debloq.createEmailEditor('#editor', {
  licenseKey: 'YOUR_LICENSE_KEY',
  modelValue: savedDesign,
  autosaveKey: 'campaign-42',        // one key per email
  onReady: async (api) => {
    if (api.draft?.available && confirm('Restore unsaved changes?')) {
      await api.restoreDraft()
    }
  }
})

The editor keeps a copy of the email in the author’s browser under that key. After you save to your server, call api.discardDraft().

Control the editor from your app

Build your own toolbar around the editor:

const api = await editor.mounted

deviceSelect.onchange = (e) => api.setDevice(e.target.value)   // 'desktop', 'tablet', 'mobile'
undoButton.onclick = () => api.undo()
redoButton.onclick = () => api.redo()
previewButton.onclick = () => api.togglePreview()
templatesButton.onclick = () => api.openTemplates()
checkButton.onclick = () => api.openPreflight()
exportButton.onclick = () => api.openExport()

api.devices lists the device sizes available to you, as { id, label }. api.controls gives the state when the editor opened: device, canUndo, canRedo, preview, and the number of preflight issues (issueCount, errorCount, warningCount).

Export the email

HTML

const { html, text, design } = await api.exportHtml()
  • html: a complete email, built to render in Gmail, Outlook, Apple Mail and mobile clients.
  • text: a plain-text version for the plain-text part of the message.
  • design: the design, to save.

Merge tags stay in the output for your sending platform to fill in.

AMP for Email

const { html, text, amp } = await api.exportAmp()
if (amp.html) {
  // Send amp.html as the text/x-amp-html part, alongside html and text.
} else {
  console.warn(amp.errors)           // why the AMP version isn't valid
}

amp.html is null when the email can’t be made into valid AMP; always send html as well. amp.warnings lists differences between the AMP and HTML versions.

Events

Option Called with When
onReady api The editor is ready to use.
onChange { design, html, text } Shortly after the author stops editing. html and text are included when your plan includes export.
onUpdateModelValue design Immediately on every edit.
onLocked reason The editor can’t open. See below.
onError Error The editor failed to start, or something unexpected happened.

Loading a design yourself doesn’t trigger onChange or onUpdateModelValue.

React, Vue and Angular

The components take every option as a prop or input, and update the editor when a prop changes. Give their container a height.

React

import { useRef } from 'react'
import { EmailEditor } from 'debloq-email-editor-react'

export function Designer({ design, onSave }) {
  const editor = useRef(null)

  async function save() {
    onSave(await editor.current.exportHtml())
  }

  return (
    <>
      <div style={{ height: 700 }}>
        <EmailEditor
          ref={editor}
          licenseKey="YOUR_LICENSE_KEY"
          modelValue={design}
          theme={{ mode: 'auto', accent: '#0f766e' }}
          onChange={({ design }) => console.log('changed')}
        />
      </div>
      <button onClick={save}>Save</button>
    </>
  )
}

The ref gives every method plus api, controls and devices. className and style apply to the container. In Next.js and other server-rendered apps, use it in a client component.

Vue

<script setup>
import { ref } from 'vue'
import { EmailEditor } from 'debloq-email-editor-vue'

const design = ref(savedDesign)
const editor = ref(null)

async function save() {
  const { html, text } = await editor.value.exportHtml()
}
</script>

<template>
  <div style="height: 700px">
    <EmailEditor
      ref="editor"
      v-model="design"
      license-key="YOUR_LICENSE_KEY"
      :theme="{ mode: 'auto', accent: '#0f766e' }"
    />
  </div>
</template>

v-model keeps design in sync with the editor. Events: @ready, @change, @update:modelValue, @locked, @error. The template ref gives every method.

Angular

import { Component, ViewChild } from '@angular/core'
import { DebloqEmailEditorComponent, type EmailDesign } from 'debloq-email-editor-angular'

@Component({
  selector: 'app-designer',
  standalone: true,
  imports: [DebloqEmailEditorComponent],
  template: `
    <debloq-email-editor
      style="height: 700px"
      licenseKey="YOUR_LICENSE_KEY"
      [(modelValue)]="design"
      [options]="options"
      (locked)="onLocked($event)">
    </debloq-email-editor>
  `
})
export class DesignerComponent {
  @ViewChild(DebloqEmailEditorComponent) editor!: DebloqEmailEditorComponent
  design: EmailDesign | null = null
  options = { theme: { mode: 'auto', accent: '#0f766e' }, brandLogo: 'Acme Mail' }

  onLocked(reason: string) {}

  async save() {
    const { html, text } = await this.editor.exportHtml()
  }
}

licenseKey, version and modelValue are inputs; put every other option in options. Outputs: ready, change, modelValueChange, locked, error. The component has loadDesign, getDesign, exportHtml and exportAmp; use editor.api for the other methods.

Options reference

Live options can be changed after the editor opens (editor.update(), or a changed prop in React, Vue and Angular). The others apply when the editor opens.

Option Description Live
licenseKey Your licence key.
version Editor version. See Versions.
modelValue The design to open. ✓
theme Editor colours and fonts. ✓
brandLogo Toolbar logo.
brand Brand colours and fonts. ✓
fonts Extra fonts.
chrome Show the editor’s top toolbar. Default true.
canvasToolbar Show the canvas toolbar. Default: only when chrome is false.
mergeTags Merge tags.
specialLinks Special links. ✓
utm UTM tracking. ✓
templates Your templates.
customComponents Your own blocks.
imageUpload Upload handler.
imageLibrary Your media library. ✓
stockSearch onStockSelect Stock photos. ✓
savedBlocksStore brandSwatchStore assetStore Author libraries.
onSendTest Send test.
autosaveKey Draft recovery.
onReady onChange onUpdateModelValue onLocked onError Events. ✓

Methods reference

Method Does
loadDesign(design) Opens a design (object or JSON string). Clears undo history.
getDesign() Returns the current design.
exportHtml() Returns { html, text, design }.
exportAmp() Returns { html, text, design, amp }.
openTemplates() Opens the template gallery.
openPreflight() Opens the preflight checks.
openExport() Opens the export dialog.
setDevice(id) Shows the 'desktop' 'tablet' or 'mobile' view.
setPreview(on) togglePreview() Turns preview mode on or off.
undo() redo() Undo and redo.
setTheme(theme) Changes the theme.
setBrand(brand) Changes the brand kit.
setSpecialLinks(list) Changes the special links.
setUtm(config) Changes UTM tracking.
setImageLibrary(fn) setStockSearch(fn) setStockPick(fn) Change the image sources.
hasDraft() getDraft() restoreDraft() discardDraft() Draft recovery.
Property Contains
devices Available device views, { id, label }[].
controls Editor state when it opened.
draft { available, savedAt } when draft recovery is on.

A method rejects if the editor isn’t ready yet, or if your plan doesn’t include that feature.

When the editor doesn’t open

Instead of the editor, the author sees a message, onLocked(reason) is called and mounted resolves with null.

Reason What to do
MissingKey Pass your licenseKey.
UnknownKey Check the key in your Debloq dashboard.
Revoked The key was revoked. Create a new one in your dashboard.
Inactive Your subscription isn’t active. Check billing in your dashboard.
DomainMismatch Add this site’s domain to your project in the dashboard.
ServiceUnavailable Temporary. The author can press Retry.
PremiumUnavailable Temporary. The author can press Retry.

After a successful retry, onReady is called and the editor works normally.

Versions

version Loads
'latest' (default) The newest release
'stable' The recommended release
'2.0.4' Exactly that release

Releases never change after publication, so pinning a version keeps the editor exactly as you tested it. When pinning, use the same version in the script URL:

<script src="https://editor.debloq.io/2.0.4/embed.js"></script>
<script>
  Debloq.createEmailEditor('#editor', { licenseKey: 'YOUR_LICENSE_KEY', version: '2.0.4' })
</script>

Stop renting features. Start shipping emails.

Drop the editor into your product today. Every block, every export and every update is already included.