Plugin SDK Developer Guide
MiN8T's Plugin SDK lets you embed a full-featured email editor directly into your own application. Your users design emails inside your product, under your brand, without ever knowing MiN8T exists behind the scenes. This guide covers the complete integration path -- from loading the SDK script to production deployment with white-labeling and performance optimization.
The SDK is used by ESP platforms, marketing automation tools, CRM systems, and SaaS products that need email design capabilities without building an editor from scratch. If your product involves emails and your users need to design them, this guide is for you.
1 SDK Architecture Overview
The SDK operates as an iframe-based embed. When you initialize the editor, the SDK loads MiN8T's editor application inside a sandboxed iframe within your designated container element. All communication between your host application and the editor happens through a postMessage-based API with typed event contracts.
Why iframe isolation?
- CSS isolation -- your application's styles cannot accidentally break the editor, and the editor's styles cannot leak into your application
- JavaScript isolation -- the editor runs in its own execution context, preventing conflicts with your application's libraries, frameworks, and global state
- Security boundary -- the iframe sandbox enforces a clean security boundary. Credentials and session state are isolated.
- Independent updates -- MiN8T can ship editor updates without requiring you to update your integration code. The SDK version pins the API contract, not the editor implementation.
Data flow
The typical data flow for an SDK integration:
- Your backend generates a short-lived plugin token by calling MiN8T's auth API with your project credentials
- Your frontend initializes the SDK with this token and a container element
- The SDK loads the editor iframe and authenticates using the token
- The user designs their email using the drag-and-drop editor
- Your application calls
min8t.getHtml()to retrieve the compiled email HTML - Your backend stores or sends the HTML through your own sending infrastructure
No vendor lock-in: The HTML output from getHtml() is standard email HTML. It does not contain any MiN8T-specific runtime dependencies. You can send it through any ESP, store it in any database, and render it in any email client. If you stop using MiN8T, your existing emails continue to work.
2 Quick Start Integration
The fastest path to a working editor is three steps: include the SDK script, create a container element, and call init(). Here is a minimal example:
<!DOCTYPE html>
<html>
<head>
<script src="https://sdk.min8t.com/v1/editor.js"></script>
</head>
<body>
<div id="min8t-editor" style="width:100%; height:800px;"></div>
<script>
const editor = Min8tEditor.init({
container: '#min8t-plugin',
token: 'YOUR_PLUGIN_TOKEN',
projectId: 'YOUR_PROJECT_ID',
onReady: function() {
console.log('Editor loaded and ready');
}
});
</script>
</body>
</html>
That is a fully functional email editor. Your users can drag blocks, edit text, upload images, and design complete email campaigns. When you need the output:
// Get the compiled email HTML
editor.getHtml(function(html) {
console.log(html); // Full email HTML, ready to send
});
// Get the editor's internal JSON (for save/restore)
editor.getJson(function(json) {
// Store this JSON to reload the design later
localStorage.setItem('emailDesign', JSON.stringify(json));
});
// Load a previously saved design
editor.loadJson(JSON.parse(localStorage.getItem('emailDesign')));
// Load from a template ID
min8t.setHtml('template-uuid-here');
Token security: Never embed your project secret key in frontend code. The token parameter should be a short-lived plugin token generated by your backend. Plugin tokens expire after 1 hour and are scoped to a single editor session.
3 Framework Patterns
The vanilla JS integration works everywhere, but if you are using a modern framework, the SDK provides adapter patterns that integrate with each framework's lifecycle and state management.
React
import { useRef, useEffect, useCallback } from 'react';
function EmailEditor({ token, projectId, onSave }) {
const containerRef = useRef(null);
const editorRef = useRef(null);
useEffect(() => {
if (!containerRef.current || editorRef.current) return;
editorRef.current = Min8tEditor.init({
container: containerRef.current,
token,
projectId,
onReady: () => console.log('Editor ready'),
onChange: () => console.log('Content changed'),
});
return () => {
editorRef.current?.destroy();
editorRef.current = null;
};
}, [token, projectId]);
const handleSave = useCallback(() => {
editorRef.current?.getHtml((html) => {
editorRef.current?.getJson((json) => {
onSave({ html, json });
});
});
}, [onSave]);
return (
<div>
<div ref={containerRef} style={{ width: '100%', height: '800px' }} />
<button onClick={handleSave}>Save Email</button>
</div>
);
}
Angular
import { Component, ElementRef, ViewChild, OnInit, OnDestroy, Input } from '@angular/core';
@Component({
selector: 'app-email-editor',
template: `
<div #editorContainer style="width:100%; height:800px"></div>
<button (click)="save()">Save Email</button>
`
})
export class EmailEditorComponent implements OnInit, OnDestroy {
@ViewChild('editorContainer', { static: true }) container!: ElementRef;
@Input() token!: string;
@Input() projectId!: string;
private editor: any;
ngOnInit() {
this.editor = (window as any).Min8tEditor.init({
container: this.container.nativeElement,
token: this.token,
projectId: this.projectId,
onReady: () => console.log('Editor ready'),
});
}
save() {
this.editor.getHtml((html: string) => {
// Send to your API
});
}
ngOnDestroy() {
this.editor?.destroy();
}
}
Vue 3
<template>
<div>
<div ref="editorContainer" style="width:100%; height:800px" />
<button @click="save">Save Email</button>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
const props = defineProps(['token', 'projectId']);
const editorContainer = ref(null);
let editor = null;
onMounted(() => {
editor = window.Min8tEditor.init({
container: editorContainer.value,
token: props.token,
projectId: props.projectId,
onReady: () => console.log('Editor ready'),
});
});
function save() {
editor?.getHtml((html) => {
// Send to your API
});
}
onUnmounted(() => editor?.destroy());
</script>
Key pattern: In every framework, the lifecycle is the same: initialize on mount, clean up on unmount. Always call min8t.destroy() in your cleanup to prevent memory leaks and orphaned iframes.
4 Authentication & Security
The SDK uses a two-tier authentication model: project-level credentials for your backend, and session-scoped plugin tokens for the frontend.
Generating plugin tokens
Your backend calls MiN8T's auth endpoint with your project ID and secret key. The response is a short-lived plugin token that you pass to the frontend SDK.
// Backend: Generate a plugin token
const response = await fetch('https://api.min8t.com/v1/plugin/token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'ES-PLUGIN-AUTH': process.env.MIN8T_PROJECT_SECRET,
},
body: JSON.stringify({
projectId: process.env.MIN8T_PROJECT_ID,
userId: currentUser.id, // Your user's ID (for audit trails)
permissions: ['edit', 'export'], // Scoped permissions
ttl: 3600, // Token lifetime in seconds
}),
});
const { token } = await response.json();
// Return this token to your frontend
Permission scoping
Plugin tokens can be scoped with granular permissions:
| Permission | Description | Use Case |
|---|---|---|
edit | Full editor access: drag-and-drop, text editing, image upload | Content creators, designers |
view | Read-only preview of the email design | Approvers, reviewers |
export | Can call getHtml() and getJson() | Automated pipelines |
template | Can load and save templates | Template library management |
upload | Can upload images to the asset CDN | Controlled by your asset policy |
Security best practice: Always generate tokens server-side. Never expose your project secret key in frontend code, environment variables accessible to the browser, or client-side configuration files. Rotate your secret key immediately if it is ever exposed.
5 Customization API
The SDK exposes a comprehensive customization API that lets you control the editor's appearance and behavior to match your product.
Toolbar configuration
Min8tEditor.init({
container: '#editor',
token: pluginToken,
projectId: 'your-project-id',
// Toolbar customization
toolbar: {
items: ['text', 'image', 'button', 'divider', 'spacer', 'video'],
// Hide blocks you don't need:
hidden: ['social', 'menu', 'html'],
},
// Block restrictions
blocks: {
maxImages: 10,
allowHtmlBlock: false,
allowCustomCode: false,
imageMaxSize: 2 * 1024 * 1024, // 2MB
imageAllowedTypes: ['image/jpeg', 'image/png', 'image/gif'],
},
// White-labeling
branding: {
logo: null, // null = no logo shown
poweredBy: false, // Hide "Powered by MiN8T"
primaryColor: '#4F46E5', // Your brand color
fontFamily: 'Inter, sans-serif',
},
// Locale
locale: 'en', // Supported: en, es, fr, de, pt, ja, zh, ko
// Event hooks
onReady: () => {},
onChange: (event) => {},
onSave: (html, json) => {},
onImageUpload: (file, callback) => {
// Custom image upload handler
uploadToYourCdn(file).then(url => callback(url));
},
onError: (error) => console.error(error),
});
Custom image upload handler
By default, images uploaded in the editor are stored on MiN8T's CDN. If you want images stored on your own infrastructure, provide an onImageUpload handler:
onImageUpload: async function(file, callback) {
const formData = new FormData();
formData.append('image', file);
const response = await fetch('/api/images/upload', {
method: 'POST',
body: formData,
});
const { url } = await response.json();
callback(url); // The editor will use this URL for the image
}
Event hooks reference
onReady-- fired when the editor iframe is loaded and interactive. Safe to call API methods after this.onChange-- fired on every content change (throttled to 300ms). Use for auto-save or dirty-state tracking.onSave-- fired when the user triggers a save action (Ctrl+S or toolbar save button).onImageUpload-- intercepts image uploads to route them through your own CDN.onError-- fired on SDK errors. Includes error code, message, and recovery suggestion.onExport-- fired when the user exports the email to an ESP format.
6 Performance & Deployment
Loading performance
The SDK script itself is under 15KB (gzipped). The editor application loads asynchronously inside the iframe after init() is called. Typical load times:
| Metric | Value | Optimization |
|---|---|---|
| SDK script load | ~50ms | Served from global CDN with aggressive caching |
| Editor iframe load | ~800ms | Preloaded assets, code-split bundles |
| Time to interactive | ~1.2s | Progressive rendering, lazy-load non-critical panels |
| Subsequent loads | ~400ms | Service worker caching, HTTP/2 push |
Preloading for instant start
If you know the user will open the editor (for example, they are on a "create email" page), you can preload the SDK resources before they click:
// Preload editor resources (call early, before user clicks "create")
Min8tEditor.preload({ projectId: 'your-project-id' });
// Later, when the user clicks "create email":
const editor = Min8tEditor.init({
container: '#editor',
token: pluginToken,
projectId: 'your-project-id',
// Resources are already cached -- editor loads in ~400ms
});
Versioning strategy
The SDK uses semantic versioning. The script URL includes the major version (/v1/editor.js), which guarantees backward compatibility for all minor and patch updates. Breaking changes only occur on major version bumps, giving you full control over when to migrate.
/v1/editor.js-- always points to the latest v1.x.x release. Safe for production./v1.5/editor.js-- pins to v1.5.x. Use if you need to freeze on a specific minor version while you test upgrades./v1.5.2/editor.js-- pins to an exact version. Use for maximum stability in regulated environments.
Deployment checklist: Before going to production, verify: (1) plugin tokens are generated server-side, (2) min8t.destroy() is called on unmount, (3) onError handler reports to your monitoring system, (4) image upload handler points to your production CDN, (5) Content Security Policy allows the MiN8T iframe origin.
- Plugin tokens generated server-side -- secret key never exposed to the browser
- Container element sized correctly -- minimum 800px height recommended for usable editing experience
- Cleanup on unmount --
min8t.destroy()called in all framework teardown paths - Error handler wired --
onErrorcallback connected to your error monitoring (Sentry, Datadog, etc.) - CSP headers updated -- allow
frame-src sdk.min8t.comandconnect-src api.min8t.com - Image upload handler tested -- verify files upload to your CDN and URLs are accessible
Embed a full email editor in your product
Get your Plugin SDK credentials and start integrating in minutes.
Get SDK access