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