@useinsider/guido
v3.6.0
Published
Guido is a Vue + TypeScript wrapper for Email Plugin. Easily embed the email editor in your Vue applications.
Readme
@useinsider/guido
Guido is a Vue 2 + TypeScript wrapper for the Stripo Email Editor plugin. Easily embed the professional email editor in your Vue applications with a clean, type-safe configuration.
Installation
npm install @useinsider/guidoPrerequisites
Your project needs:
- Vue 2.7+
- Pinia (state management)
Add these to your bundler config to optimize dependencies:
Webpack (webpack.config.js or vue.config.js):
shared: {
vue: { singleton: true },
pinia: { singleton: true },
},Vite (vite.config.js):
resolve: {
dedupe: ['vue', 'pinia'],
},Quick Start
<template>
<Guido
ref="guidoRef"
:config="config"
@ready="onReady"
@dynamic-content:open="onDynamicContentOpen"
@save:complete="onSaveComplete"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { Guido } from '@useinsider/guido';
import type { GuidoConfigInput } from '@useinsider/guido';
const guidoRef = ref<InstanceType<typeof Guido> | null>(null);
const config = ref<GuidoConfigInput>({
identity: {
templateId: 'template-123',
userId: 'user-456',
},
partner: {
name: 'your-partner-name',
},
template: {
html: '<p>Initial content</p>',
css: '',
},
});
const onReady = () => console.log('Editor ready');
const onDynamicContentOpen = () => console.log('Dynamic content requested');
const onSaveComplete = (template) => console.log('Saved:', template);
</script>Configuration
Guido uses a single, validated config prop organized by domain:
import type { GuidoConfigInput } from '@useinsider/guido';
const config: GuidoConfigInput = {
// Required: Identity
identity: {
templateId: string, // Required - Template identifier
userId: string, // Required - User identifier
variationId?: string, // Optional - A/B test variation
},
// Required: Partner
partner: {
name: string, // Required - Partner name
productType?: number, // Optional - Default: 60 (Email)
messageType?: number, // Optional - Default: 1 (Promotional)
username?: string, // Optional - Default: 'Guido User'
},
// Optional: Template content
template?: {
html?: string,
css?: string,
preselectedDynamicContent?: DynamicContent[],
selectedUnsubscribePages?: number[],
forceRecreate?: boolean, // Default: false - Force recreate template in Stripo storage
migration?: {
// Legacy block configs keyed by block ID. Consumed once by the migrator
// when upgrading templates authored with v1 block formats. See the
// "Template Migration" section below.
recommendationConfigs?: Record<string, LegacyRecommendationConfig>,
},
},
// Optional: Editor settings
editor?: {
locale?: string, // Default: 'en'
translationsPath?: string, // Default: 'window.trans.en'
migrationDate?: number, // Stripo migration timestamp
emailHeader?: {
senderName?: string,
subject?: string,
},
savedModulesFolderName?: string, // Default: 'savedModules' - folder name for user-saved modules
defaultModulesFolderName?: string, // Default: 'defaultModules' - folder name for default/prebuilt modules
},
// Optional: UI settings
ui?: {
showHeader?: boolean, // Default: true
backButtonLabel?: string,
},
// Optional: Feature toggles
features?: {
dynamicContent?: boolean, // Default: true
saveAsTemplate?: boolean, // Default: true
versionHistory?: boolean, // Default: true
testMessage?: boolean, // Default: true
displayConditions?: boolean, // Default: true
unsubscribe?: boolean, // Default: true
modulesDisabled?: boolean, // Default: false - Disable modules panel
liquidSyntax?: boolean, // Default: false - Enable Liquid template syntax
autosave?: boolean, // Default: false - Show the Auto Save toggle in the header. See wiki/AUTOSAVE.md.
},
// Optional: Callbacks
callbacks?: {
externalValidation?: (data: SavedTemplateDetails) => Promise<boolean>, // Return false to cancel save
},
// Optional: Block configuration
blocks?: {
excludeDefaults?: DefaultBlockType[],
includeCustoms?: CustomBlockType[],
},
// Optional: HTML compiler
compiler?: {
customRules?: CompilerRule[],
ignoreDefaultRules?: boolean, // Default: false
},
};Events
| Event | Payload | Description |
|-------|---------|-------------|
| ready | - | Editor is loaded and ready |
| dynamic-content:open | DynamicContent \| null | User wants to insert dynamic content |
| back | - | Back button clicked |
| save:start | - | Save process started |
| save:complete | SavedTemplateDetails | Save completed successfully (includes metadata) |
| on-change | - | Template was modified |
| test-email:click | - | Test email button clicked |
| onboarding-finished | - | Onboarding popup dismissed |
save:complete Payload
The save:complete event emits a SavedTemplateDetails object containing the compiled template data along with editor metadata:
interface SavedTemplateDetails {
dynamicContentList: DynamicContent[];
compiledHtml: string;
rawHtml: string;
css: string;
ampHtml: string;
ampErrors: string[];
modules: number[];
recommendation: {
campaignUrls: Record<string, string>;
configs: Record<string, string>;
};
unsubscribe: {
status: boolean;
config: number[];
};
metadata: Metadata;
silent: boolean; // true when triggered by autosave, false when user clicked Save
}
interface Metadata {
emailId: string;
partnerName?: string;
productType?: number;
userId: string;
username?: string;
savedModulesFolderName?: string;
defaultModulesFolderName?: string;
}⚠️ Important: The
metadataobject in thesave:completepayload must be passed directly into yourtemplateConfig/stripoConfigobject while saving. This ensures the email-service receives the correct version of sync modules features
Exposed Methods
const guidoRef = ref<InstanceType<typeof Guido> | null>(null);
// Insert dynamic content
guidoRef.value?.dynamicContent.insert({
text: 'Display Name',
value: 'variable_name',
fallback: 'Default Value',
});
// Close dynamic content modal
guidoRef.value?.dynamicContent.close();
// Silent save (no UI feedback)
guidoRef.value?.saveSilent();Block Configuration
Default Blocks (can be excluded)
type DefaultBlockType =
| 'amp-accordion'
| 'amp-carousel'
| 'amp-form-controls'
| 'banner-block'
| 'button-block'
| 'html-block'
| 'image-block'
| 'menu-block'
| 'social-block'
| 'spacer-block'
| 'text-block'
| 'timer-block'
| 'video-block';Custom Blocks (opt-in)
type CustomBlockType =
| 'dynamic-content'
| 'checkbox-block'
| 'radio-button-block'
| 'recommendation-block'
| 'unsubscribe-block'
| 'coupon-block'
| 'items-block';Examples
// Exclude specific default blocks
const config: GuidoConfigInput = {
identity: { templateId: 'tpl-123', userId: 'user-456' },
partner: { name: 'partner' },
blocks: {
excludeDefaults: ['timer-block', 'video-block'],
},
};
// Include custom blocks
const config: GuidoConfigInput = {
identity: { templateId: 'tpl-123', userId: 'user-456' },
partner: { name: 'partner' },
blocks: {
includeCustoms: ['recommendation-block', 'coupon-block', 'dynamic-content'],
},
};Module Folder Configuration
Customize the Stripo module folder names for saved and default modules. These values are passed to Stripo metadata and the dynamic folder paths are constructed in the Stripo plugin panel configuration.
const config: GuidoConfigInput = {
identity: { templateId: 'tpl-123', userId: 'user-456' },
partner: { name: 'acme' },
editor: {
// Folder name for user-saved modules
savedModulesFolderName: 'savedModules',
// Folder name for default/prebuilt modules
defaultModulesFolderName: 'defaultModules',
},
};Default Values
| Config Option | Default Value |
|--------------|---------------|
| savedModulesFolderName | 'savedModules' |
| defaultModulesFolderName | 'defaultModules' |
Note: The actual folder paths (e.g.,
guido_acme_savedModules) are configured in the Stripo plugin panel using variable substitution like${savedModulesFolderName}.
Autosave
Guido ships an opt-in autosave that saves on a 3-minute interval and when the user leaves the tab. Enable it with the features.autosave feature flag — this shows an "Auto Save" toggle in the editor header; the end user switches autosave on per session.
const config: GuidoConfigInput = {
identity: { templateId: 'tpl-123', userId: 'user-456' },
partner: { name: 'partner' },
features: {
autosave: true, // Default: false — shows the Auto Save toggle in the header
},
};- Default is
false— integrations see no change unless they opt in. - Autosave reuses the same save pipeline as the Save button, so your existing
@save:completehandler receives autosave output identically to a manual save. No new events or callbacks. - Toggle state is session-only (Pinia) — resets to OFF on reload.
For a deep dive on triggers, guards, and limitations, see wiki/AUTOSAVE.md.
Template Migration
When a template was authored with v1 block formats (notably the legacy recommendation block), Guido needs additional context that cannot be recovered from the saved HTML alone — things like filter rules, recommendation strategies, currency settings, locale, and pinned product IDs.
Pass that context once via template.migration when loading the template. The migrator consumes it during initial render to upgrade legacy blocks to the current format. After migration, the field has no effect on editor behavior — you can keep passing it (it's idempotent on already-migrated templates) or omit it.
When to provide this: Only when loading templates created before the v2 recommendation block was rolled out. New templates do not need it.
recommendationConfigs
A dictionary keyed by the legacy block's element ID. Each entry preserves the v1 block's runtime config so the migrator can hydrate the upgraded block.
const config: GuidoConfigInput = {
identity: { templateId: 'tpl-123', userId: 'user-456' },
partner: { name: 'acme' },
template: {
html: legacyHtml,
css: legacyCss,
migration: {
recommendationConfigs: {
// Key = legacy block element id (also present as `id` in the entry)
'recommendation-1700000000000': {
id: 1700000000000,
// Filter-driven block — productIds is empty, filters/strategy drive selection
productIds: [],
filters: [{ field: 'category', op: 'eq', value: 'shoes' }],
strategy: 'newArrivals',
shuffleProducts: false,
sendProductRequestFlag: true,
// Display & locale
currency: 'EUR',
currencySettings: { decimal: ',', thousand: '.', alignment: 'left' },
language: 'nl_NL',
decimalCount: 2,
// Layout
cardsInRow: 2,
orientation: 'vertical',
textTrimming: true,
unresponsive: false,
mobileLeftPadding: 0,
mobileRightPadding: 0,
// Snapshot of products as they were rendered in the legacy email
recommendedProducts: [/* ...legacy product objects... */],
},
},
},
},
};All fields on a LegacyRecommendationConfig entry are optional — pass whatever your storage layer has for that block. Unknown keys are preserved and forwarded to the migrator unchanged, so partner-specific extensions continue to round-trip.
Field reference
| Field | Type | Purpose |
|-------|------|---------|
| id | number | Block ID (matches the dictionary key and legacy HTML element id) |
| productIds | unknown[] | Pinned product IDs — empty when filter-driven |
| filters | unknown[] | Filter rules driving product selection |
| strategy | string | Recommendation strategy key (e.g. 'newArrivals') |
| shuffleProducts | boolean | Whether to randomize product order |
| sendProductRequestFlag | boolean | Whether the block requested live products at send time |
| currency | string | Currency code (e.g. 'EUR') |
| currencySettings | unknown | Separators, alignment, decimals |
| language | string | Locale (e.g. 'nl_NL') |
| decimalCount | string \| number | Decimal places for price display |
| cardsInRow | number | Product cards per row |
| orientation | 'vertical' \| 'horizontal' | Layout orientation |
| size | string \| number | Size variant marker (legacy) |
| verticalResponsiveness | boolean | Vertical responsiveness flag (legacy size=1 variants) |
| blockType | string | Block type marker used by some legacy variants |
| textTrimming | boolean | Whether long text is trimmed |
| unresponsive | boolean | Disable responsive scaling |
| mobileLeftPadding / mobileRightPadding | number | Mobile-only horizontal padding |
| recommendedProducts | unknown[] | Snapshot of products rendered by the legacy block |
Note on data shape: The schema uses
looseObjectbecause v1 partner data shapes vary across deployments. Some entries carryverticalResponsiveness, others carryblockType/orientation/size. Pass whatever you have — the migrator will use what it recognizes and preserve the rest.
HTML Compiler Rules
Add custom rules to transform HTML during export:
const config: GuidoConfigInput = {
identity: { templateId: 'tpl-123', userId: 'user-456' },
partner: { name: 'partner' },
compiler: {
customRules: [
// Replace rule
{
id: 'replace-domain',
type: 'replace',
search: 'old-domain.com',
replacement: 'new-domain.com',
priority: 10,
},
// Regex rule
{
id: 'remove-comments',
type: 'regex',
pattern: '<!--.*?-->',
replacement: '',
flags: 'g',
priority: 20,
},
// Custom processor
{
id: 'add-tracking',
type: 'custom',
processor: (html) => html.replace('</body>', '<img src="track.gif"/></body>'),
priority: 30,
},
],
ignoreDefaultRules: false, // Set true to skip built-in rules
},
};TypeScript Exports
// Component
import { Guido } from '@useinsider/guido';
// Types
import type {
GuidoConfig,
GuidoConfigInput,
IdentityConfig,
PartnerConfig,
TemplateConfig,
TemplateMigrationConfig,
LegacyRecommendationConfig,
EditorConfig,
UIConfig,
FeaturesConfig,
BlocksConfig,
CompilerConfig,
DynamicContent,
DefaultBlockType,
CustomBlockType,
} from '@useinsider/guido';
// Utilities
import {
validateConfig,
parseConfig,
MessageType,
ProductType,
} from '@useinsider/guido';
// Styles
import '@useinsider/guido/style';Constants
import { MessageType, ProductType } from '@useinsider/guido';
MessageType.PROMOTIONAL // 1
MessageType.TRANSACTIONAL // 2
ProductType.EMAIL // 60
ProductType.ARCHITECT // 49
ProductType.UNSUBSCRIBE_PAGES // 97Dynamic Content Modal
When the dynamic-content:open event fires, show your custom modal and call the insert method:
<template>
<Guido ref="guidoRef" :config="config" @dynamic-content:open="showModal = true" />
<!-- Your modal must have id="guido-dynamic-content-modal" -->
<YourModal v-if="showModal" id="guido-dynamic-content-modal" @select="insertContent" @close="closeModal" />
</template>
<script setup>
const showModal = ref(false);
const insertContent = (content) => {
guidoRef.value?.dynamicContent.insert({
text: content.label,
value: content.value,
fallback: content.fallback || '',
});
showModal.value = false;
};
const closeModal = () => {
guidoRef.value?.dynamicContent.close();
showModal.value = false;
};
</script>Development
# Install
bun install
# Start dev server
bun start
# Build
bun run build
# Lint & type-check
bun run lintEnvironment Variables
VITE_STRIPO_PLUGIN_ID=your_plugin_id
VITE_STRIPO_SECRET_KEY=your_secret_key
VITE_STRIPO_ROLE=your_roleLocal Testing
# Build the package
bun run build
# Create tarball
npm pack
# Install in your project
cd ../your-project
npm install ../guido/useinsider-guido-1.0.0.tgzLicense
ISC License
