Use this page when your app embeds the builder in an iframe and you need Save and Save as template buttons in your own UI (outside the iframe). All communication uses the browser postMessage API with the BUILDER_BRIDGE protocol.
Every message must use this shape:
| Field | Required | Description |
|---|---|---|
channel | Yes | Always "BUILDER_BRIDGE". Ignore messages with any other channel. |
type | Yes | e.g. "READY", "SAVE_STATE", "SAVE_REQUEST", "SAVE_RESULT", "SAVE_TEMPLATE_REQUEST", "SAVE_TEMPLATE_RESULT". |
payload | Yes | Object (can be {}). Type-specific data. |
requestId | No | Optional. If you send it with a request, the response will echo it so you can match requests to responses. |
In your window.addEventListener('message', ...) handler, always check event.origin against the builder’s origin (e.g. the origin of your iframe’s src) before using event.data. Only process messages where event.data?.channel === "BUILDER_BRIDGE".
The builder has tabs: Builder, Headers, Footers, and Social. Saving a campaign or template is only valid on the Builder tab (the in-builder Save buttons are disabled on the other tabs).
Your parent app must mirror this: disable your external Save and Save as template buttons when the user is not on the Builder tab.
After READY, the iframe sends SAVE_STATE whenever the active tab changes:
{
channel: 'BUILDER_BRIDGE',
type: 'SAVE_STATE',
payload: {
saveEnabled: true, // enable your Save button
saveTemplateEnabled: true // enable your Save as template button
}
}
On Headers, Footers, or Social, both flags are false. Re-enable them when the user returns to Builder.
Do not enable Save buttons on READY alone — wait for SAVE_STATE (sent immediately after READY). Optionally show a hint such as “Switch to Builder tab to save” when saveEnabled is false.
payload.saveEnabled on each SAVE_STATE.requestId if you sent one). Then hide “Saving...” and show success or payload.error.const builderOrigin = new URL(iframeEl.src).origin;
iframeEl.contentWindow.postMessage({
channel: 'BUILDER_BRIDGE',
type: 'SAVE_REQUEST',
payload: {},
requestId: 'save-' + Date.now() // optional, for correlation
}, builderOrigin);
let ready = false;
let saveEnabled = false;
let saveTemplateEnabled = false;
let saving = false;
let savingTemplate = false;
function updateSaveButtons() {
saveBtn.disabled = !ready || saving || !saveEnabled;
saveTemplateBtn.disabled = !ready || savingTemplate || !saveTemplateEnabled;
}
window.addEventListener('message', function (event) {
if (event.origin !== builderOrigin || event.data?.channel !== 'BUILDER_BRIDGE') return;
const { type, payload, requestId } = event.data;
if (type === 'READY') {
ready = true;
updateSaveButtons();
}
if (type === 'SAVE_STATE') {
saveEnabled = !!payload.saveEnabled;
saveTemplateEnabled = !!payload.saveTemplateEnabled;
updateSaveButtons();
}
if (type === 'SAVE_RESULT') {
saving = false;
updateSaveButtons();
if (payload.ok) {
// Success: payload.newsletterId
} else {
// Failure: show payload.error (e.g. "Switch to the Builder tab to save")
}
}
});
Same idea as Save: you send SAVE_TEMPLATE_REQUEST, the iframe runs “Save as template without content” (user may be prompted for template name inside the iframe), then you receive one SAVE_TEMPLATE_RESULT.
iframeEl.contentWindow.postMessage({
channel: 'BUILDER_BRIDGE',
type: 'SAVE_TEMPLATE_REQUEST',
payload: {},
requestId: 'template-' + Date.now()
}, builderOrigin);
if (type === 'SAVE_TEMPLATE_RESULT') {
if (payload.ok) {
// Success: payload.templateName
} else {
// Failure: show payload.error (e.g. "Template name missing or cancelled", "Duplicate template name")
}
}
The template name is entered by the user in a modal inside the iframe. Your app only triggers the action and then handles success/error.
/builder/launch?token=<token> (optional: &parent_origin=<your origin>).message; filter by channel === "BUILDER_BRIDGE" and event.origin.saveEnabled / saveTemplateEnabled and update button disabled state.saveEnabled): show “Saving...”, send SAVE_REQUEST, then on SAVE_RESULT handle payload.ok / payload.error.saveTemplateEnabled): send SAVE_TEMPLATE_REQUEST, then on SAVE_TEMPLATE_RESULT handle success or payload.error.