Skip to content

How authentication works

The api-key attribute must be a session token — not your secret API key. Your backend calls POST /v1/sessions and returns a 2-hour JWT that's safe to use in the browser. See Authentication for details.

Server-side endpoint (example: Express)

js
// server.js — runs server-side only
app.get('/api/editor-session', requireAuth, async (req, res) => {
  const { sessionToken } = await fetch('https://api.maillune.com/v1/sessions', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.MAILLUNE_SECRET_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ userId: req.user.id }),
  }).then(r => r.json());

  res.json({ sessionToken });
});

Via CDN (no bundler)

html
<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <script src="https://cdn.jsdelivr.net/npm/@maillune/sdk/dist/email-builder.umd.js"></script>
</head>
<body>
  <div style="height: 100vh">
    <email-builder id="builder" user-id="user_123"></email-builder>
  </div>

  <script>
    const builder = document.getElementById('builder');

    // 1. Fetch the session token from your backend
    fetch('/api/editor-session')
      .then(r => r.json())
      .then(({ sessionToken }) => {
        // 2. Set it on the element — never hardcode the secret key here
        builder.setAttribute('api-key', sessionToken);
      });

    // Save
    builder.addEventListener('eb:save', async (e) => {
      const { json } = e.detail;
      await fetch('/api/templates', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(json),
      });
    });

    // Load an existing template
    fetch('/api/templates/latest')
      .then(r => r.json())
      .then(data => builder.load(data.blockTree));
  </script>
</body>
</html>

Via ES module (with a bundler)

html
<!-- index.html -->
<div style="height: 100vh">
  <email-builder id="builder" user-id="user_123"></email-builder>
</div>
<script type="module" src="/main.js"></script>
js
// main.js
import '@maillune/sdk';

const builder = document.getElementById('builder');

// Fetch the session token from your backend
const { sessionToken } = await fetch('/api/editor-session').then(r => r.json());
builder.setAttribute('api-key', sessionToken);

builder.addEventListener('eb:save', (e) => {
  console.log('Design saved:', e.detail.json);
});

Dynamically update config

js
// Switch theme on a toggle
document.getElementById('theme-toggle').addEventListener('change', (e) => {
  builder.updateConfig({ theme: e.target.checked ? 'dark' : 'light' });
});

// Load merge tags from your API
async function loadMergeTags() {
  const tags = await fetch('/api/merge-tags').then(r => r.json());
  builder.updateConfig({ mergeTags: tags });
}

loadMergeTags();

Error handling

js
builder.addEventListener('eb:error', (e) => {
  const { code, message } = e.detail;
  console.error(`[MMS] ${code}: ${message}`);

  // Show a toast, log to Sentry, etc.
  showNotification(`Editor error: ${message}`, 'error');
});