Skip to content

Install

bash
npm install @maillune/sdk

How 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();
}