Appearance
Install
bash
npm install @maillune/sdkHow authentication works
The api-key attribute must be a session token — not your secret API key. Your backend calls POST /v1/sessions server-side and returns a 2-hour JWT that's safe to use in the browser. See Authentication for details.
Don't put your key in environment.ts
Angular environment files are compiled into the browser bundle. A mmsApiKey in environment.ts is the same as hardcoding it in HTML. Always fetch a session token from a server-side endpoint.
Enable custom elements
In your AppModule (or standalone component), add CUSTOM_ELEMENTS_SCHEMA:
ts
// app.module.ts
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
// ...
})
export class AppModule {}Or for a standalone component:
ts
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
@Component({
standalone: true,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
// ...
})
export class EmailEditorComponent {}Backend: session token endpoint
Your Angular app needs a server-side route that returns a session token. Here's an example in Express / NestJS — or use any BFF (Next.js, Nitro, etc.):
ts
// server route — GET /api/editor-session
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}`, // server-only env var
'Content-Type': 'application/json',
},
body: JSON.stringify({ userId: req.user.id }),
}).then(r => r.json());
res.json({ sessionToken });
});EmailEditor component
ts
// email-editor.component.ts
import {
Component, Input, Output, EventEmitter,
OnInit, AfterViewInit, OnDestroy, ViewChild, ElementRef,
CUSTOM_ELEMENTS_SCHEMA,
} from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { firstValueFrom } from 'rxjs';
import '@maillune/sdk';
import type { EmailBuilderElement, BlockTree } from '@maillune/sdk';
@Component({
standalone: true,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
selector: 'app-email-editor',
template: `
@if (sessionToken) {
<div style="height: 100vh">
<email-builder
#builder
[attr.api-key]="sessionToken"
[attr.user-id]="userId"
theme="auto"
></email-builder>
</div>
} @else {
<div>Loading editor…</div>
}
`,
})
export class EmailEditorComponent implements OnInit, AfterViewInit, OnDestroy {
@Input() userId = '';
@Input() initialTemplate?: BlockTree;
@Output() saved = new EventEmitter<BlockTree>();
@ViewChild('builder') builderRef!: ElementRef<EmailBuilderElement>;
sessionToken = '';
private http = inject(HttpClient);
async ngOnInit() {
// Fetch session token from your backend — the secret key never reaches the browser
const { sessionToken } = await firstValueFrom(
this.http.get<{ sessionToken: string }>('/api/editor-session')
);
this.sessionToken = sessionToken;
}
ngAfterViewInit() {
const el = this.builderRef?.nativeElement;
if (!el) return;
el.addEventListener('eb:save', (e: Event) => {
const { json } = (e as CustomEvent<{ json: BlockTree }>).detail;
this.saved.emit(json);
});
if (this.initialTemplate) {
el.load(this.initialTemplate);
}
}
}Usage
html
<!-- parent.component.html -->
<app-email-editor
[userId]="currentUser.id"
[initialTemplate]="template"
(saved)="onTemplateSaved($event)"
style="display: block; height: 100vh"
/>ts
// parent.component.ts
onTemplateSaved(json: BlockTree) {
this.templateService.save(this.templateId, json).subscribe();
}