{"_id":"@braedonsaunders/appkit-email-designer","name":"@braedonsaunders/appkit-email-designer","dist-tags":{"latest":"0.2.0"},"versions":{"0.2.0":{"name":"@braedonsaunders/appkit-email-designer","version":"0.2.0","description":"Drag-and-drop email and signature designer: GrapesJS authoring surface, merge-field blocks, and an email-safe compile pipeline.","license":"AGPL-3.0-or-later","type":"module","sideEffects":["*.css"],"exports":{".":{"types":"./index.d.ts","import":"./index.js","default":"./index.js"},"./react":{"types":"./react.d.ts","import":"./react.js","default":"./react.js"},"./inline":{"types":"./inline.d.ts","import":"./inline.js","default":"./inline.js"},"./styles.css":"./styles.css","./package.json":"./package.json"},"main":"./index.js","types":"./index.d.ts","dependencies":{"@braedonsaunders/appkit-email-render":"^0.1.1"},"peerDependencies":{"@grapesjs/react":"^2.0.0","grapesjs":"^0.22.16","juice":"^12.0.0","react":"^19.0.0","react-dom":"^19.0.0"},"peerDependenciesMeta":{"@grapesjs/react":{"optional":true},"grapesjs":{"optional":true},"juice":{"optional":true},"react":{"optional":true},"react-dom":{"optional":true}},"author":{"name":"Braedon Saunders"},"repository":{"type":"git","url":"git+https://github.com/braedonsaunders/appkit.git","directory":"packages/email-designer"},"homepage":"https://github.com/braedonsaunders/appkit/tree/main/packages/email-designer#readme","bugs":{"url":"https://github.com/braedonsaunders/appkit/issues"},"engines":{"node":">=22"},"keywords":["appkit","application-framework","email","email-signature","email-template","grapesjs","typescript","wysiwyg"],"_id":"@braedonsaunders/appkit-email-designer@0.2.0","_integrity":"sha512-Qef19E/JVfy1zFXPGsA7NN+LcwVGcsYeCNVXxsqXS2229aULs1PDwHs5nceQIdmEmwKVPSSvPANXZbXUGrwyrw==","_resolved":"/tmp/29b42acd04b5e935f444b823abf4105a/braedonsaunders-appkit-email-designer-0.2.0.tgz","_from":"file:braedonsaunders-appkit-email-designer-0.2.0.tgz","_nodeVersion":"22.23.2","_npmVersion":"10.9.8","dist":{"integrity":"sha512-Qef19E/JVfy1zFXPGsA7NN+LcwVGcsYeCNVXxsqXS2229aULs1PDwHs5nceQIdmEmwKVPSSvPANXZbXUGrwyrw==","shasum":"2d56f94d0aee6d961494fee6c14fa775a0fc24cc","tarball":"https://registry.npmjs.org/@braedonsaunders/appkit-email-designer/-/appkit-email-designer-0.2.0.tgz","fileCount":56,"unpackedSize":176363,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@braedonsaunders%2fappkit-email-designer@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIDXa7CZpbORAHEla4DjKF8qoT93bzyAqZfbo6RtbEuRJAiBnHRXVnI8Oo4PmSl0BXHD0xKrb95xBHgxvvcDt8j6DCg=="}]},"_npmUser":{"name":"braedonsaunders","email":"bsaunders@rassaun.com"},"directories":{},"maintainers":[{"name":"braedonsaunders","email":"bsaunders@rassaun.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/appkit-email-designer_0.2.0_1787007449063_0.259337451400226"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-17T22:57:28.888Z","0.2.0":"2026-08-17T22:57:29.228Z","modified":"2026-08-17T22:57:29.713Z"},"maintainers":[{"name":"braedonsaunders","email":"bsaunders@rassaun.com"}],"description":"Drag-and-drop email and signature designer: GrapesJS authoring surface, merge-field blocks, and an email-safe compile pipeline.","homepage":"https://github.com/braedonsaunders/appkit/tree/main/packages/email-designer#readme","keywords":["appkit","application-framework","email","email-signature","email-template","grapesjs","typescript","wysiwyg"],"repository":{"type":"git","url":"git+https://github.com/braedonsaunders/appkit.git","directory":"packages/email-designer"},"author":{"name":"Braedon Saunders"},"bugs":{"url":"https://github.com/braedonsaunders/appkit/issues"},"license":"AGPL-3.0-or-later","readme":"# @braedonsaunders/appkit-email-designer\n\nA drag-and-drop designer for email — whole messages and signatures — plus the\ncompile pipeline that turns what an author built into markup Outlook will not\nmangle.\n\nThe doctrine is **what you see is what ships**. Blocks are authored as real,\ninline-styled, table-based HTML rather than a private document model, so the\ncanvas holds delivery-ready markup from the first drag. There is no MJML step\nand no second renderer to drift out of sync.\n\n## Entry points\n\n| Import | Environment | Contents |\n| --- | --- | --- |\n| `@braedonsaunders/appkit-email-designer` | anywhere | Types, themes, block catalogs, `compileEmailDesign`, `renderEmailDesign`, `sampleMergeValues`, copy. No React, no Node built-ins. |\n| `@braedonsaunders/appkit-email-designer/react` | client | `EmailDesigner` (the authoring surface), `EmailBlockPalette`, `EmailTableToolbar`. |\n| `@braedonsaunders/appkit-email-designer/inline` | server | `inlineEmailCss` — the juice CSS inliner. Node only. |\n| `@braedonsaunders/appkit-email-designer/styles.css` | client | Designer chrome. Also `@import`s GrapesJS's stylesheet. |\n\nGrapesJS, `@grapesjs/react`, `juice`, and React are **optional peers**. An app\nthat only compiles or renders saved designs installs none of them.\n\n## The two halves of a design\n\n`compileEmailDesign` returns the pair you persist:\n\n```ts\nconst { sourceHtml, compiledHtml, errors } = compileEmailDesign(raw, {\n  inlineCss,              // from @braedonsaunders/appkit-email-designer/inline\n  fragment: true,         // signatures splice into an existing body\n})\n```\n\n- **`sourceHtml`** — inlined and sanitized, still carrying its `data-each`\n  markers. This is what the designer reopens, so a round-trip through the editor\n  is lossless.\n- **`compiledHtml`** — the same markup with markers expanded to `{{#each}}` /\n  `{{#if}}` blocks. Tokens are still embedded; this is what gets rendered per\n  recipient.\n\nBoth halves derive from the same inlined, sanitized markup and differ only in\nmarker expansion. Inlining first is what makes the round-trip lossless:\nsanitizing a *fragment* drops a top-level `<style>` block, so a signature saved\nwithout inlining would reopen stripped of every rule the editor had written to\nits stylesheet. Inline `style` attributes survive sanitizing — and they are what\nemail needs anyway, since Gmail and Outlook drop `<style>`.\n\nThe order is fixed: **inline, sanitize, expand**. The inliner cannot run last,\nbecause juice parses with cheerio and would mangle handlebars block syntax.\n\nAuthored markup is sanitized **once, here** — never per send. Merge values are\nescaped at render time by `@braedonsaunders/appkit-email-render`, so a compiled design is safe\nto store and re-render without re-sanitizing.\n\n## Rendering\n\n```ts\nconst { html, text } = renderEmailDesign(compiledHtml, {\n  agent: { name: 'Dana Reid', title: 'Operations' },\n  hazards: [{ name: 'Silica', level: 'High' }],\n})\n```\n\nDotted tokens resolve through nested objects. Substituted values are\nHTML-escaped, and the plain-text part is derived from the rendered HTML so the\ntwo always agree.\n\n## Merge fields and collections\n\nA **merge field** is a scalar the author drags in as `{{key}}`. A **collection**\nbecomes an editable table whose body row carries `data-each=\"key\"` — a real,\ninvisible attribute that survives the editor and compiles to a loop. This is the\ntrick that makes repeating rows authorable at all: a `<tr>` cannot hold the text\nnode a bare `{{#each}}` would need.\n\nKeys are held to `^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$`. A block with an unsafe\nkey is dropped rather than emitted, and one bad column voids its whole table.\n\n## Presets\n\n| Preset | Starter | Palette |\n| --- | --- | --- |\n| `email` | Centered content column on a page background | Content, Layout, Signature |\n| `signature` | Accent-ruled identity block, no page chrome | Signature |\n\nSignature blocks are deliberately table-based and narrow — Outlook renders\nfloats and flex unpredictably inside a quoted reply chain.\n\nSignature blocks and the signature starter are written against the conventional\n`agent.*` / `company.*` keys. If your catalog uses different ones, pass your own\n`initialHtml` and `extraBlocks`. The `email` starter is deliberately token-free:\nthe package cannot know which keys a host defines, and an unresolved token would\nrender as a blank in a real send.\n\n## Theming\n\n`EmailDesignerTheme` colors are baked into blocks **as they are inserted**;\nemail clients cannot resolve CSS variables. Changing the theme restyles newly\ndragged blocks, not what is already on the canvas. Every color that reaches a\n`style` attribute must be a plain hex literal — anything else falls back to the\ndefault rather than being concatenated into markup.\n\nThe designer's own chrome is plain CSS with no Tailwind or UI-kit dependency.\nOverride the `--ak-ed-*` custom properties to restyle it.\n\n## Mounting\n\nGrapesJS touches `window`, so mount behind a dynamic import with SSR disabled:\n\n```tsx\nconst EmailDesigner = dynamic(\n  () => import('@braedonsaunders/appkit-email-designer/react').then((m) => m.EmailDesigner),\n  { ssr: false },\n)\n\n<EmailDesigner\n  preset=\"signature\"\n  initialHtml={saved?.sourceHtml}\n  mergeFields={fields}\n  theme={{ accent: '#F5A623' }}\n  onChange={setDraftHtml}\n  onReady={(editor) => (editorRef.current = editor)}\n/>\n```\n\n`onChange` hands you the serialized design (`<style>` + markup) on every edit —\nfeed it straight to `compileEmailDesign` for a live preview. `onReady` gives you\nthe editor to snapshot with `serializeEmailEditor` on save.\n","readmeFilename":"README.md","_rev":"1-cacdab113fc211e66e61145a630726cf"}