Configuration
The editorConfiguration
When you use EmailEditor directly, a config object tailors appearance, branding, panels, localization, AI, and more.
interface MailkaroConfig {
appearance?: { mode?: "light" | "dark"; direction?: "ltr" | "rtl"; theme?: MailkaroTheme; customCss?: string | string[] };
branding?: { badge?; assetLibraryTitle?; templateLibraryTitle?; paletteLogoUrl?; paletteLogoAlt?; toolbarName?; toolbarLogoUrl? };
ui?: { panels?: { content?; blocks?; body? }; minimap?: boolean; rowInset?: boolean; builderLook?: boolean; devices?: { id; label; width; icon? }[]; toolbar?: { … } };
localization?: { locale?: string; messages?: Partial<MailkaroMessages> };
assets?: { googleDrive?; s3Connector?; unsplashConnector?; pexelsConnector? };
blocks?: { disabled?: BlockType[]; limits?: Partial<Record<BlockType, number>> };
colors?: { presets?: string[]; brand?: string[]; recent?: boolean | number };
sendTestEmail?: (email: { html; subject; to }) => Promise<void>;
linkTypes?: { id; label; url_template?; prefix?; placeholder? }[];
comments?: { key?; user; members?; canWrite?; canModerate?; load; create; reply; resolve; remove }; // see Comments
textEditor?: { customButtons?: { name; label?; icon?; text?; onAction? }[] };
ai?: { adapter?: MailkaroAiAdapter; defaultPrompt?; defaultSubjectCount? };
collaboration?:{ sessionId; user; adapter?; showPresence? };
role?: "owner" | "admin" | "editor" | "viewer"; // "viewer" ⇒ read-only editor
entitlements?: MailkaroEntitlements; // usually server-supplied
policy?: MailkaroProjectPolicy;
plugins?: MailkaroPlugin[];
}role gates the acting user in the embedded editor, a viewer gets a read-only editor (same effect as policy.read_only); editor / admin / owner can edit. Managing a team roster (inviting members, assigning their roles) is a dashboard concern, separate from this.appearance#
mode switches light/dark; direction supports RTL; theme overrides CSS variables, bg, panel, text, accent, accentStrong, canvas, font, radiusLg, shadow, and more.
<EmailEditor
config={{
appearance: {
mode: "dark", theme: { accent: "#0d9488", accentStrong: "#0f766e", font: "Inter" }
}
}}
/>branding & ui#
branding.paletteLogoUrl, your logo in the palette footer (needs thebranding.white_labelentitlement).branding.toolbarNameandbranding.toolbarLogoUrl, your own name and logo at the left of the toolbar instead of MailKaro (needs thebranding.white_labelentitlement).ui.toolbar.brand, set itfalseto hide that brand block, for example when your page already shows your logo. This works on every plan.ui.panels, hide any ofcontent,blocks,bodyby setting itfalse.ui.devices, the screen sizes in the toolbar, as{ id, label, width, icon? }. The default is Desktop and Mobile. The first size is the one the editor opens in,desktopkeeps the design's own content width, and no size is ever wider than that width.ui.minimap,trueadds a small map of the whole email at the top right of the canvas, a box for each row and a frame for what is on screen. Click or drag it to scroll, or use the arrow keys. It is off by default, it hides when the whole email already fits, and it is not shown while previewing.ui.rowInset,truedraws each row's left and right padding on the canvas, as the sent email does, so the colour behind the cards shows around them. Off by default, which keeps the canvas as it always was.ui.builderLook,truegives each builder its own look on the canvas: a sheet of paper fordocument, a browser frame forpageand a floating card forpopup. Off by default, and it never changes the exported HTML, PDF or document, only how the editor draws the canvas.ui.toolbar, toggleundoRedo,deviceToggle,preview,saveVersion,exportJson,exportHtml, or addcustomActions. Paid toolbar items also require the matching entitlement.
blocks#
blocks.disabled removes block types from the palette and stops them being added. blocks.limits caps how many blocks of a type one design can hold; the palette tile says "Limit reached" until a block is deleted. The config only narrows what the plan already allows. Blocks already in a design stay, and drag, click, duplicate and paste all follow the same rules.
<EmailEditor config={{ blocks: { disabled: ["video", "raw-html"], limits: { image: 6, button: 3 } } }} />colors#
Every color field has a small arrow beside the swatch that opens your saved colors: colors.brand first, then colors.presets, then the colors the person used last. recent is true (8 colors) by default, a number sets how many, and false turns it off. Recent colors live in the browser's local storage; when storage is blocked the picker still works. Values must be hex colors such as #0d9488, and anything else is ignored. With no palette and recent colors off, the field is just the native picker.
<EmailEditor config={{ colors: { brand: ["#0d9488", "#134e4a"], presets: ["#ffffff", "#111827"], recent: 6 } }} />linkTypes#
linkTypes adds your own kinds of button link to the Action Type list, such as WhatsApp or a booking page. The person types a value and the editor builds the link: give a url_template with {value} in it, or a prefix the value is added to. The value is URL-encoded (merge tags in it are kept), the finished link is stored on the button, and the typed value is kept so it can be edited later. A type is ignored unless it starts with https://, http://, mailto:, tel: or a merge tag, because email clients drop other kinds of link. The ids open-website, send-email, call-phone, unsubscribe and view-in-browser are taken.
<EmailEditor
config={{
linkTypes: [
{ id: "whatsapp", label: "WhatsApp", url_template: "https://wa.me/{value}", placeholder: "919876543210" },
{ id: "booking", label: "Booking page", prefix: "https://cal.example.com/", placeholder: "team-name" }
]
}}
/>comments and sendTestEmail#
commentskeeps comment threads on your own server instead of in the browser. See Comments for the adapter.sendTestEmailadds a "Send a test email" button that calls your function. See Test email.ui.toolbar.sendTestEmail: falsehides it.
textEditor#
textEditor.customButtons adds up to eight buttons to the text and heading toolbar. A button either inserts text or runs onAction(selectedText, apply); apply(text) replaces the selection, or inserts at the cursor when nothing is selected. It writes plain text, never HTML, and it can be called later (after a fetch) because the selection is remembered when the button is clicked. A button needs a name made of letters, numbers, dashes or underscores, and one of text or onAction; others are ignored. A function that throws does not break editing.
<EmailEditor
config={{
textEditor: {
customButtons: [
{ name: "signature", label: "Insert signature", icon: "Sg", text: "Warm regards, The team" },
{ name: "upper", label: "Make uppercase", icon: "AA", onAction: (selected, apply) => apply(selected.toUpperCase()) }
]
}
}}
/>ai & collaboration#
Plug your own AI backend via ai.adapter (four async methods: generate design, rewrite text, subject lines, alt text). Enable live presence via collaboration. Both are gated by plan + policy.
policy#
A per-project policy can only remove capability the plan granted: read_only, allow_ai, allow_exports, allow_uploads, allow_collaboration, allow_raw_html, allow_external_images, allow_custom_fonts. See Plans & entitlements.